Hateburo: kazeburo hatenablog

SRE / 運用系小姑 / Goを書くPerl Monger

最近の Plack のパフォーマンス改善まとめ 2015年11月版

OSS活動の成果発表のお時間です。

Plackの 1.0038と2015年11月27日時点のmasterにはPlack::Request、Plack::Responseのパフォーマンスをあげる変更が入ってます。その紹介とベンチマークです。

github.com

Plack 1.0038 で HTTP::Headers::Fast 0.20 につけた flatten メソッドを使うようになってます。Plack::Response->finalizeのパフォーマンス向上が期待できます。

github.com

こちらはまだmasterにmergeされた状態。リリースはされてない。POSTリクエストのパースにHTTP::Bodyではなく、HTTP::Entitiy::Parserを使い、パラメータのパースに WWW::Form::UrlEncodedを使うようになっています。

HTTP::Entity::Parserは tokuhirom の pullreq をベースに作ったモジュールで chansen の HTTP::MultiPartParserをuploadデータの解析に利用しています。GETのクエリーパラメータなどの文字列をパースする WWW::Form::UrlEncoded は XS版も用意され、インストールするだけで長いクエリパラメータの処理が超高速になります。

ベンチマーク

以下のような psgi を用意して Plackの 1.0037、10038、そしてmasterでベンチマークを行いました。

use Plack::Builder;
use Plack::Request;
my $length = 4000;
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');
        my $res = $req->new_response(200);
        $res->content_type('text/plain');
        $res->content_length($length);
        $res->body($body);
        $res->finilize;
    }
};

ベンチマークを行ったサーバはgcpの12コアのサーバです。下記のsysctlのチューニングを行いました

sudo sysctl -w net.core.netdev_max_backlog=8192
sudo sysctl -w net.core.somaxconn=32768
sudo sysctl -w net.ipv4.tcp_tw_recycle=1
sudo sysctl -w net.ipv4.tcp_tw_reuse=1

perlは5.20.3、Starletは 0.28、Gazelleは0.40です。

起動オプションは Starlet、Gazelleともに同じです。

plackup -s (Starlet|Gazelle) -E production --max-workers=10 --max-reqs-per-child=5000000 -a app.psgi 

ベンチマークは wrk にて取りました

./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'

結果

f:id:kazeburo:20151127160552p:plain

1.0037から1.0038は微増。1.0038からmasterは PP版で5%ほどのパフォーマンス向上、XSになると15-20%のパフォーマンス向上となりました。WWW::Form::UrlEncoded::XSを使うと実際のアプリケーションでも差異がでるかもしれません。

以下wrkコマンドの結果

Gazelle + Plack 1.0037

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   375.59us  187.59us   9.30ms   98.41%
    Req/Sec     8.58k     0.93k   12.50k    91.67%
  512632 requests in 30.00s, 1.98GB read
Requests/sec:  17085.90
Transfer/sec:     67.46MB

Gazelle + Plack 1.0038

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   369.07us  188.53us   8.06ms   98.86%
    Req/Sec     8.73k   686.61    12.24k    89.50%
  520994 requests in 30.00s, 2.01GB read
Requests/sec:  17365.83
Transfer/sec:     68.56MB

Gazelle + Plack master + WWW::Form::UrlEncoded::PP

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   347.94us  210.95us  10.08ms   98.87%
    Req/Sec     9.15k     0.90k   13.45k    91.33%
  546160 requests in 30.00s, 2.11GB read
Requests/sec:  18204.06
Transfer/sec:     71.87MB

Gazelle + Plack master + WWW::Form::UrlEncoded::XS

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   297.28us  220.10us  10.23ms   99.33%
    Req/Sec    10.30k     0.95k   14.73k    92.00%
  614871 requests in 30.00s, 2.37GB read
Requests/sec:  20494.01
Transfer/sec:     80.91MB

Starlet + Plack 1.0037

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   450.54us  175.99us   7.97ms   96.39%
    Req/Sec     7.38k     0.90k   11.11k    92.33%
  440509 requests in 30.00s, 1.71GB read
Requests/sec:  14683.24
Transfer/sec:     58.20MB

Starlet + Plack 1.0038

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   443.11us  175.54us   7.51ms   96.42%
    Req/Sec     7.47k     0.91k   11.22k    92.17%
  446088 requests in 30.01s, 1.73GB read
Requests/sec:  14866.09
Transfer/sec:     58.92MB

