Thi*_*gri 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
Run Code Online (Sandbox Code Playgroud)
每个函数cf*
都是C调用.
在性能方面,创建单个C函数cfABC
并在Haskell中只进行一次外部调用会更好吗?
int cfABC(int x) {
int a, b, c;
a = cfA(x);
b = cfB(a);
c = cfC(b);
return c;
}
Run Code Online (Sandbox Code Playgroud)
Haskell代码:
foo :: CInt -> IO CInt
foo x = do
c <- cfABC x
return c
Run Code Online (Sandbox Code Playgroud)
如何衡量Haskell的C调用的性能成本?不是C函数本身的成本,而是从Haskell到C的"上下文切换"的成本.
Dan*_*her 20
答案主要取决于外国呼叫是呼叫safe
还是unsafe
呼叫.
一个unsafe
调用C基本上是一个函数调用,因此,如果没有(平凡)类型转换,还有如果你让三家外资调用三个函数调用,当你写一至四个在C包装,这取决于有多少在编译C时可以内联组件函数,因为GHC无法内联到C的外部调用.这样的函数调用通常非常便宜(它只是参数的副本和跳转到代码),所以差别很小,当没有C函数可以内联到包装器时,包装器应该稍微慢一些,并且当所有内容都可以内联时稍微快一点[在我的基准测试中确实如此,+ 1.5ns分别为.-3.5ns,其中三个外国电话大约需要12.7ns才能返回参数].如果函数做了一些非常重要的事情,那么差异可以忽略不计(如果它们没有做任何重要的事情,你最好在Haskell中编写它们让GHC内联代码).
一个safe
调用C涉及到节能状态的一些平凡的数量,锁定,可能产生一个新的操作系统线程,因此需要更长的时间.然后,与外部调用的成本相比,可能在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
]
Run Code Online (Sandbox Code Playgroud)
所有C函数只返回参数,单个包装调用的平均值略高于100ns [105-112],而三个单独调用的平均值约为300ns [290-315].
所以safe
c调用大约需要100ns,通常,将它们包装成单个调用会更快.但是,如果被调用函数做了一些非常重要的事情,那么差别就不重要了.