我正在尝试通过将现有的 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 似乎也执行了大量内存分配。这些是您应该关注的领域,以加快代码速度。