ECUG IV归来
昨天从杭州参加ECUG IV归来,日程安排的很满,虽然大家都很辛苦,但是到最后一个下午,还是很多人在会场,让人感觉气氛非常好。总的感觉是用erlang的人越来越多。 我这次演讲的topic: erlang系统的调优。 不少人感兴趣。附上现场照片一张。
昨天从杭州参加ECUG IV归来,日程安排的很满,虽然大家都很辛苦,但是到最后一个下午,还是很多人在会场,让人感觉气氛非常好。总的感觉是用erlang的人越来越多。 我这次演讲的topic: erlang系统的调优。 不少人感兴趣。附上现场照片一张。
erl的虚拟机有2种方式 plain版本的和smp版本的。 smp版本由于锁的开销相比要比plain版本的慢很多。而32位机器由于内存访问比64位的少,也会快出很多。所有我选择在32位的linux系统下调优这个httpd服务器。这个服务器就是实现个简单的功能,在browser下返回hello world。以下我们会先编译我们的优化版本的虚拟机,然后再分别测试R13B02的标准版本的和我们优化版的性能:
root@nd-desktop:/build_opt_plain# uname -a Linux nd-desktop 2.6.31-14-generic #3 SMP Sun Nov 1 23:03:10 CST 2009 i686 GNU/Linux #准备开发环境 root@nd-desktop:/# apt-get build-dep erlang #下载otp R13B02-1源码包 root@nd-desktop:/# wget http://www.erlang.org/download/otp_src_R13B02-1.tar.gz #解开patch包 root@nd-desktop:/# tar xzvf build_opt_plain.tar.gz #解开源码包 root@nd-desktop:/# tar xzf otp_src_R13B02-1.tar.gz #打补丁 root@nd-desktop:/# cd otp_src_R13B02-1 root@nd-desktop:/otp_src_R13B02-1# patch -p1 <../build_opt_plain/otp_src_R13B02-1_patch_by_yufeng 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# ../build_opt_plain/build.plain 。。。
如果编译都没有任何错误的话, 就大功告成了。
好 现在我们开始性能比较:
#先加大文件句柄数
root@nd-desktop:/otp_src_R13B02-1# cd ../build_opt_plain root@nd-desktop:/build_opt_plain# ulimit -n 99999 #标准发布版本 root@nd-desktop:/build_opt_plain# erlc ehttpd.erl root@nd-desktop:/build_opt_plain# taskset -c 1 erl +K true +h 99999 +P 99999 -smp enable +S 2:1 -s ehttpd 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) 1>
#在另外的一台机器上发动ab测试
[root@localhost src]# ab -c 60 -n 100000 http://192.168.235.147:8888/ This is ApacheBench, Version 2.0.40-dev <$Revision: 1.146 $> apache-2.0 Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Copyright 2006 The Apache Software Foundation, http://www.apache.org/ Benchmarking 192.168.235.147 (be patient) Completed 10000 requests Completed 20000 requests Completed 30000 requests Completed 40000 requests Completed 50000 requests Completed 60000 requests Completed 70000 requests Completed 80000 requests Completed 90000 requests Finished 100000 requests Server Software: Server Hostname: 192.168.235.147 Server Port: 8888 Document Path: / Document Length: 12 bytes Concurrency Level: 60 Time taken for tests: 8.925945 seconds Complete requests: 100000 Failed requests: 0 Write errors: 0 Total transferred: 5100051 bytes HTML transferred: 1200012 bytes Requests per second: 11203.29 [#/sec] (mean) 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 Connection Times (ms) min mean[+/-sd] median max Connect: 0 1 65.7 0 3001 Processing: 0 3 1.5 4 7 Waiting: 0 2 1.8 4 6 Total: 0 4 65.8 4 3007 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) 50% 4 66% 4 75% 4 80% 4 90% 5 95% 5 98% 5 99% 5 100% 3007 (longest request)
标准smp版本1个CPU的结果是: 11203.29 [#/sec] (mean)
#启用hipe的标准版本
root@nd-desktop:/build_opt_plain# erlc +native +"{hipe, [o3]}" ehttpd.erl root@nd-desktop:/build_opt_plain# taskset -c 1 erl +K true +h 99999 +P 99999 -smp enable +S 2:1 -s ehttpd 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) 1>
标准smp hipe版本1个CPU结果是: 12390.32 [#/sec] (mean)
#我们的优化版本
root@nd-desktop:/build_opt_plain# ../otp_src_R13B02-1/bin/erlc ehttpd.erl root@nd-desktop:/build_opt_plain# taskset -c 1 ../otp_src_R13B02-1/bin/erl +K true +h 99999 +P 99999 -s ehttpd 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) 1>
优化版本单个cpu: 19662.37 [#/sec] (mean)
#启用hipe的优化版本
root@nd-desktop:/build_opt_plain# ../otp_src_R13B02-1/bin/erlc +native +"{hipe, [o3]}" ehttpd.erl root@nd-desktop:/build_opt_plain# taskset -c 1 ../otp_src_R13B02-1/bin/erl +K true +h 99999 +P 99999 -s ehttpd 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) 1>
优化版本启用hipe单个cpu:20090.83 [#/sec] (mean)
附上我们的最小的高性能的http echo 服务器:
root@nd-desktop:/build_opt_plain# cat ehttpd.erl
-module(ehttpd). -compile(export_all). start() -> start(8888). start(Port) -> N = erlang:system_info(schedulers), listen(Port, N), io:format("ehttpd ready with ~b schedulers on port ~b~n", [N, Port]), register(?MODULE, self()), receive Any -> io:format("~p~n", [Any]) end. %% to stop: ehttpd!stop. listen(Port, N) -> Opts = [{active, false}, binary, {backlog, 256}, {packet, http_bin}, {raw,6,9,<<1:32/native>>}, %defer accept %%{delay_send,true}, %%{nodelay,true}, {reuseaddr, true}], {ok, S} = gen_tcp:listen(Port, Opts), Spawn = fun(I) -> register(list_to_atom("acceptor_" ++ integer_to_list(I)), spawn_opt(?MODULE, accept, [S, I], [link, {scheduler, I}])) end, lists:foreach(Spawn, lists:seq(1, N)). accept(S, I) -> case gen_tcp:accept(S) of {ok, Socket} -> spawn_opt(?MODULE, loop, [Socket], [{scheduler, I}]); Error -> erlang:error(Error) end, accept(S, I). loop(S) -> case gen_tcp:recv(S, 0) of {ok, http_eoh} -> Response = <<"HTTP/1.1 200 OK\r\nContent-Length: 12\r\n\r\nhello world!">>, gen_tcp:send(S, Response), gen_tcp:close(S), ok; {ok, _Data} -> loop(S); Error -> Error end.
这个服务器是最小的,单是在多处理器和单cpu上都有非常好的性能。
root@nd-desktop:/build_opt_plain# cat /proc/cpuinfo model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz
注:这个http服务器基本上是在c的程序跑,erlang的代码执行的很少, 所以hipe的提升效果不是很明显。对于复杂的业务,应该是有很大的帮助的。
附件里是用到的脚本和补丁。
我们可以得出结论:
hipe启用要比不启用快。
优化版本的和标准版本的 20090:11203, 性能提高了将近80% 还是非常可观的。
我们在探索linux内核的时候,经常需要调整下变量的值,看它对系统的影响。如果这个值没有透过/proc来修改的话,那只能编译内核。这个步骤是非常繁琐的。现在我们有systemtap这个利器来帮忙了。
演示如下:
我们通过修改过
extern int sysctl_tcp_fin_timeout;的值来达到目的。是因为这个值是proc导出的 我们好验证是否成功。
root@localhost ~]# cat /proc/sys/net/ipv4/tcp_fin_timeout 15000 [root@localhost ~]# cat test.stp probe begin { printf("ready go\n"); } probe kernel.function("do_tcp_setsockopt") { $sysctl_tcp_fin_timeout = $1 printf("sysctl_tcp_fin_timeout = %d\n", $sysctl_tcp_fin_timeout); exit() } [root@localhost ~]# stap -g test.stp 18000 ready go
这个时候 stap在运行, 只是还没有触发do_tcp_setsockopt.
现在我们来触发
[root@localhost ~]# erl Erlang R13B02 (erts-5.7.3) [source] [64-bit] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false] Eshell V5.7.3 (abort with ^G) 1> {ok, LSock} = gen_tcp:listen(0, []). {ok,#Port<0.437>} 2> 2> inet:setopts(LSock, [{nodelay,true}]). ok 3>
Ok,这时候回头可以看到stap打出来以下:
sysctl_tcp_fin_timeout = 18000
我们来验证下:
root@localhost ~]# cat /proc/sys/net/ipv4/tcp_fin_timeout 18000
OK,成功。
Tips:
1. stap对全局变量的写需要-g guru模式。
2. 全局变量必须在一个单元内的函数里面才可以修改, 而且必须是在内核上下文。
PS. 这样写的话会更好,因为这个变量是单元可见的,这个模块里面的任何函数被触发都可以看到这个变量. 因为这是tcp的核心模块随时都会被出发的,免除了以上的麻烦!
$ cat test.stp probe begin { printf("ready go\n"); } probe kernel.function("*@net/ipv4/tcp.c") //probe kernel.function("do_tcp_setsockopt") { $sysctl_tcp_fin_timeout = $1 printf("sysctl_tcp_fin_timeout = %d\n", $sysctl_tcp_fin_timeout); exit() }
gen_server在erlang otp编程中的地位是无可撼动的,几乎都是gen_server或者gen_fsm的模型。那么程序运行起来的时候 我们如何查看gen_server的内部状态呢。有2种方法:
1. 自己写个类似于info这样的函数,来获取状态。
2. 利用系统现有的架构。sasl应用带了一个si的东西 全名是status inspector, 这个东西就是设计来帮用户解决这个问题的。
实验开始:
root@nd-desktop:~# cat abc.erl
-module(abc). -behaviour(gen_server). -export([start_link/0]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([format_status/2]). -export([test/0]). -record(state, {a, b}). -define(SERVER, ?MODULE). start_link() -> gen_server:start_link({local, ?SERVER}, ?MODULE, [], []). test()-> gen_server:call(?SERVER, {test, "param1"}). init([]) -> {ok, #state{a=hello, b=world}}. handle_call({test, _} = Request, _From, State) -> io:format("got msg ~p~n", [Request]), {reply, ok, State}; handle_call(_Request, _From, State) -> Reply = ok, {reply, Reply, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. format_status(_Opt, [_PDict, #state{a=A, b = B }]) -> [{data, [{"a===", A}, {"b===", B}]}].
root@nd-desktop:~# erl -boot start_sasl Erlang R13B03 (erts-5.7.4) [source][/source][/source] [smp:2:2] [rq:2] [async-threads:0] [hipe] [kernel-poll:false] =PROGRESS REPORT==== 29-Oct-2009::16:07:24 === supervisor: {local,sasl_safe_sup} started: [{pid,<0.35.0>}, {name,alarm_handler}, {mfa,{alarm_handler,start_link,[]}}, {restart_type,permanent}, {shutdown,2000}, {child_type,worker}] =PROGRESS REPORT==== 29-Oct-2009::16:07:24 === supervisor: {local,sasl_safe_sup} started: [{pid,<0.36.0>}, {name,overload}, {mfa,{overload,start_link,[]}}, {restart_type,permanent}, {shutdown,2000}, {child_type,worker}] =PROGRESS REPORT==== 29-Oct-2009::16:07:24 === supervisor: {local,sasl_sup} started: [{pid,<0.34.0>}, {name,sasl_safe_sup}, {mfa, {supervisor,start_link, [{local,sasl_safe_sup},sasl,safe]}}, {restart_type,permanent}, {shutdown,infinity}, {child_type,supervisor}] =PROGRESS REPORT==== 29-Oct-2009::16:07:24 === supervisor: {local,sasl_sup} started: [{pid,<0.37.0>}, {name,release_handler}, {mfa,{release_handler,start_link,[]}}, {restart_type,permanent}, {shutdown,2000}, {child_type,worker}] =PROGRESS REPORT==== 29-Oct-2009::16:07:24 === application: sasl started_at: nonode@nohost Eshell V5.7.4 (abort with ^G) 1> si:start(). %必须手动启动 =PROGRESS REPORT==== 29-Oct-2009::16:07:31 === supervisor: {local,sasl_sup} started: [{pid,<0.43.0>}, {name,si_server}, {mfa,{si_sasl_supp,start_link,[[]]}}, {restart_type,temporary}, {shutdown,brutal_kill}, {child_type,worker}] {ok,<0.43.0>} 2> si:help(). Status Inspection tool - usage ============================== For all these functions, Opt is an optional argument which can be 'normal' or 'all'; default is 'normal'. If 'all', all information will be printed. A Pid can be: "<A.B.C>", {A, B, C}, B, a registered_name or an abbrev. ANY PROCESS si:pi([Opt,] Pid) - Formatted information about any process that SI recognises. si:pi([Opt,] A,B,C) - Same as si:pi({A, B, C}). si:ppi(Pid) - Pretty formating of process_info. Works for any process. MISC si:abbrevs() - Lists valid abbreviations. si:start_log(Filename) - Logging to file. si:stop_log() si:start() - Starts Status Inspection (the si_server). si:start([{start_log, FileName}]) si:stop() - Shut down SI. ok 3> abc:start_link(). {ok,<0.46.0>} 4> abc:test(). got msg {test,"param1"} ok 5> sys:log(abc,true). %打开gen_server的消息log功能 ok 6> abc:test(). %这个请求消息被记录 got msg {test,"param1"} ok 7> si:pi(abc). %好戏开始 Status for generic server abc =============================================================================== Pid <0.46.0> Status running Parent <0.41.0> Logged events %这个是log到的消息 {10, [{{out,ok,<0.41.0>,{state,hello,world}}, abc, {gen_server,print_event}}, {{in,{'$gen_call',{<0.41.0>,#Ref<0.0.0.85>},{test,"param1"}}}, abc, {gen_server,print_event}}]} %这个是format_status的结果 如果没有format_status那么导出是 {a=hello, b=world} a=== hello b=== world ok 8> si:ppi(abc). Pretty Process Info ------------------- [{registered_name,abc}, {current_function,{gen_server,loop,6}}, {initial_call,{proc_lib,init_p,5}}, {status,waiting}, {message_queue_len,0}, {messages,[]}, {links,[<0.41.0>]}, {dictionary,[{'$ancestors',[<0.41.0>]},{'$initial_call',{abc,init,1}}]}, {trap_exit,false}, {error_handler,error_handler}, {priority,normal}, {group_leader,<0.25.0>}, {total_heap_size,233}, {heap_size,233}, {stack_size,9}, {reductions,117}, {garbage_collection,[{fullsweep_after,65535},{minor_gcs,0}]}, {suspending,[]}] ok 9> sys:get_status(abc). {status,<0.46.0>, {module,gen_server}, [[{'$ancestors',[<0.41.0>]},{'$initial_call',{abc,init,1}}], running,<0.41.0>, [{log,{10, [{{out,ok,<0.41.0>,{state,hello,world}}, abc, {gen_server,print_event}}, {{in,{'$gen_call',{<0.41.0>,#Ref<0.0.0.85>}, {test,"param1"}}}, abc, {gen_server,print_event}}]}}], [abc,{state,hello,world},abc,infinity]]}
结论:
这个是文档未公开的功能。上面演示了如何sys打开log, 如何察看gen_server的状态
erlang内置的port非常强大,是erlang通往外面世界的通道,所以port和erlang程序的通讯的数据格式影响了通讯的效率,和稳定性。我们在选择格式的时候, 会优先考虑到erlang的特性和port程序编写语言的特点,选出一种2者都容易处理的格式。
通讯通常有2种,基于行的文本和2进制格式。
行通讯最容易,因为是文本,调试起来就很方便。 形如这样的格式:
request args\n erlang编码这种格式就是加个\n, 解码可以用driver的{line, xxx}选项。而外部程序比如说c 解码可以用fgets, 编码也是加个\n. 缺点是: 表达上比较受限 不好表示结构数据。
2进制格式,形如这样的格式:
4个字节包长度 包体 . erlang和解码都可以利用driver的{packet,4} 自动把包体接处理。而外部程序比如说c 处理这样的也非常轻松。
包体有以下几种留下的格式:
1. 自定义格式。 比如: 4个字节cmd + 2个字节字符长度 + 字符
只要erlang和外部程序都能同样这种格式就好。erlang有很强大的binary处理这种事情很轻松, c同样也是。 缺点是太繁琐,格式变化的时候 容易漏掉东西。
2. erlang的外部协议格式。 erlang编码可以用term_to_binary, 解码用binray_to_term. c程序用ei库俩编解码。 这样erlang端的工作量就很小, c端的麻烦些。
3. json这样的格式。 erlang和c都用现成的json库来编码解码。
4. asn.1格式。erlang有强大的内置的asn编码解码。c端也有asn1c这样的编解码器。 写个asn1规格 2边各自生成代码,这样最轻松 而且能表达数据很轻松,推荐使用。
如题, 方便大家阅读,这个电子书记录了俺的研究过程,希望对大家有帮助。
erlang深度分析文章下载
R13B新添加的leex相当于c的lex, 在做文法分析非常方便,但是效率如何呢? leex的example里面带了个erlang_scan和erlang标准的发布版的erl_scan兼容,所以我们来对比测试下效率。
注意用R13B03,因为R13B02的erlc漏掉了编译xrl格式。
以下是实验:
root@nd-desktop:~# git clone git://github.com/rvirding/leex.git root@nd-desktop:~# cd leex/examples/ root@nd-desktop:~/leex/examples# cat test_scan.erl
-module(test_scan). -export([start/1]). start([A])-> {ok, F} = file:open(atom_to_list(?MODULE)++".erl", [read]), {ok, S} = file:read(F, 9999999), file:close(F), N = list_to_integer(A), test(N, fun erlang_scan:string/1, S, "erlang_scan"), test(N, fun erl_scan:string/1, S, "erl_scan"), ok. test(N, F, S, Ts)-> Start = erlang:now(), dotimes(N, fun (_) -> F(S) end), io:format("~s run ~w ms~n", [Ts,round(timer:now_diff(now(), Start) /1000)]). dotimes(0, _) -> done; dotimes(N, F) -> F(N), dotimes(N - 1, F).
root@nd-desktop:~/leex/examples# erlc erlang_scan.xrl root@nd-desktop:~/leex/examples# ls *.erl erlang_scan.erl test_scan.erl root@nd-desktop:~/leex/examples# erlc *.erl root@nd-desktop:~/leex/examples# erl -noshell -run test_scan start 10000 -s erlang halt erlang_scan run 2208 ms erl_scan run 1181 ms
%% 这个版本稍微慢点
root@nd-desktop:~/leex/examples# erlc +native *.erl root@nd-desktop:~/leex/examples# erl -noshell -run test_scan start 10000 -s erlang halt erlang_scan run 1292 ms erl_scan run 1238 ms
结论是: leex产生的代码和手写的效率几乎差不多。
Recent Comments