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) = 122acceptの前後で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,とパッチして、もう一度ベンチマークをとってみたのが次のグラフ。

パフォーマンスが落ちる事はなくなったけど、期待するほどの伸びにはならなかった。
ふむー。