即将发布的R17A版本引入很重要的一个针对性能提升的特性:”Support the LLVM backend in HiPE”,具体改变参见这里. 我们知道Erlang是一门领域语言,第一天就是为电信工业高可用,集群和热更新环境而设计的,语言的性能一开始不是重点。直到R12版本才加入SMP多处理器,充分适应多核化的硬件发展趋势,从此向着高性能大步迈进。
Erlang的虚拟机是register based的,性能上和python类似,和c语言大概有7倍的差距。虽然大部分的集群和网络服务器,性能瓶颈在IO上面,而且这块erts(erlang运行期系统)做的非常的强大,但是一旦涉及到大量的计算,就有点麻烦了,因为它缺乏类似java jit那样强大的支持,让语言足够的快。解决方案是自己写nif、driver或者bif,但是会破坏稳定性。
它很早有自己的hipe, 主要是Uppsala University大学的Kostis Sagonas带领学生做的, 97年开始做的,性能的提升虽然不少,但是在架构上有些缺点,而且和otp团队是二个不同的团队,在稳定性上无法达到产品质量。为了进一步解决这个问题,他带着Christos Stavrakakis和Yiannis Tsiouris,重新实现了基于LLVM后端的Hipe,也就是erllvm,官方网站在这里.
官方描述如下:
ErLLVM is a project aiming at providing multiple back ends for the High Performance Erlang (HiPE) with the use of the LLVM infastructure.
这次R17发布就是把ErLLVM融入到erlang主干版本去。那么ErLLVM的技术改进点在哪里?看下面的图就明白了。

最关键的一点就是之前的hipe自己从RTL生成硬件代码,而ErlLvm把这个事情交给了llvm专业去生成,它只做RTL->llvm层的薄薄的翻译,这样稳定性的问题就offload交给了llvm,而llvm的稳定性是经过社区规模考验的。

