Erlang で httpd
Erlang を改めて勉強中。
なんか並列化で遊べそうなことないかなーと思ったので、とりあえず httpd。
ベンチとか楽だし。
あ、最近は httpd なんてモジュールが標準で入ったみたいなのでそっち使うと良いのかもしれないですよ。
ソース
とりあえずソース。
並列化前
-module(httptest). -export([listen/1]). print(X) -> io:format("~p\n", [X]). recvAll(Sock, Result) -> receive {tcp, Sock, Bin} -> {ok, [Bin|Result]}; {tcp_closed, Sock} -> {ok, Result} end. recvAll(Sock) -> {ok, Result} = recvAll(Sock, []), {ok, list_to_binary(lists:reverse(Result))}. response(Sock) -> {ok, Bin} = recvAll(Sock), gen_tcp:send(Sock, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, Erlang"), gen_tcp:close(Sock). acceptLoop(Sock) -> {ok, Accept} = gen_tcp:accept(Sock), response(Accept), acceptLoop(Sock). listen(Port) -> {ok, Sock} = gen_tcp:listen(Port, [binary, {packet, 0}]), acceptLoop(Sock).
で
1> httptest:listen(8080).
してブラウザでアクセスすると "Hello, Erlang" と表示される。
並列化後
acceptLoop を process 分けて何個か走らせる。
Listen Socket に対する accept は複数プロセスから発行しても良いらしい。
-module(httptest). -export([listen/1]). print(X) -> io:format("~p\n", [X]). recvAll(Sock, Result) -> receive {tcp, Sock, Bin} -> {ok, [Bin|Result]}; {tcp_closed, Sock} -> {ok, Result} end. recvAll(Sock) -> {ok, Result} = recvAll(Sock, []), {ok, list_to_binary(lists:reverse(Result))}. response(Sock) -> {ok, Bin} = recvAll(Sock), gen_tcp:send(Sock, "HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nHello, Erlang"), gen_tcp:close(Sock). acceptLoop(Sock) -> {ok, Accept} = gen_tcp:accept(Sock), response(Accept), acceptLoop(Sock). listen(Port) -> {ok, Sock} = gen_tcp:listen(Port, [binary, {packet, 0}]), lists:map(fun(_) -> spawn(fun() -> acceptLoop(Sock) end) end, lists:seq(1, 100)), acceptLoop(Sock).
ベンチ
$ ab -c 10 -n 10000 http://127.0.0.1:8080/
してみる。
並列化前
$ ab -c 10 -n 10000 http://127.0.0.1:8081/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Server Hostname: 127.0.0.1 Server Port: 8081 Document Path: / Document Length: 13 bytes Concurrency Level: 10 Time taken for tests: 2.999 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 580000 bytes HTML transferred: 130000 bytes Requests per second: 3334.00 [#/sec] (mean) Time per request: 2.999 [ms] (mean) Time per request: 0.300 [ms] (mean, across all concurrent requests) Transfer rate: 188.84 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 30.0 0 2998 Processing: 0 1 16.1 1 1171 Waiting: 0 1 16.1 1 1171 Total: 0 2 34.0 1 2999 Percentage of the requests served within a certain time (ms) 50% 1 66% 1 75% 1 80% 1 90% 1 95% 1 98% 1 99% 1 100% 2999 (longest request)
並列化後
大体 Requests per second が倍まで行かない程度あがっている感じ。
Core2 Duo のマシンなのでコア数分くらいは速くなっている。すげー。
$ ab -c 10 -n 10000 http://127.0.0.1:8080/ This is ApacheBench, Version 2.3 <$Revision: 655654 $> Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ Licensed to The Apache Software Foundation, http://www.apache.org/ Benchmarking 127.0.0.1 (be patient) Completed 1000 requests Completed 2000 requests Completed 3000 requests Completed 4000 requests Completed 5000 requests Completed 6000 requests Completed 7000 requests Completed 8000 requests Completed 9000 requests Completed 10000 requests Finished 10000 requests Server Software: Server Hostname: 127.0.0.1 Server Port: 8080 Document Path: / Document Length: 13 bytes Concurrency Level: 10 Time taken for tests: 1.680 seconds Complete requests: 10000 Failed requests: 0 Write errors: 0 Total transferred: 580000 bytes HTML transferred: 130000 bytes Requests per second: 5951.62 [#/sec] (mean) Time per request: 1.680 [ms] (mean) Time per request: 0.168 [ms] (mean, across all concurrent requests) Transfer rate: 337.10 [Kbytes/sec] received Connection Times (ms) min mean[+/-sd] median max Connect: 0 0 0.2 0 5 Processing: 0 1 23.6 1 1670 Waiting: 0 1 23.6 1 1670 Total: 0 1 23.6 1 1671 Percentage of the requests served within a certain time (ms) 50% 1 66% 1 75% 1 80% 1 90% 1 95% 1 98% 2 99% 3 100% 1671 (longest request)
はまったところ
Socket からのデータを receive {tcp, Scok, Bin} -> ... で受信する場合は、そのソケットを開いたプロセスじゃないとだめなので、 accept で取れたソケットを spawn で別プロセスに渡すと receive での受信ができない。
そういうときは gen_tcp:recv すれば良いんだと思うんだけども、試していない。
あと、ベンチは別マシンでやらないと意味ないかもね、とも。
まとめ
- 並列化万歳!
- Erlang 楽しい