如何强制GHC内联FFI调用?

6

我编写了一个小的C模块来提高性能,但是GHC不会内联外部函数,并且调用成本消除了加速效果。 例如,test.h

int inc (int x);

test.c:

#include "test.h"
int inc(int x) {return x + 1;}

Test.hc:

{-# LANGUAGE ForeignFunctionInterface #-}
module Test (inc) where
import Foreign
import Foreign.C
foreign import ccall unsafe "test.h inc" c_inc :: CInt -> CInt
inc = fromIntegral . c_inc . fromIntegral
{-# INLINE c_inc #-}
{-# INLINE inc #-}

Main.hs:

import System.Environment
import Test
main = do {args <- getArgs; putStrLn . show . inc . read . head $ args }

制作:
$ gcc -O2 -c test.c
$ ghc -O3 test.o Test.hs
$ ghc --make -O3 test.o Main
$ objdump -d Main > Main.as

最后,在Main.as中,我使用了callq <inc>指令,而不是期望的inc指令。


3
你希望ghc在其生成的代码中内联一个C函数?如果使用-via-C选项,这可能有效,否则是无望的(因为它将需要ghc读取C代码并为其生成代码)。 - augustss
2
在没有链接时优化的情况下是不可能的。尝试一种(hacky)方法,将Haskell和C编译为LLVM位码,使用llvm-link组合.bc文件,使用opt进行优化,然后使用llc发出可执行代码。 - Mikhail Glushenkov
@MikhailGlushenkov,你能写一个制作命令序列的草图吗?我无法通过谷歌了解如何从Haskell代码中获取.bc文件。 - leventov
1
inc 提供一个类型。目前您正在将其转换为整数,这是您的意图吗?那会使 FFI 的开销过大(每次调用约 900 纳秒,如果我没记错的话)。 - Don Stewart
@leventov -keep-llvm-files 会给出 .ll 汇编代码,然后您可以使用 llvm-as 进行编译。 - Mikhail Glushenkov
1个回答

9

GHC不会通过其asm后端或LLVM后端内联C代码。通常,您只有在所调用的内容确实非常耗费资源时才会为了性能而调用C。递增一个int并不是这样的情况,因为我们已经有了primops来处理它。

现在,如果您通过C进行调用,则可能会让GCC内联一些内容(请检查生成的汇编代码)。

现在,但是,您可以采取一些措施来最小化调用成本:

foreign import ccall unsafe "test.h inc" c_inc :: CInt -> CInt

inc = fromIntegral . c_inc . fromIntegral

inc提供一个类型签名。在这里,你正在支付宝贵的周期来转换为整数。

像你所做的那样将调用标记为“不安全”,以便在调用之前不会将运行时记录到书签中。

测量FFI调用开销 - 它应该在纳秒级别。但是,如果您发现它仍然太昂贵,您可以编写一个新的原始操作并直接跳转到它。 但是你最好先了解你的标准数字。


实际上,我的“inc”是一组无分支SSE最小-最大函数:https://gist.github.com/4476908 - leventov
啊,我明白了——你确实想要新的primops。你是在复制http://hackage.haskell.org/trac/ghc/ticket/3557的某些东西吗? - Don Stewart
一般来说不会,但也许这些最小-最大指令在工单中特别考虑了,我还没有详细研究过。 - leventov

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接