C语言 Unicode 整理 NIF 运行速度比 Pure Erlang 实现慢



我正在尝试通过将现有的 unicode 排序规则库(用 Erlang 编写(重写为 NIF 实现来优化它。主要原因是因为排序规则是 CPU 密集型操作。

实现链接:https://github.com/abhi-bit/merger

通过基于 Pure Erlang 的优先级队列对 1M 行进行 Unicode 排序:

erlc *.erl; ERL_LIBS="..:$ERL_LIBS" erl -noshell -s perf_couch_skew main 1000000 -s init stop
Queue size: 1000000
12321.649 ms

通过基于 NIF 的二项式堆对 1M 行进行 Unicode 排序:

erlc *.erl; ERL_LIBS="..:$ERL_LIBS" erl -noshell -s perf_merger main 1000000 -s init stop
Queue size: 1000000
15871.965 ms

这是不寻常的,我预计它可能会快 ~10 倍。

我打开了eprof/fprof但是当涉及到NIF模块时,它们没有多大用处,以下是eprof所说的突出功能

FUNCTION                                         CALLS      %     TIME  [uS / CALLS]
--------                                         -----    ---     ----  [----------]
merger:new/0                                         1   0.00        0  [      0.00]
merger:new/2                                         1   0.00        0  [      0.00]
merger:size/1                                   100002   0.31    19928  [      0.20]
merger:in/3                                     100000   3.29   210620  [      2.11]
erlang:put/2                                   2000000   6.63   424292  [      0.21]
merger:out/1                                    100000  14.35   918834  [      9.19]

我敢肯定,NIF实现可以做得更快,因为我有一个基于二进制堆的纯C实现,使用动态数组,而且要快得多。

$ make
gcc -I/usr/local/Cellar/icu4c/55.1/include  -L/usr/local/Cellar/icu4c/55.1/lib  min_heap.c collate_json.c kway_merge.c kway_merge_test.c -o output -licui18n -licuuc -licudata
./output
Merging 1 arrays each of size 1000000
mergeKArrays took 84.626ms

我在这里的具体问题:

  • 由于 NIF 模块中的 Erlang <-> C 通信,预计会有多慢?在这种情况下,纯 C 和 NIF 实现之间的速度可能降低 30 倍或更多
  • 哪些工具可用于调试与NIF相关的减速(如本例(?我尝试使用 perf top 来查看函数调用,顶部的(显示一些十六进制地址(来自"beam.smp"。
  • 在优化 NIF 时,我应该考虑哪些可能领域?例如:我听说应该将 Erlang 到 C 之间的数据传输保持在最低限度,反之亦然,还有更多这样的领域需要考虑吗?

调用 NIF 的开销很小。当 Erlang 运行时加载加载 NIF 的模块时,它会使用模拟器指令修补模块的光束代码以调用 NIF。指令本身在调用实现 NIF 的 C 函数之前仅执行少量设置。这不是导致性能问题的领域。

分析

NIF 与分析 C/C++ 代码大致相同。从您的Makefile来看,您似乎正在OS X上开发此代码。在该平台上,假设您安装了 XCode,则可以将 Instruments 应用程序与 CPU Samples仪器一起使用,以查看代码大部分时间花费在何处。在 Linux 上,您可以将 valgrind 的 callgrind 工具与支持 valgrind 的 Erlang 模拟器一起使用来测量您的代码。

例如,如果您在代码上使用这些工具,您会发现perf_merger:main/1大部分时间都花在merger_nif_heap_get上,这反过来又在CollateJSON上花费了相当多的时间。该函数似乎调用了convertUTF8toUChar,并且createStringFromJSON相当多。您的 NIF 似乎也执行了大量内存分配。这些是您应该关注的领域,以加快代码速度。

相关内容

  • 没有找到相关文章

最新更新