Starlet + Plack master + WWW::Form::UrlEncoded::PP

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   420.69us  177.07us   6.51ms   98.15%
    Req/Sec     7.82k   786.01    11.52k    91.33%
  467110 requests in 30.00s, 1.81GB read
Requests/sec:  15569.02
Transfer/sec:     61.71MB

Starlet + Plack master + WWW::Form::UrlEncoded::XS

$ ./wrk -t 2 -c 8 -d 30 'http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome'
Running 30s test @ http://localhost:5000/foo?foo=bar&bar=baz&q=HTTP%3A%3ARequest%3A%3ACommon&ie=UTF-8&sourceid=chrome
  2 threads and 8 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   373.80us  193.16us   6.98ms   98.96%
    Req/Sec     8.65k   785.91    12.77k    95.00%
  516923 requests in 30.02s, 2.00GB read
Requests/sec:  17222.14
Transfer/sec:     68.26MB

crontabのsyntax checkをTravis-CIで行う

crontabファイルをrespositoryで管理していますが、テストができてなかったので、それを解消すべくTravis-CIでやってみました。

.travis.yml

language: ruby
script:
  - cat crontab.ok.txt | crontab
  - cat crontab.fail.txt | crontab

travisで実行された様子

f:id:kazeburo:20150902145316p:plain

意外なことに簡単に動いた。

Travisはcrontabが登録されても実際にはコマンドが動かない、はず。

songmuさんのParse::Crontabも非常に良いのですが、今のプロジェクトはPerlのプロジェクトではないので若干入れにくい。

参考

おそらくはそれさえも平凡な日々: 運用におけるcrontabのテストとParse::Crontab

第25回 cron周りのベストプラクティス(1):Perl Hackers Hub|gihyo.jp … 技術評論社

2007-2015 YAPC::Asia Tokyo で喋ってきたまとめ

今年のblogはまだ書いてないけど書きました、これまでYAPC::Asia Tokyo で喋ってきたblog記事をまとめてみました 2007年から2015年まで10回中9回、トーク(2007年はLT)をさせて頂きました。

2015 ISUCONの勝ち方

isucon優勝するぞー!

blog.nomadscafe.jp

2014 Dockerで遊んでみよっかー

2014年はDockerの話をしました

blog.nomadscafe.jp

2013 PSGI/Plack・Monocerosで学ぶハイパフォーマンスWebアプリケーションサーバの作り方

2013年はPlackのサーバを作る話。前夜祭LTもやりました

blog.nomadscafe.jp

2012 1台から500台までのMySQL運用(YAPC::Asia編)

2012年はDB運用の話

blog.nomadscafe.jp

2011 運用しやすいWebアプリケーションの構築方法

2011年は運用しやすいWebアプリケーションというテーマで、ログやSQLについて

blog.nomadscafe.jp

2010 Introduction to CloudForecast

cloudforecastの話。最近はkuradoというのを作って使ってます

blog.nomadscafe.jp

2009 大規模画像配信とPerl

大規模画像配信の話

blog.nomadscafe.jp

2008 memcached in mixi

mixiのエンジニアblogで書いてました。2つトークをしていて、1つはmemcachedの話、もう一つはmod_perlをつかったjob処理でした

alpha.mixi.co.jp

2007 Expectをつかった Expectをつかったサーバ管理レシピ

LTでコマンドを複数のサーバで実行する話をしました

YAPC::Asia 2007でLTしてきました : blog.nomadscafe.jp

Rhebok, a High Performance Rack Handler/Server 2x faster than Unicorn

Last December I released Rhebok to rubygems. Rhebok is a High Performance Rack Handler/Server.

rhebok | RubyGems.org | your community gem host

kazeburo/rhebok · GitHub

Rhebok is a standalone Preforking Web Server. This server is optimized for running HTTP application server behind a reverse proxy like nginx. When start Rhebok as upstream server of nginx and connect via unix domain socket, Rhebok is 2 times faster than Unicorn.

Benchmark

f:id:kazeburo:20150106161554p:plain

"nginx static file" represents req/sec when delivering 13 bytes static files from nginx

Benchmark details is here.

Features

Rhebok supports following features.

  • ultra fast HTTP processing using picohttpparser
  • uses accept4(2) if OS support
  • uses writev(2) for output responses
  • prefork and graceful shutdown using prefork_engine
  • hot deploy using start_server (here is golang version by lestrrat-san)
  • supports HTTP/1.1. But does not have Keepalive.
  • supports OobGC

