答案主要取决于外来电话是 asafe
还是unsafe
call。
一个unsafe
C 调用基本上只是一个函数调用,所以如果没有(非平凡的)类型转换,如果你进行三个外部调用,就会有三个函数调用,当你用 C 编写包装器时,会有一到四个,这取决于有多少编译 C 时可以内联组件函数,因为 GHC 不能内联对 C 的外部调用。这样的函数调用通常非常便宜(它只是参数的副本和代码的跳转),因此无论哪种方式差异都很小,当没有 C 函数可以内联到包装器时,包装器应该稍微慢一些,并且当所有都可以内联时稍微快一点[在我的基准测试中确实是这种情况,分别为 +1.5ns。-3.5ns,其中三个外部调用只返回参数大约需要 12.7ns]。如果函数做了一些不平凡的事情,
Csafe
调用涉及保存一些重要的状态、锁定、可能产生一个新的 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,通常,将它们包装成一个调用会更快。但是,如果被调用的函数做了一些足够重要的事情,那么差异就无关紧要了。