31

前の関数の結果に応じて、複数の C 関数を呼び出したい場合、3 つの呼び出しを処理するラッパー C 関数を作成する方がよいでしょうか? 型を変換せずに Haskell FFI を使用するのと同じコストがかかりますか?

次の Haskell コードがあるとします。

foo :: CInt -> IO CInt
foo x = do
  a <- cfA x
  b <- cfB a
  c <- cfC c
  return c

各関数cf*は C 呼び出しです。

Haskell でのような単一の C 関数を作成し、cfABC外部呼び出しを 1 つだけ行う方が、パフォーマンスの点で優れていますか?

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 へ、およびその逆の「コンテキスト切り替え」のコストです。

4

2 に答える 2

20

答えは、主に外国からの呼び出しが であるか、safeまたは呼び出しであるかによって異なりunsafeます。

アンunsafeC 呼び出しは基本的に単なる関数呼び出しであるため、(重要な) 型変換がない場合、3 つの外部呼び出しを行うと 3 つの関数呼び出しがあり、C でラッパーを作成する場合は、関数呼び出しの数に応じて 1 から 4 になります。 C への外部呼び出しは GHC によってインライン化できないため、コンポーネント関数は C をコンパイルするときにインライン化できます。このような関数呼び出しは一般に非常に安価です (引数のコピーとコードへのジャンプにすぎません)。そのため、どちらの方法でも違いは小さく、C 関数をラッパーにインライン化できない場合、ラッパーはわずかに遅くなるはずです。すべてをインライン化できる場合はわずかに高速です [実際、私のベンチマークでは +1.5ns resp. -3.5ns で、3 つの外部呼び出しでは、引数を返すだけで約 12.7ns かかりました]。関数が自明でないことをする場合、

C 呼び出しには、かなりの量の状態のsafe保存、ロック、場合によっては新しい OS スレッドの生成が含まれるため、はるかに時間がかかります。その場合、おそらく C で 1 つの関数をさらに呼び出すことによる小さなオーバーヘッドは、外部呼び出しのコストと比較して無視できます [引数を渡すために異常な量のコピー、多くの巨大なstructs などを必要としない限り]。私の何もしないベンチマークでは

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

そのため、safec 呼び出しには約 100ns かかり、通常は、それらを 1 回の呼び出しにまとめた方が高速です。それでも、呼び出された関数が十分に自明でないことを行う場合、違いは問題になりません。

于 2013-01-25T18:32:58.550 に答える
-3

それはおそらくあなたの正確なHaskellコンパイラ、Cコンパイラ、そしてそれらを結合する接着剤に大きく依存します。確実に見つける唯一の方法はそれを測定することです。

より哲学的な調子では、言語を混合するたびに、新参者の障壁が作成されます。この場合、HaskellとC(すでに狭いセットを提供しています)に堪能であるだけでは十分ではありませんが、呼び出し規約も知っておく必要があります規約とそれらを扱うのに十分ではないもの。そして、多くの場合、処理すべき微妙な問題があります(C ++からCを呼び出す場合でも、非常によく似た言語であり、些細なことではありません)。非常に説得力のある理由がない限り、私は単一の言語に固執します。私が手に負えないと考えることができる唯一の例外は、たとえば、Python用のNumPyのような既存の複雑なライブラリへのHaskellバインディングを作成することです。

于 2013-01-25T14:06:23.397 に答える