performance - 性能:从C 调用Haskell函数的开销为何?

  显示原文与译文双语对照的内容
124 4

我注意到在C 中调用Haskell函数的开销很大,远远大于原生C 函数调用的开销。为了提取问题,我编写了一个只初始化Haskell运行时的程序,运行一个空函数 100,000,000次。

在内联函数中,程序需要 0.003秒。调用用C 编写的空函数,以 0.18为单位。调用用Haskell编写的空函数以 15.5为。( 奇怪的是,如果在链接前单独编译空的Haskell文件,则需要花几秒钟的时间。子问题:为什么?

因此,在调用 100-fold 函数和调用Haskell函数之间似乎存在一个减速。这是什么原因,还有一种方法可以以减缓这种减速?

代码

编辑:我已经在 NoFib基准套件中发现了这个测试的一个版本。有一个漂亮的博客帖子,爱德华。Z 。杨在GHC计划程序的上下文中提到这个测试。我仍然在尝试grok这个博客帖子和非常不错的答案。我还不确定有什么方法可以更快地完成这项工作 !

若要编译"慢速"haskell版本,请运行

ghc -no-hs-main -O2 -optc-O3 test.c Test.hs -o test

若要编译"快速"c 版本,请运行

ghc -no-hs-main -O2 -optc-O3 test.c test2.c TestDummy.hs -o test

test.c:

#include"HsFFI.h"
extern void __stginit_Test(void);
extern void test();
int main(int argc, char *argv[]) {
 hs_init(&argc, &argv);
 hs_add_root(__stginit_Test);
 int i;
 for (i = 0; i <100000000; i++) {
 test();
 }
 hs_exit();
 return 0;
}

test2.c:

void test() {
}

Test.hs:

{-# LANGUAGE ForeignFunctionInterface #-}
module Test where
foreign export ccall test :: ()
test :: ()
test = ()

TestDummy.hs:

module Test where
时间:原作者:0个回答

126 4

收费费,博士:原因:RTS和呼叫。解决方案:不要从C 调用琐碎的Haskell函数。

这是什么原因。

免责声明:我从未从中调用 Haskell 。虽然我很熟悉C 和 Haskell,但我很少纠缠两者,除非我正在编写一个包装器。现在我已经经失去了我的信任,让我们开始这个测试的基准测试,拆卸和它的他。

使用gprof进行基准测试

检查消耗所有资源的一个简单方法是使用 gprof 。为了使编译器和链接器同时使用 -pg,我们将稍微更改编译行( 请注意:我已经将 test.c 重命名为 main.c 和 test2.c 以进行测试:

$ ghc -no-hs-main -O2 -optc-O3 -optc-pg -optl-pg -fforce-recomp 
 main.c Test.hs -o test
$./test
$ gprof./test

这将给我们以下( 平) 配置文件:

Flat profile:
Each sample counts as 0.01 seconds.
 % cumulative self self total 
 time seconds seconds calls Ts/call Ts/call name 
 16.85 2.15 2.15 scheduleWaitThread
 11.78 3.65 1.50 createStrictIOThread
 7.66 4.62 0.98 createThread
 6.68 5.47 0.85 allocate
 5.66 6.19 0.72 traverseWeakPtrList
 5.34 6.87 0.68 isAlive
 4.12 7.40 0.53 newBoundTask
 3.06 7.79 0.39 stg_ap_p_fast
 2.36 8.09 0.30 stg_ap_v_info
 1.96 8.34 0.25 stg_ap_0_fast
 1.85 8.57 0.24 rts_checkSchedStatus
 1.81 8.80 0.23 stg_PAP_apply
 1.73 9.02 0.22 rts_apply
 1.73 9.24 0.22 stg_enter_info
 1.65 9.45 0.21 stg_stop_thread_info
 1.61 9.66 0.21 test
 1.49 9.85 0.19 stg_returnToStackTop
 1.49 10.04 0.19 move_STACK
 1.49 10.23 0.19 stg_ap_v_fast
 1.41 10.41 0.18 rts_lock
 1.18 10.56 0.15 boundTaskExiting
 1.10 10.70 0.14 StgRun
 0.98 10.82 0.13 rts_evalIO
 0.94 10.94 0.12 stg_upd_frame_info
 0.79 11.04 0.10 blockedThrowTo
 0.67 11.13 0.09 StgReturn
 0.63 11.21 0.08 createIOThread
 0.63 11.29 0.08 stg_bh_upd_frame_info
 0.63 11.37 0.08 c5KU_info
 0.55 11.44 0.07 stg_stk_save_n
 0.51 11.50 0.07 threadPaused
 0.47 11.56 0.06 dirty_TSO
 0.47 11.62 0.06 ghczmprim_GHCziCString_unpackCStringzh_info
 0.47 11.68 0.06 stopHeapProfTimer
 0.39 11.73 0.05 stg_threadFinished
 0.39 11.78 0.05 allocGroup
 0.39 11.83 0.05 base_GHCziTopHandler_runNonIO1_info
 0.39 11.88 0.05 stg_catchzh
 0.35 11.93 0.05 freeMyTask
 0.35 11.97 0.05 rts_eval_
 0.31 12.01 0.04 awakenBlockedExceptionQueue
 0.31 12.05 0.04 stg_ap_2_upd_info
 0.27 12.09 0.04 s5q4_info
 0.24 12.12 0.03 markStableTables
 0.24 12.15 0.03 rts_getSchedStatus
 0.24 12.18 0.03 s5q3_info
 0.24 12.21 0.03 scavenge_stack
 0.24 12.24 0.03 stg_ap_7_upd_info
 0.24 12.27 0.03 stg_ap_n_fast
 0.24 12.30 0.03 stg_gc_noregs
 0.20 12.32 0.03 base_GHCziTopHandler_runIO1_info
 0.20 12.35 0.03 stat_exit
 0.16 12.37 0.02 GarbageCollect
 0.16 12.39 0.02 dirty_STACK
 0.16 12.41 0.02 freeGcThreads
 0.16 12.43 0.02 rts_mkString
 0.16 12.45 0.02 scavenge_capability_mut_lists
 0.16 12.47 0.02 startProfTimer
 0.16 12.49 0.02 stg_PAP_info
 0.16 12.51 0.02 stg_ap_stk_p
 0.16 12.53 0.02 stg_catch_info
 0.16 12.55 0.02 stg_killMyself
 0.16 12.57 0.02 stg_marked_upd_frame_info
 0.12 12.58 0.02 interruptAllCapabilities
 0.12 12.60 0.02 scheduleThreadOn
 0.12 12.61 0.02 waitForReturnCapability
 0.08 12.62 0.01 exitStorage
 0.08 12.63 0.01 freeWSDeque
 0.08 12.64 0.01 gcStableTables
 0.08 12.65 0.01 resetTerminalSettings
 0.08 12.66 0.01 resizeNurseriesEach
 0.08 12.67 0.01 scavenge_loop
 0.08 12.68 0.01 split_free_block
 0.08 12.69 0.01 startHeapProfTimer
 0.08 12.70 0.01 stg_MVAR_TSO_QUEUE_info
 0.08 12.71 0.01 stg_forceIO_info
 0.08 12.72 0.01 zero_static_object_list
 0.04 12.73 0.01 frame_dummy
 0.04 12.73 0.01 rts_evalLazyIO_
 0.00 12.73 0.00 1 0.00 0.00 stginit_export_Test_zdfstableZZC0ZZCmainZZCTestZZCtest

哇,这是有很多函数被称为。这与你的C 版本相比如何?

$ ghc -no-hs-main -O2 -optc-O3 -optc-pg -optl-pg -fforce-recomp 
 main.c TestDummy.hs test.c -o test_c
$./test_c
$ gprof./test_c
Flat profile:
Each sample counts as 0.01 seconds.
 % cumulative self self total 
 time seconds seconds calls Ts/call Ts/call name 
 75.00 0.05 0.05 test
 25.00 0.06 0.02 frame_dummy

那是一个 simpler simpler 。但为什么?

后面发生了什么?

也许你想知道为什么 test 甚至在前面的概要中出现。好吧,gprof本身增加了一些开销,可以通过 objdump 看到:

$ objdump -D./test_c | grep -A5"<test>:"
0000000000405630 <test>:
 405630: 55 push %rbp
 405631: 48 89 e5 mov %rsp,%rbp
 405634: e8 f7 d4 ff ff callq 402b30 <mcount@plt>
 405639: 5d pop %rbp
 40563a: c3 retq 

mcount的调用是由gcc添加的。因此,对于下一部分,你要删除 -pg 选项。让我们先在C 中检查反汇编的test 例程:

$ ghc -no-hs-main -O2 -optc-O3 -fforce-recomp 
 main.c TestDummy.hs test.c -o test_c
$ objdump -D./test_c | grep -A2"<test>:"
0000000000405510 <test>:
 405510: f3 c3 repz retq

实际上,repz retq 是一些优化魔法插件,但是在这种情况下,你可以把它看作是一个( 大部分) 而不是操作返回。

如何与Haskell版本进行比较?

$ ghc -no-hs-main -O2 -optc-O3 -fforce-recomp 
 main.c Test.hs -o test_hs 
$ objdump -D./Test.o | grep -A18"<test>:"
0000000000405520 <test>:
 405520: 48 83 ec 18 sub $0x18,%rsp
 405524: e8 f7 3a 05 00 callq 459020 <rts_lock>
 405529: ba 58 24 6b 00 mov $0x6b2458,%edx
 40552e: be 80 28 6b 00 mov $0x6b2880,%esi
 405533: 48 89 c7 mov %rax,%rdi
 405536: 48 89 04 24 mov %rax,(%rsp)
 40553a: e8 51 36 05 00 callq 458b90 <rts_apply>
 40553f: 48 8d 54 24 08 lea 0x8(%rsp),%rdx
 405544: 48 89 c6 mov %rax,%rsi
 405547: 48 89 e7 mov %rsp,%rdi
 40554a: e8 01 39 05 00 callq 458e50 <rts_evalIO>
 40554f: 48 8b 34 24 mov (%rsp),%rsi
 405553: bf 64 57 48 00 mov $0x485764,%edi
 405558: e8 23 3a 05 00 callq 458f80 <rts_checkSchedStatus>
 40555d: 48 8b 3c 24 mov (%rsp),%rdi
 405561: e8 0a 3b 05 00 callq 459070 <rts_unlock>
 405566: 48 83 c4 18 add $0x18,%rsp
 40556a: c3 retq 
 40556b: 0f 1f 44 00 00 nopl 0x0(%rax,%rax,1)
 405570: d8 ce fmul %st(6),%st

这看起来很不一样。实际上,RTS 函数看起来可疑。让我们看看它们:

所以我们找到了所有的功能。每次调用 150ns的开销都会提供所有的信用。

。还有什么方法可以缓解这种减缓?

你的test 基本上是个 no 。你正在调用 100000000次,总运行时为 15 s 。C 版本相比,这是 ~149ns 每次调用的开销。

解决方案非常简单:不要在你的C 环境中使用Haskell函数进行琐碎的任务。在正确的情况下使用正确的工具。毕竟,如果你需要添加两个保证小于 10的数字,则不使用GMP库。

除了这里聚合解决方案:不,上面显示的程序集是由rtc创建的,不可以能在一刻内创建一个没有vxml调用的变量。

原作者:
...