sourcetip

해스켈 FFI가 C로 들어갔다가 다시 돌아오는데 얼마입니까?

fileupload 2023. 10. 25. 23:40
반응형

해스켈 FFI가 C로 들어갔다가 다시 돌아오는데 얼마입니까?

앞의 결과에 따라 각각 1개 이상의 C 기능을 호출하려면 3개의 호출을 처리하는 포장지 C 기능을 만드는 것이 좋습니까?종류를 변환하지 않고 하스켈 FFI를 사용하는 것과 같은 비용이 들까요?

내가 다음과 같은 하스켈 코드를 가지고 있다고 가정합니다.

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

각기능cf*C 콜입니다.

성능적인 면에서 다음과 같은 단일 C 기능을 만드는 것이 더 나을까요?cfABC해스켈에서 외국 전화 한 통만 걸었단 말입니까?

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

하스켈의 C 통화 수행 비용을 측정하는 방법은?C 함수 자체의 비용이 아니라, 하스켈에서 C로 그리고 다시 돌아가는 "맥락 전환"의 비용입니다.

답은 대부분 외국 전화가 A인지 여부에 달려 있습니다.safe혹은unsafe불러.

unsafeC 호출은 기본적으로 함수 호출일 뿐이므로 (비정상적인) 형태의 변환이 없는 경우, C로의 외호는 GHC에 의해 내선화될 수 없기 때문에 C를 컴파일할 때 구성 요소 함수 중 몇 개를 인라인화할 수 있는지에 따라 세 개의 함수 호출이 있고, C에 래퍼를 작성할 때 하나에서 네 개 사이가 있습니다.이러한 함수 호출은 일반적으로 매우 저렴합니다(인수를 복사하고 코드로 이동하는 것에 불과합니다). 따라서 어느 쪽이든 차이가 작습니다. C 함수를 래퍼에 인라인할 수 없을 때 래퍼는 약간 느려야 하며 모든 함수를 인라인할 수 있을 때는 약간 빨라야 합니다. [제 벤치마킹에서는 실제로 +1.5ns resp. -3.5ns.여기서 3개의 외국 전화는 단지 논쟁을 돌려주는 모든 것에 대해 약 12.7ns가 걸렸습니다.].함수가 사소한 것을 수행하는 경우 차이는 무시할 수 있습니다(그리고 사소한 것을 수행하지 않는 경우에는 GHC가 코드를 입력하도록 하기 위해 하스켈로 작성하는 것이 좋을 것입니다).

A safeC 호출은 사소하지 않은 양의 상태를 저장하고 잠금을 설정하며 새로운 OS 스레드를 생성할 수 있으므로 훨씬 더 오랜 시간이 걸립니다.그러면 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]입니다.

비상대기상태safec 통화는 대략 100ns가 소요되며, 보통 한 번의 통화로 마무리하는 것이 더 빠릅니다.그러나 여전히 호출된 함수가 충분히 사소한 것을 수행한다면 그 차이는 중요하지 않을 것입니다.

그것은 아마도 당신의 정확한 Haskell 컴파일러, C 컴파일러, 그리고 그것들을 결합하는 접착제에 매우 달려있을 것입니다.확실히 알 수 있는 유일한 방법은 그것을 측정하는 것입니다.

좀 더 철학적인 곡조로 언어를 섞을 때마다 새로운 고객에게 장벽을 만들어 줍니다.이 경우 해스켈과 C를 유창하게 구사하는 것은 충분하지 않지만(이미 좁은 집합을 제공함), 호출 규칙과 함께 일하기에는 충분하지 않은 것도 알아야 합니다.그리고 종종 처리해야 할 미묘한 문제들이 있습니다(심지어 C++에서 C를 부르는 것조차 매우 유사한 언어들은 전혀 사소한 것이 아닙니다).아주 설득력 있는 이유가 없는 한, 저는 한 가지 언어를 고수할 것입니다.내가 즉시 생각할 수 있는 유일한 예외는 예를 만드는 것입니다.기존의 복잡한 라이브러리에 바인딩된 해스켈(Haskell), 예를 들어 NumPy for Python.

언급URL : https://stackoverflow.com/questions/14519905/how-much-does-it-cost-for-haskell-ffi-to-go-into-c-and-back

반응형