这样就很好的解决了稳定性和性能的问题。 Read more…
erl的虚拟机有2种方式 plain版本的和smp版本的。 smp版本由于锁的开销相比要比plain版本的慢很多。而32位机器由于内存访问比64位的少,也会快出很多。所有我选择在32位的linux系统下调优这个httpd服务器。这个服务器就是实现个简单的功能,在browser下返回hello world。以下我们会先编译我们的优化版本的虚拟机,然后再分别测试R13B02的标准版本的和我们优化版的性能:
root@nd-desktop:/build_opt_plain |
Linux nd-desktop 2.6.31-14-generic |
root@nd-desktop:/otp_src_R13B02-1 |
patching file erts/emulator/beam/erl_binary.h |
patching file erts/emulator/beam/erl_process.c |
patching file erts/emulator/beam/sys.h |
patching file erts/emulator/drivers/common/inet_drv.c |
patching file erts/preloaded/src/Makefile |
patching file erts/preloaded/src/prim_inet.erl |
patching file lib/asn1/src/Makefile |
patching file lib/hipe/Makefile |
patching file lib/parsetools/src/Makefile |
root@nd-desktop:/otp_src_R13B02-1 |
如果编译都没有任何错误的话, 就大功告成了。
好 现在我们开始性能比较:
#先加大文件句柄数
root@nd-desktop:/otp_src_R13B02-1 |
root@nd-desktop:/build_opt_plain |
root@nd-desktop:/build_opt_plain |
root@nd-desktop:/build_opt_plain |
Erlang R13B03 (erts-5.7.4) [ source ][/ source ] [smp:2:1] [rq:2] [async-threads:0] [hipe] [kernel-poll: true ] |
ehttpd ready with 2 schedulers on port 8888 |
Eshell V5.7.4 (abort with ^G) |
#在另外的一台机器上发动ab测试
This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0 |
Benchmarking 192.168.235.147 (be patient) |
Server Hostname: 192.168.235.147 |
Document Length: 12 bytes |
Time taken for tests: 8.925945 seconds |
Complete requests: 100000 |
Total transferred: 5100051 bytes |
HTML transferred: 1200012 bytes |
Requests per second: 11203.29 [ |
Time per request: 5.356 [ms] (mean) |
Time per request: 0.089 [ms] (mean, across all concurrent requests) |
Transfer rate: 557.92 [Kbytes/sec] received |
min mean[+/-sd] median max |
WARNING: The median and mean for the waiting time are not within a normal deviation |
These results are probably not that reliable. |
Percentage of the requests served within a certain time (ms) |
100% 3007 (longest request) |
标准smp版本1个CPU的结果是: 11203.29 [#/sec] (mean)
#启用hipe的标准版本
root@nd-desktop:/build_opt_plain |
root@nd-desktop:/build_opt_plain |
Erlang R13B03 (erts-5.7.4) [ source ][/ source ] [smp:2:1] [rq:2] [async-threads:0] [hipe] [kernel-poll: true ] |
ehttpd ready with 2 schedulers on port 8888 |
Eshell V5.7.4 (abort with ^G) |
标准smp hipe版本1个CPU结果是: 12390.32 [#/sec] (mean)
#我们的优化版本
root@nd-desktop:/build_opt_plain |
root@nd-desktop:/build_opt_plain |
Erlang R13B02 (erts-5.7.3) [ source ][/ source ] [rq:1] [hipe] [kernel-poll: true ] |
ehttpd ready with 1 schedulers on port 8888 |
Eshell V5.7.3 (abort with ^G) |
优化版本单个cpu: 19662.37 [#/sec] (mean)
#启用hipe的优化版本
root@nd-desktop:/build_opt_plain |
root@nd-desktop:/build_opt_plain |
Erlang R13B02 (erts-5.7.3) [ source ][/ source ] [rq:1] [hipe] [kernel-poll: true ] |
ehttpd ready with 1 schedulers on port 8888 |
Eshell V5.7.3 (abort with ^G) |
优化版本启用hipe单个cpu:20090.83 [#/sec] (mean)
附上我们的最小的高性能的http echo 服务器:
root@nd-desktop:/build_opt_plain |
N = erlang:system_info (schedulers), |
io:format ( "ehttpd ready with ~b schedulers on port ~b~n" , [N, Port ]), |
register( ?MODULE , self()), |
receive Any -> io:format ( "~p~n" , [ Any ]) end . |
{raw,6,9,<< 1:32 /native>>}, |
{ok, S} = gen_tcp:listen ( Port , Opts ), |
register(list_to_atom( "acceptor_" ++ integer_to_list(I)), |
spawn_opt( ?MODULE , accept, [S, I], [link, {scheduler, I}])) |
lists:foreach ( Spawn , lists:seq (1, N)). |
case gen_tcp:accept (S) of |
{ok, Socket } -> spawn_opt( ?MODULE , loop, [ Socket ], [{scheduler, I}]); |
Error -> erlang:error ( Error ) |
case gen_tcp:recv (S, 0) of |
Response = << "HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nhello world!" >>, |
gen_tcp:send (S, Response ), |
这个服务器是最小的,单是在多处理器和单cpu上都有非常好的性能。
root@nd-desktop:/build_opt_plain |
model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz |
注:这个http服务器基本上是在c的程序跑,erlang的代码执行的很少, 所以hipe的提升效果不是很明显。对于复杂的业务,应该是有很大的帮助的。
附件里是用到的脚本和补丁。
我们可以得出结论:
hipe启用要比不启用快。
优化版本的和标准版本的 20090:11203, 性能提高了将近80% 还是非常可观的。
点击下载附件
前篇文章http://mryufeng.javaeye.com/blog/428845 讲述了如何启用erlang hipe支持,但是用户程序大量依赖的标准库如stdlib, kernel等默认都不是native模式的, 所以我们的程序虽然启用了hipe,但是只是部分启用了。用oprofile等工具可以看到我们的程序还是在process_main(虚拟机的代码解释 在这里)里面打转。 我们来个极致的,通通hipe化。
有2个方案可以解决:
1. 在编译otp_src的时候 export ERL_COMPILE_FLAGS=’+native +”{hipe, [o3]}”‘ 但是这个方案有个问题就是
native方式是和beam的模式有关的 如beam和beam.smp它的代码是不同的,但是所有的beam又公用一套库,这样只能舍弃一个了。所以这个方案就比较麻烦。
Erlang R13B01 (erts-5.7.2) [ source ][/ source ] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll: false ] |
Eshell V5.7.2 (abort with ^G) |
<HiPE ( v 3.7.2)> Warning: not loading native code for module fib: it was compiled for an incompatible runtime system; please regenerate native code for this runtime system |
Erlang R13B01 (erts-5.7.2) [ source ][/ source ] [64-bit] [rq:1] [async-threads:0] [hipe] [kernel-poll: false ] |
Eshell V5.7.2 (abort with ^G) |
这个也可以通过修改 alias erl=erl -smp disable 以便欺骗编译器生成单cpu模式的beam
去绕过去
2. 动态编译, 等系统运行起来以后,动态把相关的模块编译一遍,这个思路看起来最简单。
我做了个原型 证明这样是可行的。。。
[ turn(M, P)|| {M, P} <- code:all_loaded (), P=/=preloaded]. |
P1 = binary_to_list(iolist_to_binary( re:replace ( filename:join ( filename:dirname (P), filename:basename (P, ".beam" )), "ebin" , "src" ))), |
COpts = get_compile_options(L), |
COpts1 = lists:foldr ( fun ({K, V}, Acc ) when is_list(V) and is_integer(hd(V)) ->[{K, tr(V)}] ++ Acc ; ( Skip , Acc ) -> Acc ++ [ Skip ] end , [], COpts ), |
c:c ( P1 , COpts1 ++ [native, "{hipe, [o3]}" ]). |
binary_to_list(iolist_to_binary( re:replace (P, "/net/isildur/ldisk/daily_build/otp_prebuild_r13b01.2009-06-07_20/" , "/home/yufeng/" ))). |
get_compile_options(L) -> |
case get_compile_info(L, options) of |
get_compile_info(L, Tag ) -> |
case lists:keysearch (compile, 1, L) of |
{value, {compile, I}} -> |
case lists:keysearch ( Tag , 1, I) of |
{value, { Tag , Val }} -> {ok, Val }; |
Erlang R13B01 (erts-5.7.2) [ source ][/ source ][/ source ] [64-bit] [smp:8:8] [rq:8] [async-threads:0] [hipe] [kernel-poll: false ] |
Eshell V5.7.2 (abort with ^G) |
1& gt ; mnesia:start(). %启动我们的应用程序 |
Module dict compiled: Date: August 23 2009, Time: 17.20 |
Compiler options: [{cwd, "/home/yufeng/otp_src_R13B01/lib/stdlib/src" }, |
{outdir, "/home/yufeng/otp_src_R13B01/lib/stdlib/src/../ebin" }, |
{i, "/home/yufeng/otp_src_R13B01/lib/stdlib/src/../include" }, |
{i, "/home/yufeng/otp_src_R13B01/lib/stdlib/src/../../kernel/include" }, |
debug_info,<span style= "color: red;" >native, "{hipe, [o3]}" </span>] |
Object file : /home/yufeng/otp_src_R13B01/lib/stdlib/src/../ebin/dict.beam |
。。。
看到了是用native模式编译的哦。。。
不过编译过程中有几个模块是有点问题, 得改进下。
Recent Comments