Haskell FFI/C MPFR库包装器问题



为了为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]  
Judah Jacobsen在Haskell咖啡馆邮件列表上回答了这个问题:

这是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 -Cghc -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的库,这将是唯一的选择。

最新更新