Haskell-> C FFI性能

问题描述 投票:1回答:1

这是Performance considerations of Haskell FFI / C?的双重问题:我想以尽可能小的开销调用C函数。

要设置场景,我具有以下C函数:

typedef struct
{
    uint64_t RESET;
} INPUT;

typedef struct
{
    uint64_t VGA_HSYNC;
    uint64_t VGA_VSYNC;
    uint64_t VGA_DE;
    uint8_t VGA_RED;
    uint8_t VGA_GREEN;
    uint8_t VGA_BLUE;
} OUTPUT;

void Bounce(const INPUT* input, OUTPUT* output);

让我们从C运行​​它,并用gcc -O3计时它:

int main (int argc, char **argv)
{
    INPUT input;
    input.RESET = 0;
    OUTPUT output;

    int cycles = 0;

    for (int j = 0; j < 60; ++j)
    {
        for (;; ++cycles)
        {
            Bounce(&input, &output);
            if (output.VGA_HSYNC == 0 && output.VGA_VSYNC == 0) break;
        }

        for (;; ++cycles)
        {
            Bounce(&input, &output);
            if (output.VGA_DE) break;
        }
    }

    printf("%d cycles\n", cycles);
}

将其运行25152001个周期大约需要400毫秒:

$ time ./Bounce
25152001 cycles

real    0m0.404s
user    0m0.403s
sys     0m0.001s

现在让我们写一些Haskell代码来设置FFI(请注意,BoolStorable实例确实使用完整的int):

data INPUT = INPUT
    { reset :: Bool
    }

data OUTPUT = OUTPUT
    { vgaHSYNC, vgaVSYNC, vgaDE :: Bool
    , vgaRED, vgaGREEN, vgaBLUE :: Word64
    }
    deriving (Show)

foreign import ccall unsafe "Bounce" topEntity :: Ptr INPUT -> Ptr OUTPUT -> IO ()

instance Storable INPUT where ...
instance Storable OUTPUT where ...

并且让我们做我认为在功能上等同于我们之前的C代码的事情:

main :: IO ()
main = alloca $ \inp -> alloca $ \outp -> do
    poke inp $ INPUT{ reset = False }

    let loop1 n = do
            topEntity inp outp
            out@OUTPUT{..} <- peek outp
            let n' = n + 1
            if not vgaHSYNC && not vgaVSYNC then loop2 n' else loop1 n'
        loop2 n = do
            topEntity inp outp
            out <- peek outp
            let n' = n + 1
            if vgaDE out then return n' else loop2 n'
        loop3 k n
          | k < 60 = do
              n <- loop1 n
              loop3 (k + 1) n
          | otherwise = return n

    n <- loop3 (0 :: Int) (0 :: Int)
    printf "%d cycles" n

我使用-O3使用GHC 8.6.5构建它,并且得到了超过3秒的时间!

$ time ./.stack-work/dist/x86_64-linux/Cabal-2.4.0.1/build/sim-ffi/sim-ffi
25152001 cycles

real   0m3.468s
user   0m3.146s
sys    0m0.280s

而且这也不是启动时的固定开销:如果我运行10次循环,我从C处得到大约3.5秒,从Haskell得到34秒。

如何减少Haskell-> C FFI开销?

c performance haskell marshalling ffi
1个回答
0
投票

我设法减少了开销,因此现在2500万个呼叫在1.2秒内完成。更改为:

  1. loop1参数中使loop2loop3n严格(使用BangPatterns
  2. INLINEpeek实例中的OUTPUT添加Storable编译指示

当然,#1点很愚蠢,但这就是我之前没有进行概要分析的结果。仅凭这一变化,我就可以节省1.5秒。...

然而,第2点具有很多意义,并且通常适用。它还解决了@Thomas M. DuBuisson的评论:

您是否曾经在haskell中需要Haskell结构?如果您只是将其保留为指向内存的指针,并且具有一些测试功能(例如vgaVSYNC :: Ptr OUTPUT -> IO Bool),那么它将保存复制,分配和GC在每次调用时的工作日志。

在最终的完整程序中,我确实需要查看OUTPUT的所有字段。但是,内联peek时,GHC很乐意进行大小写转换,因此我可以在Core中看到现在没有分配OUTPUT值。 peek的输出直接使用。

© www.soinside.com 2019 - 2024. All rights reserved.