Installation and Usage

Add this line to your application's Gemfile and execute bundle install

gem 'rhebok'

Or install it yourself as

$ gem install rhebok

To run Rhebok, use the rackup command.

$ rackup -s Rhebok --port 8080 -O MaxWorkers=5 -O MaxRequestPerChild=1000 -O OobGC=yes -E production config.ru

For more details, please read README.md. If you found problems and bug, please send p-r or issue to github

SqaleでRailsアプリを高速サーバ「Rhebok」を使って起動する

Herokuに続き、Unicornの2倍のパフォーマンス発揮するRackサーバ「Rhebok」をパパボさんのPaaSであるSqaleで動かしてみる。15日間は無料お試しが出来るそうですよ。

Sqale - 開発者のためのホスティングサービス【スケール】 Ruby on Rails 対応。

Gemfileの用意

まず Gemfileを用意します。適当なディレクトリで

$ bundle init

して Gemfileを編集します

cat Gemfile
# A sample Gemfile
source "https://rubygems.org"
gem "rails", "4.2.0"

とりあえず最新のRails 4.2.0で固定して、bundle install

$ bundle install --path vendor/bundle

Railsアプリケーションの作成

$ bundle exec rails new . --skip-bundle

すると、途中でGemfileを上書きするか聞かれるので、そのままリターンで続行

    conflict  Gemfile
Overwrite /Users/kazeburo/Develop/sqale-rhebok/Gemfile? (enter "h" for help) [Ynaqdh]

上書きされたGemfileにRhebokを追加する

$ tail Gemfile
gem 'gctools'
gem 'rhebok', '>= 0.2.1'

OobGCを使う為にgctoolsも入れる。

ここまで出来たらbundle install

$ bundle install

Hello Worldだけでは面白くないので、Scaffoldを使ってアプリケーションのひな形を作る

$ bundle exec rails g scaffold msg nick:string body:text
$ bundle exec rake db:migrate 

ローカル環境で動くかどうか確認します。

$ bundle rails s

ローカルでRhebokを起動するには以下のコマンド

$ bundle exec rackup -s Rhebok -O MaxWorkers=1 -O OobGC=yes -O MaxRequestPerChild=0

Rhebokに変更しても問題なく動作すると思われます。

デプロイ準備

secrets.ymlのsecret_key_baseを設定する必要があります。今回はとりあえず環境変数にて設定してみました。

echo 'SECRET_KEY_BASE="'$(bundle exec rake secret)'"' > .env

Sqaleは.envというファイルに環境変数を書いておくと起動時に読み込んでくれます。ちなみに、SqaleではデータベースにMySQLを使う事ができますが、今回はテストなのでSQLiteのままいきます

Rhebokでアプリケーションを起動したいので、Procfileも書きます。

$ cat Procfile
app: bundle exec rackup -s Rhebok -O Path=/var/run/app/app.sock -O MaxWorkers=3 -O OobGC=yes -O MaxRequestPerChild=1000 -O MinRequestPerChild=500

Sqaleのコンテナはnginxとアプリケーションサーバが起動していて、間の通信にUnix Domain Socketを使うのが大きな特徴。なので、RhebokもUnix Domain SocketをListenさせる。

Rubyのバージョンを2.1.4に変更。ちなみに手元はrbenvを使わずにxbuildで2.1.5をいれてる

echo '2.1.4' > .ruby-version

vendorディレクトリをgitで管理しないようにして、git commit

$ echo '/vendor' >> .gitignore 
$ git init
$ git add .
$ git commit -m 'init'

最後に、sqaleにpush。

$ git remote add sqale ssh://sqale@gateway.sqale.jp:2222/userid/app_name.git
$ git push sqale master

最初は起動までに数分かかりました。2回目からは1分ぐらいでデプロイされた。

ブラウザでみると、こんなページができました。

f:id:kazeburo:20141227233921p:plain

SqaleにSSHして確認

ちゃんとRhebokで起動したか、SSHでコンテナにログインして ps コマンドを打ってみました。

$ ssh -p 2222 sqale@gateway.sqale.jp
sqale@app_name-1:~$ ps fax
  PID TTY      STAT   TIME COMMAND
 6816 ?        S      0:07 sshd: sqale@pts/0   
 6817 pts/0    Ss     0:00  \_ -bash
