31

如果我想调用多个 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 调用。

就性能而言,创建一个类似的 C 函数cfABC并在 Haskell 中只进行一次外部调用会更好吗?

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

哈斯克尔代码:

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

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

4

2 回答 2

20

答案主要取决于外来电话是 asafe还是unsafecall。

一个unsafeC 调用基本上只是一个函数调用,所以如果没有(非平凡的)类型转换,如果你进行三个外部调用,就会有三个函数调用,当你用 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]。

所以一个safec 调用大约需要 100ns,通常,将它们包装成一个调用会更快。但是,如果被调用的函数做了一些足够重要的事情,那么差异就无关紧要了。

于 2013-01-25T18:32:58.550 回答
-3

这可能很大程度上取决于确切的 Haskell 编译器、C 编译器以及将它们绑定在一起的胶水。唯一确定的方法是测量它。

从更哲学的角度来看,每次混合语言时,都会为新手制造障碍:在这种情况下,精通 Haskell 和 C 是不够的(这已经提供了一个狭窄的集合),但你还必须知道调用约定和其他不足以与它们一起工作的东西。很多时候有一些微妙的问题需要处理(即使从 C++ 调用 C,它们是非常相似的语言也不是微不足道的)。除非有非常令人信服的理由,否则我会坚持使用单一语言。我能临时想到的唯一例外是创建例如 Haskell 绑定到预先存在的复杂库,比如 Python 的 NumPy。

于 2013-01-25T14:06:23.397 回答