为了为Double创建一个任意精度的浮点/插入替换,我试图使用FFI包装MPFR,但尽管我尽了所有努力,最简单的代码还是不起作用。它编译、运行,但在假装工作了一段时间后,它滑稽地崩溃了。一个简单的C版本的代码愉快地将数字"1"打印到(640位小数)总共10000次。当Haskell版本被要求做同样的事情时,它只在289次打印出"1.0000…0000"后就悄悄地破坏了数据,在385次打印出后,它会导致断言失败并爆炸。我不知道如何进行调试,因为它"应该可以工作"。
代码可在http://hpaste.org/10923并在下载http://www.updike.org/mpfr-broken.tar.gz
我在FreeBSD 6上使用GHC6.83,在Mac OS X上使用GHC8.8.2。注意,你需要安装MPFR(使用2.3.2测试),并为libs和头文件(以及GMP中的文件)安装正确的路径(更改Makefile)才能成功编译它。
问题
-
为什么C版本有效,而Haskell版本却失效了?接近外国金融机构时,我还缺少什么?我尝试了StablePtrs,得到了完全相同的结果。
-
其他人可以通过编译和运行我的代码来验证这是否是仅限Mac/BSD的问题吗?(C代码"有效"吗?Haskell代码"nowworks"有效吗?)Linux和Windows上的任何人都可以尝试编译/运行,看看你是否得到了相同的结果吗?
C代码:(works.C)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gmp.h>
#include <mpfr.h>
#include "mpfr_ffi.c"
int main()
{
int i;
mpfr_ptr one;
mpf_set_default_prec_decimal(640);
one = mpf_set_signed_int(1);
for (i = 0; i < 10000; i++)
{
printf("%dn", i);
mpf_show(one);
}
}
Haskell代码:(Main.hs--不工作)
module Main where
import Foreign.Ptr ( Ptr, FunPtr )
import Foreign.C.Types ( CInt, CLong, CULong, CDouble )
import Foreign.StablePtr ( StablePtr )
data MPFR = MPFR
foreign import ccall "mpf_set_default_prec_decimal"
c_set_default_prec_decimal :: CInt -> IO ()
setPrecisionDecimal :: Integer -> IO ()
setPrecisionDecimal decimal_digits = do
c_set_default_prec_decimal (fromInteger decimal_digits)
foreign import ccall "mpf_show"
c_show :: Ptr MPFR -> IO ()
foreign import ccall "mpf_set_signed_int"
c_set_signed_int :: CLong -> IO (Ptr MPFR)
showNums k n = do
print n
c_show k
main = do
setPrecisionDecimal 640
one <- c_set_signed_int (fromInteger 1)
mapM_ (showNums one) [1..10000]
这是GHC的一个已知问题,因为GHC内部使用GMP的方式(维护Integers)。
显然,堆中的C数据基本上在所有情况下都由GHC单独处理,但代码除外,该代码使用FFI访问GMP或任何依赖GMP的C库(如我想要使用的MPFR)。有一些变通办法(痛苦),但"正确"的方法是要么破解GHC(困难),要么让西蒙夫妇消除GHC对GMP的依赖(困难)。
我在上也看到了这个问题
$ uname -a
Linux burnup 2.6.26-gentoo-r1 #1 SMP PREEMPT Tue Sep 9 00:05:54 EDT 2008 i686 Intel(R) Pentium(R) 4 CPU 2.80GHz GenuineIntel GNU/Linux
$ gcc --version
gcc (GCC) 4.2.4 (Gentoo 4.2.4 p1.0)
$ ghc --version
The Glorious Glasgow Haskell Compilation System, version 6.8.3
我还看到输出从1.0000…000变为1.0000…[垃圾]。
让我们看看,以下确实有效:
main = do
setPrecisionDecimal 640
mapM_ (const $ c_set_signed_int (fromInteger 1) >>= c_show) [1..10000]
这将问题缩小到CCD_ 1的部分在运行时以某种方式被撞击。然而,查看ghc -C
和ghc -S
的输出并没有给我任何提示。
嗯,./noworks +RTS -H1G
也起作用,而./noworks +RTS -k[n]k
,对于不同的[n]
值,以不同的方式显示故障。
我没有可靠的线索,但我脑海中浮现出两种可能性:
- GHC运行时使用的GMP和MPFR有一些奇怪的交互
- GHC运行时中调用的C函数的堆栈空间有限,MPFR处理不好
话虽如此。。。你推出自己的绑定而不是使用HMPFR是有原因的吗?
HMPFR的维护人员AlešBizjak在haskell咖啡馆发帖,展示了如何阻止GHC控制四肢的分配(因此让它们独处,而不是对它们进行GCing和重击):
mpfr_ptr mpf_new_mpfr()
{
mpfr_ptr result = malloc(sizeof(__mpfr_struct));
if (result == NULL) return NULL;
/// these three lines:
mp_limb_t * limb = malloc(mpfr_custom_get_size(mpfr_get_default_prec()));
mpfr_custom_init(limb, mpfr_get_default_prec());
mpfr_custom_init_set(result, MPFR_NAN_KIND, 0, mpfr_get_default_prec(), limb);
return result;
}
对我来说,这比在GHC中编写GMP的替代品要容易得多,如果我真的想使用任何依赖GMP的库,这将是唯一的选择。