ISUCON12 予選突破
チーム「と」最終31962点で11位。
方針
「まずは SQLite のまま進める。テーブル単位で徐々に MySQL に載せ替えつつも、ボトルネック解消に専念し1台でこれ以上大きく伸びなそう。と思うタイミングで複数台使う。大体16時ごろを目処」と思っていたが、結局上手くいかず何も MySQL に載せることはなく最後まで1台のまま進めることにした。また、最後の1時間で MySQL だけでも分けようとも思ったが、ログ出力を消したことで FAIL するようになったことの対応に追われて叶わず。
スコアを伸ばせたのは
- MySQL ネックの解消
- visit_history への index 追加と dispenseID の xid 化
- SQLite に対する非効率なクエリの解消
- visitor/player count と rank を都度計算せずに保存する
- score の bulk insert
ぐらいだったと思う。
使ったツール
- alp
- pt-query-digest
- pprof
- top
やったこと
錯乱しながら作業していたのであまり正確ではない。
- 10:00 - 13:00
- 13:00 - 14:45
- pprof の有効化
- MySQL ネックではなくなったので、この後 pt-query-digest は使わずに pprof と top だけ見ていた
- 何も前進せず予選敗退の恐怖に支配され心が折れかける
- pprof の有効化
- 14:45
- 妻に「切り替えてやれ」と凄まれ、自然と予選敗退の恐怖は消えさった。妻に感謝
- 14:45 - 16:00
competition
テーブルにvisitor_count
/player_count
追加- 大会終了時に計算して UPDATE
- 16:00 - 17:00
ranking (competition_id [pk], ranking [pk], score, player_id [idx1], player_display_name)
テーブルを追加して、ページングして取得できるようにする/api/player/player/:player_id
でもこのテーブルを使った
- initialize が timeout するようになったので雑に並列に insert 投げて、sleep 2s している。sync.WaitGroup 使えよという感じがするが、FAIL を繰り返していたのでとにかく通したいという思いが先行した。
- player_score を bulk insert にする
- 17:00 - 18:00
- go-json を使う
- ログ出力をやめると to many files が出るようになったので LimitNOFILE を設定
- keepalive や connection pool のサイズを変更したりしたが特段効果なし
改めて振り返ると無駄なことで時間を消費していたことが分かるし、やろうと思ったことは全然出来なかった。ただ、時間内でもっと出来たとは思えず、まあまあうまくいったほうじゃないかなという気がしている。
このままでは本戦を戦えなさそうなので、何度か練習しておこうと思う。
gopwt の windows 対応をした
一応本体のテストは通り、別の小さいライブラリの gopwt を使ったテストも通った。とりあえず全く動かない今よりは大分マシなのでマージした。
Windows で go インストールまでも結構らくだったし、AppVeyor という便利なのがあり CI も出来て、思ったよりスムーズに事が進んだ。
■
React Native を使う、型による支援がなくて不安、flow を使う。というのがなんか胃の中掻き回されてる感じがしてなんかしっくりこない。Web だったらたしかに良くなるねとなる。ネイティブアプリでも多分自分の場合はそのほうが効率的だろうな思うが、あべこべなことをやっているんじゃないか、どうせだったら型は静的に決まらないほうがいいんだ!と叫んで flow のようなものを使わないほうがまだ一貫性があるんじゃないかなどと考え出して上手くいかない。別に使うかどうか悩んだり迷ったりしているわけではなくて、たまにこういう風に思って結論が出ない。
Web っぽい UI
iOS で幅が可変のタグっぽい UI を作ってるとやりたいことのわりには結構大変だった。Web だと数秒でできそうだけど、他ではそんなに簡単でないものを Web っぽい UI と呼ぶことにした。Web っぽい UI を実装しないと行けないときに React Native だと大分楽に出来そうな気がしている。それとは別に Web っぽい UI を実装すべきかどうかはよく分からん問題がある。良い UI だから使われてきたものか手軽にそれなりに出せるから使われてきたものなのかがはっきりしない気がする。結果的に自分で決めるときは特に強い理由がなければ避けて、もう決まってる時は実装するという流れになっている。
Sierra で SandS
いくつかのリマッパーを試したけど、Karabiner on El Capitan での挙動を再現することができなかった。 自分の設定がよくないのか、そういうもんなのか分からないながらも「本当に Space だけを押した時のみ Space になり、Alt-Space や Control-Space が使えなくなる」もしくは「上手く動かない」という状態で、なんとか上手いことやれないか試していたものの諦めてSandS専用のアプリを作った。
Assets/ に DLL をほうりこむ時
$ xbuild X/X.csproj
とかってやって作った DLL は、Unity のエディタに D&D して取り込んだ場合は問題なく使えるが、 Unity とは関係のないところでコピーすると Unhandled Exception: System.Reflection.ReflectionTypeLoadException: The classes in the module cannot be loaded.
と言われて使えない。
なんでそうなのかはよく分からんけど、git submodule で管理してるやつをビルドして Assets/ にコピーしてということをコマンドでやりたいのでそれはちょっと困る。
こうやって作ると良かった。
unity-dll.sh:
#!/bin/sh UNITY=/Applications/Unity/Unity.app/Contents $UNITY/Mono/bin/smcs -r:$UNITY/Managed/UnityEngine.dll -r:$UNITY/Managed/UnityEditor.dll -out:$1 -target:library $2
$ ./unity-dll.sh Foo.dll Foo/*.cs
Unity - マニュアル: マネージド プラグイン に書いてあったけど、ここに書いてるのとは UnityEngine.dll の場所が微妙に違う。自分のところでは Contents/Frameworks/Managed/UnityEngine.dll
ではなく Contents/Managed/UnityEngine.dll
にあった。
勝手に空いてるポートでやってほしい
docker-compose:
version: '2' services: web: build: . ports: - $PORT:3000
こういうのがあった時に、どこが空いてるか考えて PORT=3033 docker-compose up
とするのが面倒。
かといってハードコードしてしまったら被った時にさらに面倒。なので勝手に空いてるところでやってほしい。
randport:
#!/usr/bin/env ruby require 'socket' s = TCPServer.open(0) port = s.addr[1] s.close STDOUT.puts "randport: PORT=#{port}" exec("PORT=#{port} #{ARGV.join(' ')}")
これで楽
$ randport docker-compose up randport: PORT=53450 ...
追記
@ToQoz これ、 0:3000 ってやればできるよ https://t.co/H9jEw2YDVp
— Daisuke Murase (@typester) September 20, 2016
とても間抜けなことをやっていたことに気付き、赤面。
とても楽。とても良い
3DCG手習い
学習/馴れるコストが異様に高いというイメージを持っていたけど意外とそんなことはなくて、1日やってみたらちょっと満足感を得られる程度には形になった。思ったようなものを作れるまでかなり遠いもののこの調子だと適度に続けられそうな気がする。
sculptris で粘土をこねる。
blender に import してボーンを設定する。色んなポーズが取れるようになる。
動かしてみた。
見たもの
- sculptris 入門-introduction (Japanese) on Vimeo
- 【Blender】Sculptrisで作成したモデルをBlenderへインポートする | ユガラボ
- 【Blender】メッシュにボーン(Armature)を入れてポーズ変更できるようにする | ユガラボ
- 【Blender】ボーンを動かしてアニメーションを作成する | ユガラボ
操作の解説が分かりやすくて、自分の環境で一部のショートカットが効かなくて困ったこと以外は結構スムーズにいった。
気軽な playground
gopwt 用の play.golang.org みたいなのを作りたいなと思っていた。だからといって、入力されたコードをサーバーで実行しても大丈夫なような環境を作るのも面倒ということで、gopwt で変換して、それを、gopherjs で javascript にして、ブラウザで実行してもらえば良いというとても気軽な案を思いついたら結構うまくいっている。そのうち play.gopwt.org 的なところで動かす予定。
テストコードが対象になるので、go test
がやっているような main.main を作ってやらないといけないんだけど、それは適当に手を抜きつつこんなテンプレートで作ってる。
testmain.go:
package main import ( "testing" ) var benchmarks = []testing.InternalBenchmark{} var examples = []testing.InternalExample{} var tests = []testing.InternalTest{ {{range .}} { "{{.}}", {{.}}, }, {{end}} } func main() { match := func(pat string, str string) (bool, error) { return true, nil } testing.Main(match, tests, benchmarks, examples) }`
gopwt につけようと思っている expected と got の diff
github.com/sergi/go-diff/diffmatchpatch を使う。
なんか良さげな基準で文字単位か行単位かを切り替えたい。power assert は何も考えずに assert 書いとけばいい感じの出力が得られるというのが嬉しいところなので、オプションはつけたくないということで考えた。
このような用途に限って言うと、すごく似てるけどちょっと違ってどこが違うのが見つけるのが辛い時だけに文字単位の diff があったらよくて、どう見ても違うみたいなケースでは必要ないだろうという予想があって、まず文字単位で diff をとって、複数行にまたがる diff があったら行ごとの diff をとり直すということにしようかなと思った。
あと副作用的に、複数行にまたがらなければ [- -]
{+ +}
みたいなマークなしで色だけで ins/del 表現してもそんな分かりにくくなくて良さそうだった。
文字単位で嬉しい
word-diff:
foo(x("a[-'-]{+"+}) bar
line-diff:
- foo(x("a') + foo(x("a") bar
行単位でいい
word-diff:
fo{+x bo+}o bar ba[-r2-]{+z+}
line-diff
- foo + fox + boo bar -bar2 +baz