在我的項目里面, 很多運算logic是由外部的程序來計算的 那么消息先透過pipe發到外部程序,外部程序讀到消息, 處理消息, 寫消息, erlang程序讀到消息, 這條鏈路很長,而且涉及到pipe讀寫,上下文切換,這個開銷是很大的.但是具體是多少呢?
我設計了個這樣的ring. 每個ring有N個環組成, 每個環開個port. 當ring收到個數字的時候 如果數字不為0, 那么把這個數字發到外部成程序,這個外部程序echo回來數字,收到echo回來的消息后,把數字減1,繼續傳遞.當數字減少到0的時候 銷毀整個ring.
/* 注意這個數字非常重要 它影響了Erlang程序3個地方 1. epoll的句柄集大小 2. MAX_PORT 以及port的表格大小 3. open_port的時候 子進程關閉的文件句柄大小*/
root@nd-desktop:~/test#ulimit -n 1024
root@nd-desktop:~/test# cat pipe_ring.erl
-module(pipe_ring).
-export([start/1]).
-export([make_relay/1, run/3]).
make_relay(Next)->
Port = open_port({spawn, "/bin/cat"}, [in, out, {line, 128}]),
relay_loop(Next, Port).
relay_loop(Next, Port) ->
receive
{Port, {data, {eol, Line}}} ->
Next ! (list_to_integer(Line) - 1),
relay_loop(Next, Port);
K when is_integer(K) andalso K > 0 ->
port_command(Port, integer_to_list(K) ++ "\n"),
relay_loop(Next, Port);
K when is_integer(K) andalso K =:=0 ->
port_close(Port),
Next ! K
end.
build_ring(K, Current, N, F) when N > 1 ->
build_ring(K, spawn(?MODULE, make_relay, [Current]), N - 1, F);
build_ring(_, Current, _, F) ->
F(),
make_relay(Current).
run(N, K, Par) ->
Parent = self(),
Cs = [spawn(fun ()-> Parent!run1(N, K, P) end) || P<-lists:seq(1, Par)],
[receive _-> ok end || _<-Cs].
run1(N, K, P)->
T1 = now(),
build_ring(K, self(), N, fun ()-> io:format("(ring~w setup time: ~ws)~n", [P, timer:now_diff(now(), T1) /1000]), self() ! K end).
start(Args) ->
Args1 = [N, K, Par] = [list_to_integer(atom_to_list(X)) || X<-Args],
{Time, _} = timer:tc(?MODULE, run, Args1),
io:format("(total run (N:~w K:~w Par:~w) ~wms ~w/s)~n", [N, K, Par, round(Time/1000), round(K*Par*1000000/Time)]),
halt(0).
root@nd-desktop:~/test# erl +Bd -noshell +K true -smp disable -s pipe_ring start 10 100000 8
(ring1 setup time: 0.021s)
(ring2 setup time: 0.02s)
(ring3 setup time: 0.019s)
(ring4 setup time: 0.03s)
(ring5 setup time: 0.018s)
(ring6 setup time: 0.031s)
(ring7 setup time: 0.027s)
(ring8 setup time: 0.039s)
(total run (N:10 K:100000 Par:8) 23158ms 34546/s)
參數的意義:
N K Par
N:ring有幾個環 每個環開一個port
K:每個環傳遞多少消息
Par: 多少ring一起跑
總的消息數是 K * Par.
我們可以看到 每秒可以處理大概 3.4W個消息 我有2個核心. 也就是說每個消息的開銷大概是 30us. 每個port的創建時間不算多, 1ms一個.
root@nd-desktop:~/test# dstat
----total-cpu-usage---- -dsk/total- -net/total- ---paging-- ---system--
usr sys idl wai hiq siq| read writ| recv send| in out | int csw
33 18 50 0 0 1| 0 0 | 438B 2172B| 0 0 |5329 33k
42 11 48 0 0 0| 0 0 | 212B 404B| 0 0 |5729 58k
41 11 49 0 0 0| 0 0 | 244B 1822B| 0 0 |5540 59k
40 11 49 0 0 0| 0 0 | 304B 404B| 0 0 |4970 60k
注意上面的csw 達到6W每秒.
root@nd-desktop:~/test# pstree
├─sshd─┬─sshd─┬─bash───pstree
│ │ └─bash───man───pager
│ ├─sshd───bash─┬─beam─┬─80*[cat]
│ │ │ └─{beam}
│ │ └─emacs
│ ├─sshd───bash───emacs
│ └─sshd───bash───nmon
我們運行了80個echo程序(/bin/cat)
讀者有興趣的話可以用systemtap 詳細了解 pipe的讀寫花費,以及context_switch情況, 具體腳本可以向我索要.
root@nd-desktop:~# cat /proc/cpuinfo
processor : 1
vendor_id : GenuineIntel
cpu family : 6
model : 23
model name : Pentium(R) Dual-Core CPU E5200 @ 2.50GHz
stepping : 6
cpu MHz : 1200.000
cache size : 2048 KB
physical id : 0
siblings : 2
core id : 1
cpu cores : 2
apicid : 1
initial apicid : 1
fdiv_bug : no
hlt_bug : no
f00f_bug : no
coma_bug : no
fpu : yes
fpu_exception : yes
cpuid level : 10
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe nx lm constant_tsc arch_perfmon pebs bts pni dtes64 monitor ds_cpl em
bogomips : 4987.44
clflush size : 64
power management:
結論是: 用port的這種架構的開銷是可以接受的.
Recent Comments