Ответ 1
Ответ зависит в основном от того, является ли внешний вызов вызовом safe
или unsafe
.
An unsafe
C вызов - это просто вызов функции, поэтому, если нет (нетривиального) преобразования типа, есть три вызова функций, если вы делаете три внешних вызова, а между 1 и 4, когда вы пишете обертку в C, в зависимости от того, сколько компонентов компонента может быть встроено при компиляции C, поскольку внешний вызов C не может быть встроен GHC. Такой вызов функции, как правило, очень дешев (это просто копия аргументов и переход к коду), поэтому разница небольшая в любом случае, оболочка должна быть немного медленнее, если в оболочку не может быть включена функция C, и немного быстрее, когда все могут быть встроены [и это действительно имело место в моем бенчмаркинге, + 1.5ns соответственно. -3.5ns, где три внешних вызова заняли около 12.7ns для всего, что только вернуло аргумент]. Если функции делают что-то нетривиальное, разница незначительна (и если они ничего не делают нетривиальными, вы, вероятно, лучше напишите их в Haskell, чтобы GHC установил код).
A safe
C вызов включает сохранение некоторого нетривиального количества состояния, блокирование, возможно, появление нового потока ОС, так что это займет гораздо больше времени. Тогда небольшие накладные расходы, возможно, вызывая одну функцию больше в C, пренебрежимо малы по сравнению со стоимостью внешних вызовов [если передача аргументов не требует необычного количества копий, многие огромные struct
или около того]. В своем тесте do-nothing
{-# 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 принимает примерно 100 нс и, как правило, он быстрее сворачивает их в один вызов. Но все же, если вызываемые функции делают что-то достаточно нетривиальное, разница не имеет значения.