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

がでています。

予選実施時とくらべたポイントは

これぐらいです。

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コマンドが現在まだサポートしていない。

問題は二つあって、

  1. development環境でアップグレードしたモジュールがcarton.lockに反映されない
  2. deployment環境でcarton.lockに反映されているモジュールのバージョンが正しくインストールされない。

というのがあるのでそれぞれの現在の現実的な解決策をメモ。

モジュールのアップグレード自体は

cpanm -L local FooModule

またはcpanfile/Makefile.PL/Build.PLに新たな依存条件を記述して

carton install

のどちらでもおk.

development環境でアップグレードしたモジュールがcarton.lockに反映

問題の詳細については以前書いたエントリを参照。今回はアップグレードへの対応という意味での現実的な対策である解決策1"最新バージョンをインストールバージョンとする "を行うスクリプトを書いたのでこれを使う。

cpandupmetaclean

実行結果は

% 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で管理しないんだろう!

参考: http://d.hatena.ne.jp/hirose31/20090106/1231171068

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'});

などのように変更できます。

制限事項は以下の通りです。

機能singlelookuplookup_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とほぼ遜色ないかんじになっております