Haskell FFI进入C并返回需要多少钱?

时间:2013-01-25 10:29:29

标签: c performance haskell ffi

如果我想调用多个C函数,每个函数取决于前一个函数的结果,是否最好创建一个处理三个调用的包装器C函数?它是否与使用Haskell FFI而不转换类型相同?

假设我有以下Haskell代码:

foo :: CInt -> IO CInt
foo x = do
  a <- cfA x
  b <- cfB a
  c <- cfC c
  return c

每个函数cf*都是C调用。

在性能方面,创建像cfABC这样的单个C函数并在Haskell中只进行一次外部调用会更好吗?

int cfABC(int x) {
   int a, b, c;
   a = cfA(x);
   b = cfB(a);
   c = cfC(b);
   return c;
}

Haskell代码:

foo :: CInt -> IO CInt
foo x = do
  c <- cfABC x
  return c

如何衡量Haskell的C调用的性能成本?不是C函数本身的成本,而是从Haskell到C的“上下文切换”的成本。

2 个答案:

答案 0 :(得分:20)

答案主要取决于外国电话是safe还是unsafe电话。

unsafe C调用基本上只是一个函数调用,所以如果没有(非平凡的)类型转换,如果你进行三次外来调用,有三个函数调用,当你写一个包装器时,有一个和四个函数调用在C中,取决于在编译C时可以内联多少组件函数,因为GHC无法内联到C的外部调用。这样的函数调用通常非常便宜(它只是参数的副本和跳转到代码),所以差别很小,当没有C函数可以内联到包装器时,包装器应该稍微慢一些,并且当所有内容都可以内联时稍微快一点[在我的基准测试中确实如此,+ 1.5ns分别为。 -3.5ns,其中三个外国电话大约需要12.7ns才能返回参数]。如果函数做了一些非常重要的事情,差异可以忽略不计(如果它们没有做任何重要的事情,你最好在Haskell中编写它们让GHC内联代码)。

safe C调用涉及保存一些非常重要的状态,锁定,可能会产生新的OS线程,因此需要更长的时间。然后,与外部调用的成本相比,可能在C中调用一个函数的小开销可以忽略不计[除非传递参数需要不寻常的复制量,许多巨大的struct s左右]。在我无所事事的基准

{-# LANGUAGE ForeignFunctionInterface #-}
module Main (main) where

import Criterion.Main
import Foreign.C.Types
import Control.Monad

foreign import ccall safe "funcs.h cfA" c_cfA :: CInt -> IO CInt
foreign import ccall safe "funcs.h cfB" c_cfB :: CInt -> IO CInt
foreign import ccall safe "funcs.h cfC" c_cfC :: CInt -> IO CInt
foreign import ccall safe "funcs.h cfABC" c_cfABC :: CInt -> IO CInt

wrap :: (CInt -> IO CInt) -> Int -> IO Int
wrap foo arg = fmap fromIntegral $ foo (fromIntegral arg)

cfabc = wrap c_cfABC

foo :: Int -> IO Int
foo = wrap (c_cfA >=> c_cfB >=> c_cfC)

main :: IO ()
main = defaultMain
            [ bench "three calls" $ foo 16
            , bench "single call" $ cfabc 16
            ]

其中所有C函数只返回参数,单个包装调用的平均值略高于100ns [105-112],而三个单独调用的平均值约为300ns [290-315]。

因此,safe c调用大约需要100ns,通常,将它们包装成单个调用会更快。但是,如果被调用的函数做了一些非常重要的事情,那么差别就无所谓了。

答案 1 :(得分:-3)

这可能取决于非常你的确切Haskell编译器,C编译器以及将它们绑定在一起的粘合剂。唯一能找到答案的方法就是测量它。

在一个更哲学的曲调上,每次你混合语言都会为新手们创造一个障碍:在这种情况下,它不足以流利于Haskell和C(已经给出了一个狭窄的集合),但你还必须知道调用约定以及与它们一起工作的东西。很多时候还有一些微妙的问题需要处理(即使从C ++调用C语言,非常类似的语言也不是一件容易的事)。除非有非常令人信服的理由,否则我会坚持使用单一语言。我能想到的唯一例外是创造例如Haskell绑定到预先存在的复杂库,类似于NumPy for Python。