c - Haskell FFI 进入 C 并返回需要多少费用?

标签 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

每个函数 cf* 都是一个 C 调用。

就性能而言,创建单个 C 函数(如 cfABC)并在 Haskell 中仅进行一次外部调用是否更好?

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

haskell 代码:

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

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

最佳答案

答案主要取决于外部调用是安全还是不安全调用。

一个 unsafe C 调用基本上只是一个函数调用,所以如果没有(非平凡的)类型转换,如果你进行三个外部调用,就会有三个函数调用,而当你进行在 C 中编写一个包装器,这取决于在编译 C 时可以内联多少组件函数,因为 GHC 无法内联对 C 的外部调用。这样的函数调用通常非常便宜(它只是参数的副本和代码的跳转),所以两者之间的差异很小,当没有 C 函数可以内联到包装器中时,包装器应该稍微慢一些,并且当所有内容都可以内联时稍微快一些[在我的基准测试中确实如此,+1.5ns resp。 -3.5ns,其中三个外部调用花费了大约 12.7ns 来返回参数]。如果函数做了一些重要的事情,那么差异可以忽略不计(如果它们没有做任何重要的事情,你最好用 Haskell 编写它们,让 GHC 内联代码)。

安全 C 调用涉及保存一些重要的状态、锁定、可能产生新的操作系统线程,因此需要更长的时间。然后,与外部调用的成本相比,在 C 中调用一个函数的小开销可以忽略不计 [除非传递参数需要不寻常的复制量,许多巨大的 struct 左右]。在我什么都不做的基准测试中

{-# 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]。

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

关于c - Haskell FFI 进入 C 并返回需要多少费用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14519905/

相关文章:

mysql - 通用投票表与单独的投票表?

c - 找到可能的总和数的有效方法

haskell - 有什么方法可以恢复足够的懒惰以在单子(monad)中喜结连理吗?

haskell - Arrow 库中 `first` 的实现

从 C 程序调用 Windows 10 bash

c - 使用接口(interface)的质数

c - 第 25 行中的运行时错误 : Char 35: runtime error: member access within null pointer of type 'struct HASH_TABLE' (solution. c)

c - 指向字符串的指针数组

javascript - 提高大规模 IndexedDB 插入的性能

haskell - foldr 如何在此数据树上工作?