ISUCON4 予選でスコア30万点以上を出す方法 #isucon
山形組提出コード
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/master
- 1プロセス
- オンメモリ
- nginx embeded perl
./benchmarkerv2 bench --workload 8 type:score success:290030 fail:0 score:62651 GOGC=off ./benchmarkerv2 bench --workload 8 type:score success:452310 fail:0 score:97702
提出後のチューニング
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/more_optimize
- indexの完全静的出力
./benchmarkerv2 bench --workload 8 type:score success:299330 fail:0 score:64659 GOGC=off ./benchmarkerv2 bench --workload 8 type:score success:455210 fail:0 score:98329
静的ファイルのJS出力
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/css_remove
- 静的ファイルのJS出力
./benchmarkerv2 bench --workload 16 type:score success:235064 fail:4 score:235064 GOGC=off ./benchmarkerv2 bench --workload 16 type:score success:308936 fail:0 score:308936
一応これで自分の中では予選問題を成仏させることができました。
追記(2014/10/02 01:00)
もう少し供養を続けてみました。
HTML-minify
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/html_minify
- HTML-minify
GOGC=off ./benchmarkerv2 bench --workload 32 type:score success:339802 fail:0 score:339802
Shift_JIS化
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/shiftjis
GOGC=off ./benchmarkerv2 bench --workload 32 type:score success:194522 fail:0 score:194522
Shift_JISにすることによってすこし容量が減るので(index.htmlで2055bytes->1832bytes)若干アップするかとおもったら大幅ダウンするという学び。ベンチマーク側の問題な気はするけど。
追記(2014/10/02 01:40)
workload 128
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/html_minify
ここまでくるとさらにworkloadはあげられるようで
GOGC=off ./benchmarkerv2 bench --workload 128 type:score success:346578 fail:0 score:346578
永続化の/reportまでの遅延
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/delay_write
レギュレーション微妙ではあるけどfujiwara組さんもやられてた技ですね。
[2014/10/02 13:28 追記]fujiwara組さんが実施されていたのは、/reportの出力のためのデータの作成の遅延であって、ここで自分がやっているような永続化自体の遅延ではありませんでした。訂正いたします。
参考
#isucon 2014にfujiwara組で出場して予選2日目暫定1位を取りました - beatsync.net
#isucon 4にfujiwara組として参加しました - handlename's blog
#isucon 4に参加して予選2日目暫定1位になりました - 酒日記 はてな支店
[/追記ここまで]
GOGC=off ./benchmarkerv2 bench --workload 128 type:score success:358384 fail:0 score:358384
ちなみにGOGC=offということもあり、ここまでくるとbenchmarkerは終了時には7G超のメモリを確保することになってます。
追記(2014/10/02 02:20)
合掌
TCP Fast Open
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/delay_write
sudo sh -c "echo 3 > /proc/sys/net/ipv4/tcp_fastopen" GOGC=off ./benchmarkerv2 bench --workload 256 type:score success:352112 fail:0 score:352112 sudo sh -c "echo 0x707 > /proc/sys/net/ipv4/tcp_fastopen" GOGC=off ./benchmarkerv2 bench --workload 256 type:score success:363856 fail:0 score:363856
追記(2014/10/07 01:24)
これが限界だと思ったときが限界なんだと悟りました。
HTML全体をjsで組み立て
HTML全体をjsに逃がしてしまえばベンチマーカーが取得するhtmlの容量を大幅に縮めつつ、jsを解釈するブラウザの見た目は維持できてしまう。ただし一部の項目はベンチマーカーがjs解釈をしないままにセレクタでチェックを行うため、非常にいびつながらも一部エレメントは埋め込んでおく必要がある。このあたりのチェックの仕様からいってもレギュレーションにおける変えてはいけないDOMというのはjs実行前と解釈するのがやはり自然だとは思いますね。
https://github.com/nihen/isucon4_yosen_yamagatagumi/tree/script_html
GOGC=off ./benchmarkerv2 bench --workload 256 type:score success:496764 fail:0 score:496764
うーん、惜しい。さらに50万超えるためのアイデアを思いつくかなぁ。
追記(2014/10/07 01:36)
おっとと、上記はTCP Fast Openを有効にして実行してなかった。0x707で有効にすると50万をギリギリ超えました。もう少しでフリーザ様の戦闘力に追いつける。
sudo sh -c "echo 0x707 > /proc/sys/net/ipv4/tcp_fastopen" GOGC=off ./benchmarkerv2 bench --workload 256 type:score success:500070 fail:0 score:500070
ISUCON3 予選のAMIでスコア74000点以上を出す方法 #isucon
https://github.com/nihen/isucon3_yosen_yamagatagumi
上記のisucon3.psgi/nginx.confでworkload=3で
Result: SUCCESS
RawScore: 74385.9
Fails: 0
Score: 74385.9
がでています。
予選実施時とくらべたポイントは
- workload指定(ちなみに予選通過amiもworkload指定すると5万超えしてました)
- isucon_sessionクッキーを持っていない場合のみproxy_cache
- アプリケーションは余計なキャッシュの妨げとなるクッキーを吐かない
これぐらいです。
Perlの変数はCoWで共有されてもいずれCopyされるという話
https://gist.github.com/nihen/7085103
http://www.perlmonks.org/?node_id=905667
https://rt.perl.org/rt3//Public/Bug/Display.html?id=119937
http://d.hatena.ne.jp/kazuhooku/20100909/1284005880
大きいマスターデータなどを親プロセスで読み込んでおいて、その後forkし、子プロセスでCoWで共有する、そんな幻想を抱いていた時期が僕にもありました。
PerlのGCのための参照カウンタは、変数の内部にカウンターをもっているため、そのカウンターに増減があるとCoWで共有されていたメモリのうちカウンターと同一のページ内のメモリは文字通りCopyされる。
通常のコード内でそのことに気をつけたとしても、子プロセスが死ぬときにどうしてもカウンターを0に減らさなければならず、すべての変数のカウンターと同一のページは最終的にすべてCopyされるものと思わなければならない。
つまり変数とカウンターがすべて同一ページにのっているような細かいデータの集まり等をマスターデータで読み込んでいると、CoWなど最終的には意味がなくなる。
2013/10/22 13:26 追記
上記の文章を読むとPerlの変数のCoWは意味がないと誤解する人もいるかもしれないのだけど、Copyされるデータはあくまで変数に付随するカウンター値と同一のページなので、変数自体が複数のページにまたがっている大きいデータ等の場合は、Copyされるのは1ページだけであるから意味が無いわけではない。また、カウンタが維持されている間は紛れも無く共有されているわけなので、下記の対策にも書いてあるとおり、カウンタ変更のタイミングを制御することにより、意味あるものとすることも可能です。誤解のなきよう。
対策
1. Copyされても問題のない使用メモリ容量とfork数におさえる
2. 子プロセスが一斉に死なないように慎重に配慮する。例えば Parallel::Prefork のspawn_interval がそれに使える。
現在のcarton利用時のモジュールアップグレード方法について
追記(2013/09/20) carton1.0以降を使うことにより下記の問題はすべて解決しています。心配するのをやめて新しいcartonをお使いください。
cartonはupdateコマンドが現在まだサポートしていない。
問題は二つあって、
- development環境でアップグレードしたモジュールがcarton.lockに反映されない
- deployment環境でcarton.lockに反映されているモジュールのバージョンが正しくインストールされない。
というのがあるのでそれぞれの現在の現実的な解決策をメモ。
モジュールのアップグレード自体は
cpanm -L local FooModule
またはcpanfile/Makefile.PL/Build.PLに新たな依存条件を記述して
carton install
のどちらでもおk.
development環境でアップグレードしたモジュールがcarton.lockに反映
問題の詳細については以前書いたエントリを参照。今回はアップグレードへの対応という意味での現実的な対策である解決策1"最新バージョンをインストールバージョンとする "を行うスクリプトを書いたのでこれを使う。
実行結果は
% cpandupmetaclean Find 2 Duplicate modules HTTP::Body * 1.17 1.16 HTTP::Message * 6.06 6.03 Clean old .meta directories?(Y/n) HTTP::Body : Delete .meta directories... 1.16 local/lib/perl5/i686-linux/.meta/HTTP-Body-1.16 unlink local/lib/perl5/i686-linux/.meta/HTTP-Body-1.16/MYMETA.json unlink local/lib/perl5/i686-linux/.meta/HTTP-Body-1.16/install.json rmdir local/lib/perl5/i686-linux/.meta/HTTP-Body-1.16 HTTP::Message : Delete .meta directories... 6.03 local/lib/perl5/i686-linux/.meta/HTTP-Message-6.03 unlink local/lib/perl5/i686-linux/.meta/HTTP-Message-6.03/MYMETA.json unlink local/lib/perl5/i686-linux/.meta/HTTP-Message-6.03/install.json rmdir local/lib/perl5/i686-linux/.meta/HTTP-Message-6.03
となり、metaディレクトリが綺麗になるのでその後再度
carton install
を実行すると、carton.lockが最新バージョンに更新されるはず。
deployment環境でcarton.lockに反映されているモジュールのバージョンをインストールする
carton.lockにかかれている他のモジュールから依存されているモジュールをアップグレードした場合、その親モジュール側のバージョンが既にインストール済みだと、依存されている側のモジュールのアップグレードが起こらないという問題がある。
これについて今日、pull requestを送ったのでこれが取り込まれるのを待つか、自分のforkレポジトリからcartonをインストールすると良いと思います。
git-set-file-times became to include merge commits.
git-set-file-times はmerge commitの時間でmtimeを更新してくれていなかったので、コミット日時とマージ順序が逆転しているとmtimeが更新されないままになっていたので修正しました。
diff: https://git.wiki.kernel.org/index.php?title=ExampleScripts&curid=44&diff=18471&oldid=15172
このExampleScripts、なんでgitで管理しないんだろう!
Teng vs DBIx::Skinny vs DBIx::Class vs DBI ベンチマーク 2012
https://gist.github.com/3801485
--- insert --- Rate dbic teng skinny dbi dbic 941/s -- -17% -35% -45% teng 1131/s 20% -- -21% -34% skinny 1437/s 53% 27% -- -16% dbi 1706/s 81% 51% 19% -- --- fast_insert --- Rate dbic skinny teng dbi dbic 950/s -- -32% -38% -45% skinny 1399/s 47% -- -9% -19% teng 1540/s 62% 10% -- -11% dbi 1722/s 81% 23% 12% -- --- single --- Rate dbic skinny teng dbi dbic 1810/s -- -58% -63% -83% skinny 4325/s 139% -- -11% -60% teng 4876/s 169% 13% -- -55% dbi 10882/s 501% 152% 123% -- --- lookup --- Rate dbic skinny teng dbi dbic 1600/s -- -61% -76% -85% skinny 4072/s 155% -- -39% -62% teng 6720/s 320% 65% -- -38% dbi 10860/s 579% 167% 62% -- --- search --- Rate dbic skinny teng dbi dbic 950/s -- -42% -58% -83% skinny 1630/s 72% -- -28% -70% teng 2268/s 139% 39% -- -58% dbi 5440/s 473% 234% 140% -- --- search_by_sql --- Rate skinny teng dbi skinny 1879/s -- -28% -66% teng 2619/s 39% -- -53% dbi 5547/s 195% 112% -- --- search_named --- Rate skinny teng skinny 1777/s -- -27% teng 2419/s 36% --
Teng::Plugin::LookupPK
Tengには機能は制限されているけれどもsingle()よりも速いlookup()が使える、Teng::Plugin::Lookupていうプラグインが付属しているのだけれども、それよりもさらに機能を制限して速くしたlookup_pk()ってのを実装してみました。
一旦Teng本体をforkしたものにコミットしてみたけど、多分独立したモジュールになると思います。
__PACKAGE__->load_plugin('LookupPK');
としてあげて
$teng->lookup_pk(user => 1); # SELECT * FROM user where id = ? LIMIT 1 [1]
のように使います。primary key名がidじゃない場合は
__PACKAGE__->load_plugin('LookupPK', {pk => 'gid'});
などのように変更できます。
制限事項は以下の通りです。
機能 | single | lookup | lookup_pk |
---|---|---|---|
FOR UPDATE | o | o | o |
selectカラムの指定 opt->{columns/+columns} |
o | o | x |
カラム名のquote | o | o | x |
複数primary key | o | o | x |
SQL::Makerを用いた複雑な条件式 | o | x | x |
また、lookup_pkではselectカラムの指定を*(アスタリスク)を使っていますのでSchemaクラスで定義したcolumnsと実際のテーブルのcolumnsが一致しない場合に結果が異なることになります。
で、ORMがこのように普通は*(アスタリスク)を使わないのがなんでなのかーというのがよくわからなくて、思いつく理由としては「schema定義からはわざと外すフィールドがある」とかぐらいしかなくてたいした理由じゃないなーということで、どなたかその理由を説明してくださるかたがいらっしゃれば嬉しいです!
追記: 2012/09/18 13:54
あー、ベンチマーク結果のせるのわすれていた
Benchmark: timing 10000 iterations of dbi, lookup, lookup_arrayref, lookup_pk, single, single_by_sql... dbi: 0.755288 wallclock secs ( 0.50 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.50 CPU) @ 20000.00/s (n=10000) lookup: 1.61698 wallclock secs ( 1.02 usr 0.00 sys + 0.00 cusr 0.00 csys = 1.02 CPU) @ 9803.92/s (n=10000) lookup_arrayref: 1.68399 wallclock secs ( 1.04 usr 0.00 sys + 0.00 cusr 0.00 csys = 1.04 CPU) @ 9615.38/s (n=10000) lookup_pk: 1.24839 wallclock secs ( 0.80 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.80 CPU) @ 12500.00/s (n=10000) single: 2.4761 wallclock secs ( 1.59 usr 0.00 sys + 0.00 cusr 0.00 csys = 1.59 CPU) @ 6289.31/s (n=10000) single_by_sql: 1.29905 wallclock secs ( 0.77 usr 0.00 sys + 0.00 cusr 0.00 csys = 0.77 CPU) @ 12987.01/s (n=10000) Rate single lookup_arrayref lookup lookup_pk single_by_sql dbi single 6289/s -- -35% -36% -50% -52% -69% lookup_arrayref 9615/s 53% -- -2% -23% -26% -52% lookup 9804/s 56% 2% -- -22% -25% -51% lookup_pk 12500/s 99% 30% 27% -- -4% -37% single_by_sql 12987/s 106% 35% 32% 4% -- -35% dbi 20000/s 218% 108% 104% 60% 54% --
このようなかんじでsingle_by_sqlとほぼ遜色ないかんじになっております