¿Cuánto cuesta para Haskell FFI entrar en C y volver?


Si quiero llamar a más de una función C, cada una dependiendo del resultado de la anterior, ¿es mejor crear una función C que maneje las tres llamadas? ¿Costará lo mismo que usar Haskell FFI sin convertir tipos?

Supongamos que tengo el siguiente código de Haskell:

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

Cada función cf* es una llamada C.

¿Es mejor, en términos de rendimiento, crear una sola función C como cfABC y hacer solo una llamada extranjera Haskell?

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

Código Haskell:

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

¿Cómo medir el coste de rendimiento de una llamada C desde Haskell? No el costo de la función C en sí, sino el costo del "cambio de contexto" de Haskell a C y viceversa.

Author: Thiago Negri, 2013-01-25

2 answers

La respuesta depende principalmente de si la llamada extranjera es una llamada safe o una llamada unsafe.

Una llamada a unsafe C es básicamente una llamada a función, por lo que si no hay conversión de tipo (no trivial), hay tres llamadas a función si realiza tres llamadas extranjeras, y entre una y cuatro cuando escribe un wrapper en C, dependiendo de cuántas de las funciones del componente se pueden inlinear al compilar la C, ya que una llamada extranjera en C no puede ser inlineada por GHC. Tal llamada a la función es generalmente muy barato (es solo una copia de los argumentos y un salto al código), por lo que la diferencia es pequeña de cualquier manera, la envoltura debe ser ligeramente más lenta cuando ninguna función de C se puede insertar en la envoltura, y ligeramente más rápida cuando todo se puede insertar [y ese fue el caso en mi evaluación comparativa, +1.5 ns resp. -3.5 ns donde las tres llamadas extranjeras tomaron alrededor de 12.7 ns para todo solo devolviendo el argumento]. Si las funciones hacen algo no trivial, la diferencia es insignificante (y si no están haciendo cualquier cosa no trivial, probablemente sería mejor escribirlas en Haskell para dejar que GHC inline el código).

Una llamada a safe C implica guardar una cantidad no trivial de estado, bloquear, posiblemente generar un nuevo hilo del sistema operativo, por lo que lleva mucho más tiempo. Entonces la pequeña sobrecarga de tal vez llamar a una función más en C es insignificante en comparación con el costo de las llamadas extranjeras [a menos que pasar los argumentos requiera una cantidad inusual de copia, muchos structs enormes más o menos]. En mi no hacer nada parámetro

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

Donde todas las funciones de C solo devuelven el argumento, la media para la llamada envuelta individual es un poco superior a 100ns [105-112], y para las tres llamadas separadas alrededor de 300ns [290-315].

Así que una llamada safe c toma aproximadamente 100ns y, por lo general, es más rápido envolverlos en una sola llamada. Pero aún así, si las funciones llamadas hacen algo suficientemente no trivial, la diferencia no importará.

 20
Author: Daniel Fischer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-25 18:32:58

Eso probablemente depende muy mucho de tu compilador exacto de Haskell, el compilador de C, y el pegamento que los une. La única manera de averiguarlo con seguridad es medirlo.

En un tono más filosófico, cada vez que se mezclan idiomas se crea una barrera para newcommers: En este caso no es suficiente con ser fluido en Haskell y C (que ya da un conjunto estrecho), pero también hay que conocer las convenciones de llamada y lo que no es suficiente para trabajar con ellos. Y muchas veces hay problemas sutiles para manejar (incluso llamar a C desde C++, que son muy lenguajes similares no es en absoluto trivial). A menos que haya muy razones convincentes, me quedaría con un solo idioma. La única excepción que se me ocurre es para crear, por ejemplo, enlaces Haskell a una biblioteca compleja preexistente, algo como NumPy para Python.

 -3
Author: vonbrand,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-01-25 14:06:23