11236 pts/0    R+     0:00      \_ ps fax
   92 ?        S      0:00 nginx: worker process         
11133 ?        Sl     0:00 foreman: master                                                                                                                
11218 ?        Sl     0:02  \_ ruby /home/sqale/current/vendor/bundle/ruby/2.1.0/bin/rackup -s Rhebok -O Path=/var/run/app/app.sock -O MaxWorkers=3 -O OobGC=yes -O MaxRequestPerC
11223 ?        Sl     0:00      \_ ruby /home/sqale/current/vendor/bundle/ruby/2.1.0/bin/rackup -s Rhebok -O Path=/var/run/app/app.sock -O MaxWorkers=3 -O OobGC=yes -O MaxRequest
11226 ?        Sl     0:00      \_ ruby /home/sqale/current/vendor/bundle/ruby/2.1.0/bin/rackup -s Rhebok -O Path=/var/run/app/app.sock -O MaxWorkers=3 -O OobGC=yes -O MaxRequest
11229 ?        Sl     0:00      \_ ruby /home/sqale/current/vendor/bundle/ruby/2.1.0/bin/rackup -s Rhebok -O Path=/var/run/app/app.sock -O MaxWorkers=3 -O OobGC=yes -O MaxRequest

ちゃんと起動出来ているようですね〜

SSHで入ってログ見たり、straceでデバックしたり、killコマンドでアプリケーションの再起動できたりするのでSqale便利!

HerokuでSinatraのアプリを「Rhebok」で起動する

Unicornの2倍のパフォーマンス発揮するRackサーバ「Rhebok」をherokuで動かしてみる

アプリケーションは

heroku で Sinatra のアプリを動かす - Please Sleep

を参考にさせて頂きました。

Gemfile

まずGemfileを用意します

$ cat Gemfile
source 'https://rubygems.org'
ruby "2.1.5"
gem 'sinatra'
gem 'gctools'
gem 'rhebok'

rubyのバージョンは2.1系を使い、gctoolsもいれます。Rhebokはgctoolsに含まれるGC::OOBを利用し、リクエスト処理終了後(Out Of Band)のGCを効率的に行います。

tmm1/gctools · GitHub

日本語訳: Ruby 2.1: Out-of-Band GC — sawanoboly.net

app.rb

"hello world"を出力するアプリケーションです。

$ cat app.rb
require 'sinatra'

get '/' do
  @text = "hello world\n"
  erb :index
end

views/index.erb

テンプレート

$ cat views/index.erb 
<!DOCTYPE html>
<html>
<body>
<%= @text %>
</body>
</html>

config.ru

config.ruファイルもつくります。

$ cat config.ru
require './app'
run Sinatra::Application

bundle install

依存モジュールのインストール

$ bundle install --path vendor/bundle

.gitignoreの用意

vendor、.bundleディレクトリを対象外とします

cat .gitignore
vendor
.bundle

手元で起動

$ bundle exec rackup -s Rhebok config.ru
Rhebok starts Listening on localhost:9292 Pid:77840

9292ポートで起動するのでブラウザやcurlで確認します。

Procfile

Rhebokでアプリケーションが起動するようにProcfileを書きます

$ cat Procfile
web: bundle exec rackup --port $PORT -s Rhebok -O OobGC=yes config.ru

OobGCを有効にしてみました。デフォルトで5個のworkerが起動します。

herokuへpush

git commitしてherokuへpushします

$ git init
$ git add .
$ git commit -m '…'
$ heroku create APP_NAME
$ git push heroku master

これでアプリケーションがherokuにて起動します。

curlでアクセスして確認します。

$ curl -v https://APP_NAME.herokuapp.com/
...
< HTTP/1.1 200 OK
< Connection: keep-alive
< Server: Rhebok
< Content-Type: text/html;charset=utf-8
< Content-Length: 59
< X-Xss-Protection: 1; mode=block
< X-Content-Type-Options: nosniff
< X-Frame-Options: SAMEORIGIN
< Date: Thu, 25 Dec 2014 02:47:58 GMT
< Via: 1.1 vegur
< 
<!DOCTYPE html>
<html>
<body>
hello world
</body>
</html>

Server: Rhebok と表示されたので、無事にRhebokにてアプリケーションが起動したことが確認できました。

同じアクセス数を処理している場合、RhebokはUnicornと比べてCPU使用率が低いという特徴もあるので、ぜひ試してくださいませ

