答えは、主に外国からの呼び出しが であるか、safe
または呼び出しであるかによって異なりunsafe
ます。
アンunsafe
C 呼び出しは基本的に単なる関数呼び出しであるため、(重要な) 型変換がない場合、3 つの外部呼び出しを行うと 3 つの関数呼び出しがあり、C でラッパーを作成する場合は、関数呼び出しの数に応じて 1 から 4 になります。 C への外部呼び出しは GHC によってインライン化できないため、コンポーネント関数は C をコンパイルするときにインライン化できます。このような関数呼び出しは一般に非常に安価です (引数のコピーとコードへのジャンプにすぎません)。そのため、どちらの方法でも違いは小さく、C 関数をラッパーにインライン化できない場合、ラッパーはわずかに遅くなるはずです。すべてをインライン化できる場合はわずかに高速です [実際、私のベンチマークでは +1.5ns resp. -3.5ns で、3 つの外部呼び出しでは、引数を返すだけで約 12.7ns かかりました]。関数が自明でないことをする場合、
C 呼び出しには、かなりの量の状態のsafe
保存、ロック、場合によっては新しい OS スレッドの生成が含まれるため、はるかに時間がかかります。その場合、おそらく C で 1 つの関数をさらに呼び出すことによる小さなオーバーヘッドは、外部呼び出しのコストと比較して無視できます [引数を渡すために異常な量のコピー、多くの巨大な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 関数は引数を返すだけで、1 回のラップされた呼び出しの平均は 100ns を少し上回り [105-112]、3 つの個別の呼び出しの平均は約 300ns [290-315] です。
そのため、safe
c 呼び出しには約 100ns かかり、通常は、それらを 1 回の呼び出しにまとめた方が高速です。それでも、呼び出された関数が十分に自明でないことを行う場合、違いは問題になりません。