Starman と Starlet のベンチマークと Accept Serialization
StarmanとStarletの違いはいくつかありますが、Starletにいくつか手を加えたあと、速度はどうなっているのか比較してみた。
なお、以下の記事はHello Worldのベンチマークなので、実際のアプリケーションのパフォーマンスにはあまり影響がないと思われます。
各ソフトウェアのバージョンは以下。
Starletのベンチマークとほぼ同じアプリケーションを書いてサーバを起動した
use Plack::Builder; use Plack::Request; my $length = 12; my $body = 'x'x$length; builder { enable 'AccessLog', logger => sub { }; sub { my $env = shift; my $req = Plack::Request->new($env); my @params = $req->param('foo'); [200, ['Content-Type'=>'text/plain','Content-Length'=>$length],[$body]] } }
前回との違いはbodyのサイズ、今回は1200Byteだったのを12Byteと小さくしている。
ベンチマークの環境は、4core/8threadの L5630*2を搭載したサーバ。abを使って別のホストからリクエストを送っている。abを起動したサーバも同じスペック。
abは -c 30 で実行して、Starman、Starletはそれぞれ workers、max-workersの設定を変更してベンチマークを行った。
ab
$ ab -c 30 -n 30000 'http://10.x.x.x:5000/foo?foo=bar&bar=baz&baz=hoge&hoge=foo'
$ carton exec -- starman --preload-app --workers=16 --max-requests=50000 -a app.psgi
Starlet
$ carton exec -- plackup -s Starlet -E production --max-workers=16 --max-reqs-per-child=50000 -a app.psgi
下のグラフが結果。
Starletの方が全体に request/sec がよく、worker数に応じてスケールしている。Starmanはworker数6で頭打ちになり、それ以降 request/sec が下がってくるという結果になった。
このパフォーマンス低下の原因として考えたのがStarman(Net::Server)が使っている accept(2)のserialization。id:naoya さんの2007年のblogが詳しい。
http://d.hatena.ne.jp/naoya/20070311/1173629378
Starmanでは、accept(2)のserializationにflockを使っている。straceでも確認できる
write(6, "2043 waiting\n", 13) = 13 flock(7, LOCK_EX) = 0 getsockopt(4, SOL_SOCKET, SO_TYPE, [223338299393], [4]) = 0 accept(4, {sa_family=AF_INET, sin_port=htons(44030), sin_addr=inet_addr("10.x.x.x")}, [18350761115241152528]) = 8 ioctl(8, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffb5f62250) = -1 EINVAL (Invalid argument) lseek(8, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) ioctl(8, SNDCTL_TMR_TIMEBASE or TCGETS, 0x7fffb5f62250) = -1 EINVAL (Invalid argument) lseek(8, 0, SEEK_CUR) = -1 ESPIPE (Illegal seek) fcntl(8, F_SETFD, FD_CLOEXEC) = 0 flock(7, LOCK_UN) = 0 write(6, "2043 processing\n", 16) = 16 getsockname(8, {sa_family=AF_INET, sin_port=htons(5000), sin_addr=inet_addr("10.x.x.x")}, [4657227515171962896]) = 0 ... read(8, "GET /foo?foo=bar&bar=baz&baz=hoge&hoge=foo HTTP/1.0\r\nHost: 10.x.x.x:5000\r\nUser-Agent: ApacheBench/2.3\r\nAccept: */*\r\n\r\n", 65 536) = 122
acceptの前後でflockしていますね。
Net::Server(::PreforkSimple)ではオプションでserializationの方法が選択できて、none(なし)も選べる。そこでserializationが必要ないパターンをApacheのconfigureを参考にして
diff --git a/lib/Starman/Server.pm b/lib/Starman/Server.pm index 1c2e08a..73e7e64 100644 --- a/lib/Starman/Server.pm +++ b/lib/Starman/Server.pm @@ -68,7 +68,7 @@ sub run { port => $port, host => $host, proto => $proto, - serialize => 'flock', + serialize => ( $^O =~ m!(linux|darwin|bsd|cygwin)$! ) ? 'none' : 'flock', log_level => DEBUG ? 4 : 2, ($options->{error_log} ? ( log_file => $options->{error_log} ) : () ), min_servers => $options->{min_servers} || $workers,
とパッチして、もう一度ベンチマークをとってみたのが次のグラフ。
パフォーマンスが落ちる事はなくなったけど、期待するほどの伸びにはならなかった。
ふむー。