picohttpparserのRubyバインディングとPreforkサーバを書く時に便利なgemをリリースしたので、Rackサーバ書いてみた

GazelleでやったことをRubyでもやってみようと思い、まず picohttpparserRuby バインディングと、perforkなサーバを書く時に便利なモジュールであるParallel::PreforkRuby版を書いてリリースしました。

pico_http_parser

http://rubygems.org/gems/pico_http_parser

prefork_engine

http://rubygems.org/gems/prefork_engine

そしてこの2つを使って、StarletのRuby版を書いてみました。ソースコードはprefork_engineのrepositoryにあります。

https://github.com/kazeburo/prefork_engine/blob/master/example/starlet.rb

動かし方

まず上の2つのgemをいれます。bundlerを使っても良いですね

$ gem install pico_http_parser prefork_engine

prefork_engineのrepositoryをcloneしてきてexampleディレクトリに移動

$ git clone https://github.com/kazeburo/prefork_engine.git
$ cd prefork_engine/example

hello worldなサンプルアプリケーションも同梱されているので、そいつを動かしてみます

$ rackup -r ./starlet.rb -s Starlet -O MaxWorkers=5 -O MaxRequestPerChild=1000 config.ru

curlでアクセスすると

$ curl -sv http://localhost:9292/|less
*   Trying 127.0.0.1...
* Connected to localhost (127.0.0.1) port 9292 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.38.0
> Host: localhost:9292
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Connection: close
< Content-Type: text/html
< Date: Thu, 11 Dec 2014 07:00:30 GMT
< Server: RubyStarlet
<
{ [data not shown]
* Closing connection 0
hello world

ちゃんと出て来た。大丈夫そう!

軽くベンチマーク

環境はEC2のc3.4xlarge、Amazon Linuxを使いました。nginxのうしろにアプリケーションサーバとして動作させた場合を想定してます。

ベンチマーク準備

Rubyはxbuildを使っていれます。unix domain socketで通信する為にstart_serverというPerl製のコマンドが必要になるのでPerlもいれます。

$ git clone https://github.com/tagomoris/xbuild.git
$ ./xbuild/ruby-install 2.1.5 ~/local/ruby-2.1
$ xbuild/perl-install 5.20.1 ~/local/perl-5.20 -j4
$ export PATH=/home/ec2-user/local/ruby-2.1/bin:$PATH
$ export PATH=/home/ec2-user/local/perl-5.20/bin:$PATH

nginxはyumでインストールし、次のように設定をしました

worker_processes  4;

events {
  worker_connections  10000;
}

http {
  include     mime.types;
  access_log  off;
  sendfile    on;
  tcp_nopush  on;
  tcp_nodelay on;
  etag        off;
  upstream app {
    server unix:/dev/shm/app.sock;
  }

  server {
    location / {
      proxy_pass http://app;
    }
  }
}

比較のためにunicornもいれました。

$ gem install pico_http_parser prefork_engine unicorn

ruby版Starletの起動コマンド

$ start_server --path /dev/shm/app.sock -- rackup -r ./starlet.rb -E production -s Starlet -O MaxWorkers=12 -O MaxRequestPerChild=500000 config.ru

unicornのconfigと起動コマンド

$ cat unicorn.rb
worker_processes 8
preload_app true
listen "/dev/shm/app.sock"
$ unicorn -E production -c unicorn.rb config.ru

ベンチマーク結果

ruby版Starlet

$ ./wrk -t 2 -c 32 -d 30  http://localhost/
Running 30s test @ http://localhost/
  2 threads and 32 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   401.75us  114.12us   5.67ms   80.92%
    Req/Sec    40.83k     2.90k   53.56k    72.18%
  2291892 requests in 30.00s, 384.58MB read
Requests/sec:  76397.24
Transfer/sec:     12.82MB

unicorn

$ ./wrk -t 2 -c 32 -d 30  http://localhost/
Running 30s test @ http://localhost/
  2 threads and 32 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency   393.75us  188.08us   4.41ms   78.60%
    Req/Sec    42.66k     6.64k   62.33k    71.41%
  2389390 requests in 30.00s, 437.40MB read
Requests/sec:  79647.31
Transfer/sec:     14.58MB

picohttpparserがすごく速いというのはありますが、それ以外の部分がRubyだけで書かれているにしては良いベンチマーク結果になっている気がします