Hamlit v2.0をリリースしました & RubyKaigi登壇します

Slimより高速なHaml実装「Hamlit」

RubyでHTMLを生成するのにERB以外でよく使われるテンプレート言語にHamlSlimがあります。haml *1 をやめて高速なslimに移行する人が多かったのですが、私はHamlのシンタックスの方が好きなので、slimが用意したベンチマークでslimより高速なHaml実装「Hamlit」を3月にリリースしました。 *2

これはslimが提供しているベンチマークでHTML escapeを有効にし *3、FamlとHamlitを追加したベンチマーク結果です。なおHamlitは完全にHaml互換の仕様ではなく、この非互換が有利に働いています。

互換性と性能が大幅に向上したHamlit v2.0

Hamlitの互換性の問題

Hamlitは最初のv0.1の時点で上記のようなベンチでSlimより高速ではあったのですが、以下のような欠陥がありました。

  • HTMLエスケープがない
  • エラーがコンパイル時に出る & ランタイムで出ても行番号がズレる
  • コーナーケースで属性のレンダリングが重複したり、消えたりする
  • 最適化の影響で、書き方が静的か動的かでHTMLのレンダリング結果が変わってしまう
  • 空白除去オペレータの挙動がおかしい

社内の20,000個以上のテストケースがあるRailsアプリで使ってみたら5,000個くらいのテストが落ちる状態でした。 以前、これを地道に潰しながら致命的な仕様の見直しを行ったHamlit v1.0.0をリリースしました。v1のどこかのバージョンでそのアプリの全てのテストが通っています。

それでも上記の問題が完全に潰せていたわけではなく、保守性的にも様々な問題を抱えていたため、2ヶ月かけてフルスクラッチを行い、大幅に互換性を改善したHamlit v2.0をリリースしました。

もう一つのHaml実装「Faml」との戦い

Hamlitより先にリリースされたhamlより高速なHaml実装としてFamlがあります。 slimが提供しているベンチマーク(をFamlが公平に参加できるようにしたもの)ではHamlitの非互換性が有利に働きHamlitの方が高速に動作しますが、その他の多くのベンチマークではC拡張を持っているFamlの方が高速に動作する状態でした。

Famlが恣意的に用意したベンチマークでは、1.35倍程度Hamlitの方が遅い状態でした。 さて、 v2.0ではHamlitもC拡張を用意 してC言語レベルでの最適化を行い、上記のような動的なテンプレートでも以下のようにHamlitの方が高速に動作するようになりました。 (Travisでの結果)

      hamlit2 v2.0.1:    24225.3 i/s (0.041ms)
         faml v0.7.0:    19405.8 i/s (0.052ms) - 1.25x slower
      hamlit1 v1.7.2:    10450.2 i/s (0.096ms) - 2.32x slower
  haml v5.0.0.beta.2:     5260.5 i/s (0.190ms) - 4.61x slower

でも昨日FamlがC++になって速くなってるので、明日には抜かれているかもしれません。

静的解析エンジン

FamlはRubyの式が静的であればコンパイル時にレンダリング結果を確定させる機構を持っているのですが、Hamlitのそれはかなり貧弱のため、そういったケースでもレンダリング速度で劣っていました。 真面目に実装しなおしたため、上記のテンプレートの値をFamlやHamlitが解析可能な位置にズラしたベンチマークでは、 本家hamlの569倍 の速度が出るようになりました。 (Travis)

      hamlit2 v2.0.1:  3639877.1 i/s (0.000ms)
         faml v0.7.0:  1434853.5 i/s (0.001ms) - 2.54x slower
      hamlit1 v1.7.2:    17734.8 i/s (0.056ms) - 205.24x slower
  haml v5.0.0.beta.2:     6388.2 i/s (0.157ms) - 569.78x slower

コンパイル速度の問題

これまで出てきたのは全てレンダリング速度に関するベンチマークですが、本来はコンパイル速度も全体のパフォーマンスに影響し得ます。 Webサービスではあるテンプレートのコンパイル回数に比べてレンダリング回数の方がかなり多くなるためコンパイル速度は無視できますが、Hamlitはコンパイルが非常に遅く、レンダリング回数がそれほど多くないタスクで使うには必ずしもHamlitをオススメできない状態でした。

これはhamlのリポジトリにあるstandard.hamlという少し大きめのテンプレートですが、コンパイル速度でFamlに勝てるようになりました。(Travis) コンパイル時に最適化をあまり行わないhamlにはまだ勝てていません。

  haml v5.0.0.beta.2:      297.5 i/s (3.361ms)
      hamlit2 v2.0.1:      152.6 i/s (6.551ms) - 1.95x slower
         faml v0.7.0:      139.4 i/s (7.173ms) - 2.13x slower
      hamlit1 v1.7.2:       78.0 i/s (12.827ms) - 3.82x slower

RubyKaigiでどうやって速くしたか話します

RubyKaigi 2015に、Famlの作者である @eagletmt さんと一緒に登壇します。一つの枠で二人のアイデアが聞けるのでお得です。 どう速くしたのかの詳細はあえてこの記事では書きませんでしたが、何を考えて作ったのかとかも含めRubyKaigiで話す予定です。是非聞きに来てください!!

*1:言語に関してはHaml、gemに関してはhamlで表記します

*2:Hamlit v0.1.0リリース時の記事: http://k0kubun.hatenablog.com/entry/2015/03/31/004021

*3:元のベンチではSlimもERBもRubyのスクリプトに対してHTML escapeを行わないオペレータを使っているんだけど、HamlにはHTML escapeされるオペレータを使っているのでSlimやERBでもHTML escapeされるように直してる。HTML escapeしない側に倒さないのは、ベンチに加えたいFamlにはHTML escapeしないオプションがないのと、Railsの標準だとescapeされるため。

byebugやpry-byebugを使った後の挙動を10倍高速にしました

byebugとpry-byebugのbundle updateをしましょう

byebugはRails標準でインストールされるRuby 2.1, 2.2向けのデバッガで、pry-byebugはpry *1 にデバッガの機能を追加するpryのプラグインです。 一昨日から今日にかけて、以下のパフォーマンス改善を含む byebug v8.0.0 と pry-byebug v3.3.0 がリリースされました。

github.com github.com

byebugとpry-byebugには、一度byebugbinding.pryを叩くとそれ以降ずっとアプリケーションの挙動が10倍遅くなるという問題がありました。 これが上記の変更により改善されたので、 Railsアプリやgemのデバッグにbyebugやpry-byebugを利用している方はそれらのbundle updateをおすすめします。

binding.pryすると10倍遅くなる問題

Byebugは行の移動やブレークポイントで処理を止めたりするため、Ruby 2.0から導入されたTracePoint *2 *3 というRubyのVMで起きるイベントに処理をフックさせるためのAPIを使っています。

byebugとpry-byebugはbyebugbinding.pryを叩いたときにByebug.startというメソッドの中で必要なTracePointを設定することによってデバッグを可能にしているのですが、 いままでのbyebugやpry-byebugはデバッグ終了後にデバッグ用のイベントフックを無効にしていなかった ため、一度byebugbinding.pryをするとそれ以降どんなアプリでも10倍程度遅くなってしまう状態でした。

github.com

なぜそんな状態だったのか

byebugとpry-byebugの作者はこの問題とどうしたら直るかを認識していたのですが、以下の理由により簡単には解決できない状態でした。

  • TracePointを無効にするためのByebug.stopがバグっていて、使うと大量にテストがfailする
  • TracePointを複数設定した後それらを無効にすると、freeしたTracePointを参照してしまうRuby自体のバグがありランダムにSEGVする

これらの問題をgdbを使って地道にデバッグを行って原因を特定し冒頭に載せたパッチによって問題が解決したため、デバッグ終了時にByebug.stopを呼ぶことが可能になりパフォーマンスが大幅に改善されました。なお、Ruby自体のSEGVに関しても原因を特定しパッチを投げています。

github.com

注意点

byebugやpry-byebugにはブレークポイントを設定する機能があるのですが、それを設定している間はByebug.stopしない(したらブレークポイントが動かなくなる)ためブレークポイントを使っていると遅いままになります。解除すればまた速くなります。

なお、Byebug.stopされているかはアプリケーション上でByebug.started?falseを返すかを見ることで確認することができます。 falseが返れば高速に動作しています。

所感

直すの結構大変だったので是非最新のbyebugとpry-byebugを使っていただきたい気持ちです。

ISUCON5予選に「railsへの執着はもはや煩悩の域であり、開発者一同は瞑想したほうがいいと思います。 」チームで参加してきた #isucon

学生枠で出場した去年に引き続き、今年は新卒社員になった@cnosuke@rkmathiと僕@k0kubunの3人でISUCON5予選に出場した。
僕らが最後に確認できたスコアは11045で、その時のスクショだと2日目の16位という感じで、予選通過はなりませんでした。

id:rkmathiの記事: http://rkmathi.hatenablog.com/entry/2015/09/28/004734

戦略

最初にコードは読まず、nginxのアクセスログをパースして集計する奴とnewrelicを使ってベンチ中最も時間を使っているエンドポイントを特定し、そのエンドポイントの中でどこが遅いかはrack-lineprofで把握してそこだけ読んで潰していく感じでやった。
去年の感じだと予選はいきなりキャッシュとか入れなくても落ちついて普通のチューニングをしていれば突破できると思っていたので、地道にSQLのチューニングをしていた。

僕らのチームがやったこと

時系列で並べるのが好きなのでそれで書く。
cnosukeがインフラレイヤーをやって、rkmathiがMySQLのログ集計とかインデックス直しをやって、僕がクエリ変形させたりひたすらN+1を潰すのをやっていた。
他は思いついた奴を出しあって手があいてる人がやる感じだった。

score やったこと
200 Rubyの初期状態(大体)
800 nginxで静的ファイル配信
926 relationsのINSERTをone < another保証にしてクエリの条件減らした。created_atのソートを全てidにした。
1192 footprintsにインデックスを貼った
1264 InnoDBのバッファのサイズを上げた
2947 relationsのoneとanotherにインデックス貼った。セッションのクッキーストアの暗号化をやめた。
6202 "/" へのリクエスト用にis_friend?のN+1用preloader (JOINではなくクエリ2つで引く)を書いた。entriesのレスポンスをRedisにキャッシュするようにした。
9557 LIMIT 1000を10個に絞るクエリ2つを両方LIMIT 10で引き、commentsのSELECTのrowsが減るインデックスを貼り、Redisのチューニングをした
9610 firstしてる系にLIMIT 1を付加
10194 friend idsを引くN+1をなんとかした
7200 再起動しただけで点数が減った
7963 N+1になるget_userの汎用的なpreloaderを書いて適用
8402 ベンチを回す度に点数が回復していた
10000 friend idsを引くクエリをRedisのsetに変えた(けどこの辺でボトルネックがIOになっていて、期待よりスコアが伸びていない)
11045 遅いエンドポイントのビューの中のget_userとかのN+1を潰した。

最初に10000点まで行くのは結構早くて順調だったんだけど、RedisとMySQLのウォームアップがちゃんと書けず、CPUもメモリも使いきらない状態のまま提出となった。
この辺の経験不足が敗因なのかなあと思っているのと、正直このラインまでやれば予選抜けられると思っていたので、レベルが上がってるような気もした。

感想

めちゃくちゃ楽しかったし、ずっと集中しててすごい疲れた。去年は学生枠でギリギリ本戦に出してもらった感じだったけど、今年は去年よりちゃんとチームで連携してスコアが上げられて、メンバーの成長が感じられてよかった。
今年結構テンプレートエンジン周りを触ってて、その辺練習の時は最適化とかやってて今回の問題でも直せるところあるなと思ったけど、本番ではそのレイヤーにボトルネックが移せなかったのが悔しかった。

次から次へと直すところが出てくる面白い問題で、出題や運営をしてくださった方々には感謝ばかりです。是非来年も参加したいです。

#tqrk09 で「Rubyを使った開発環境構築の自動化」についてLTしてきた

TokyuRuby会議09でLTした

あんまり外部でRubyについて話したことなくて、こないだshibuya.rbでactiverecord-precountについてLTしたけど、それ以外にもまだ紹介したいgemがあったのでTokyuRuby会議にLT応募したら通ったので話してきた。

rebuild gem

github.com

OSXでgitのインストールとdotfilesのgit cloneとその中のスクリプトの実行までやってくれるgem。一度セットアップしたあとは、新しいのをgit pullしてきてgitconfigで指定しておいたスクリプトを走らせるのに使う。僕のgemの中だと結構ダウンロードされている奴で、今でも普段普通に使っている。

実行するスクリプトにRubyのライブラリ使うとメンテがダルいみたいな話をしたんだけど、適材適所だと思うので、どちらかだけ使っていれば良いみたいな話でもないとは思っている。あるいは自分が使ってて楽しいgemを使うのが良いと思う。

AppleScriptでGUI操作してるんだけど、boxenとか見ると別のやり方もすでにあったので、Macで自動化をするときは先にboxenとかを見ると良い。

karabiner-dsl gem

github.com

これはMacのキーの設定をするKarabinerというユーティリティのXMLの設定ファイルを吐き出すRuby DSL。日常的に頻繁にキーバインドをいじる人間なので、これもよく使っている。これを使って普段どんな最悪なキーバインドを設定しているかを紹介した。やめたい。

エディタ何使ってるんだろうみたいなツイートを見かけたけど、vimを使っていて、vimのキーバインドを潰してしまっている部分はvimrcで別のものに設定している。とにかく最悪という感じがする。

RubyのDSLでキーバインドの変更履歴を管理したいみたいな人には向いてると思う。

TokyuRuby会議に初参加した感想

スポンサーついててビールが無限に出てくるのやばい。あと持ち寄りの料理もすごいおいしかった。おいしいもの飲み食いしながらいろんな人と話せて最高の会だった。来年も参加したい。

ElectronでYoruFukurou風のTwitterクライアントを作った

f:id:k0kubun:20150824200844p:plain

最強のTwitterクライアント戦争

なんか戦争をやってる人たちがいたので乱入することにした。

YoruFukurou風のTwitterクライアント

最近デスクトップでLinuxを使い始めたんだけど、YoruFukurouみたいな感じで使えるTwitterクライアントがなくて困っていた。

なので、YoruFukurouを再実装した。Mac環境で本家を置き換えようとは思ってなくて、あくまでWindowsとかLinuxとかでYoruFukurouを使いたい人向けに作っている。

github.com

デモ

インストール方法

https://github.com/k0kubun/Nocturn/releases

からzipでダウンロードできるようにしてある。
これからカスタマイズするつもりはあるけど、現状はパクりアプリなのでApp Storeに出す予定はない。

実装されている機能について

下記のショートカットで使える機能と、画面に見えてるボタンの機能が実装されている。

Key Command
Enter ツイートの投稿、リプライ先の選択、検索実行
j, ↓ 次のツイートを選択
k, ↑ 前のツイートを選択
0, space 最初のツイートを選択
f お気に入りに追加
Cmd-backspace ツイートを削除

自分がよく使う機能しかまだ実装してない。面倒そうなのもちょっと省いているけど、8割くらいのユースケースは満たせる気がする。

なぜNocturnなのか

中二病です

Electron使ってみた感想

僕はJavaScriptの文法を知らない(CoffeeScriptを書く)くらいフロントエンド力が低いけど、Electron使ったことない状態から始めて1〜2日で完成したのでかなりわかりやすくて使いやすいと思う。 HTMLとCSSとJavaScriptをちょっと書くだけでデスクトップアプリがサクサク作れるのでめちゃくちゃ楽しい。みんな絶対やったほうがいい。
あと、Macで開発してたけどWindowsとかLinuxで起動したら普通にうごいた。すごい。

というわけで、Electron触ってみたい人はぜひNocturnをいじってみてください。

PCを自作してArch Linuxを入れた

f:id:k0kubun:20150816180540p:plain

社会人になって経済的に余裕ができてきたので、はじめての自作PCに挑戦することにした。 入社直後に生活が苦しすぎてヤフオクでiMacを売ってしまったのでデスクトップPCが欲しかった。

材料

種類 型番 値段
CPU Intel Core i7 4790K (4 core / 8 threads, 4.0GHz) ¥39,500
マザボ ASRock Z97 Extreme4 ¥12,980
メモリ CT2KIT102464BA160 (DDR3-1600, 8GB x 2) ¥13,999
SSD CT120BX100SSD1 (120GB) ¥8,277
DVDドライブ iHAS324-17 ¥1,780
電源 KRPW-PT600W/92+ (600W) ¥9,980
PCケース CMS-693-KKN1-JP ¥11,800

合計98,316円。前日が給料日だったのでうっかり手が滑って高いパーツを集めてしまった。 家賃を振り込んだのと持株会にフルベットしているのと合わせて今月の給料が1日で蒸発した。 困ったなぁ〜

歩くのがめんどくさかったので全部秋葉原のツクモeXで買った。全部手で持ち帰ったんだけど死ぬほど重くて、帰りに山手線のホームを間違えたけどそのまま目黒まで逆周した。全部持ったまま体重計に乗ったら 91kg あった。なお本人の体重については不詳。

組み立て

完全に素人であることが店員に一発で見ぬかれ「初めての自作PC講座」を渡されたので、これを読んで作った。この冊子につくもたんが出現するのは表紙だけで、中はマザボの写真とおっさんのイラストしか出てこない。

CPUの取り付け

なんかハンダ付けとかしないといけないと思ってたんだけどマザーボードの上にのせてフタを閉じるだけだった。なんでこれで動くのかよくわからない(本当はグリスとかする気がする)

メモリと電源を挿して動作確認

メモリと電源を挿してディスプレイつないでスイッチを押すともう電源がつく。一回つくと以降大分安心して作業できる。

PCケースに取り付けて配線

PCケースにマザボを取り付け、入りそうなとこに適当にSSDとDVDドライブと電源をいれる。刺さりそうなピンに勘で適当にケーブルを挿していき、動作するまでの回数を競うゲーム。

完成

真面目に配線したら一発では動かなくて、よくわからんけど似たようなとこに適当に付け替えたら動いた。やはりエンジニアに必要なのは運命力。

Arch Linux

どうやら僕に常識が欠如しているのはLinuxに慣れてないかららしいので、Linuxをいれることにした。

某所のサーバに使われてるubuntuと迷ったんだけど、常識を知るために触るならユーザーが全部自分でやらないといけないディストリビューションのほうがいいだろうと思ったのでArchを選択した。実際デフォルトだと何も入ってなくて、これ動かすのにはこれが必要なんだみたいなのが知れて面白い。

セットアップ時にやった作業はQiitaに残した。
http://qiita.com/k0kubun/items/e60fae90c688ac960ab7

感想

休日にコード書くのをやめてたまにはPCを組み立てるなどの運動をすると楽しいし健康に良くて最高

markdownからkeynoteを生成する「md2key」を作った

markdownからkeynoteを生成する奴作った

なんか昨日社内でLT頼まれててLTしたんだけど、前日に資料を作るモチベが沸かなすぎて資料を作る奴を作ってしまった。

使い方

  1. gem install md2keyでmd2keyをインストールしておく。
  2. キーノートを開いて好きなテーマを選んで新規作成。
  3. 一つ目のスライドが表紙のスライドになるので、好きなレイアウトのスライドに変える。
  4. 二つ目のスライドがコンテンツのスライドのレイアウトに使われるので、使いたいレイアウトのスライドを追加する。
  5. md2key **.mdを叩く。

markdownのフォーマット

# The presentation
@k0kubun

## Hello world
- I'm takashi kokubun
- This is a pen

## How are you?
- I'm fine thank you

みたいな奴が動く。最悪なことに現時点ではここに出てこない記法は全く対応してない。

Deckset互換性

giginet.hateblo.jp

僕がMarkdownからプレゼンを生成するときはいままではDecksetを使っていたので、最終的にはDecksetの記法を全部実装したいという気持ちである。(理想)

なので、---でスライドを区切るみたいな書き方に対応したい。でも---は不要では?と言われたので、現状は内部的には見出しでスライドを変えて---はただ無視している。

けど僕としては普通のMarkdownがいい感じに変換されることよりDecksetとの互換性のほうが重要度が高いので、いつか見出しが複数あるスライドを許容して必ず---で区切るようにするかもしれない。

どういう仕組みなの

2ヶ月くらい前も同じことを考えていたんだけど、AppleScriptでGUI操作してなんとかするというあまり現実的ではない && Keynoteを起動しなければならないダサい方法しか思いつかなかった。

絶対にAppleScriptを書きたくなくて、その日は https://github.com/obriensp/iWorkFileFormat を読みながらKeynoteファイルの中身を見ていたけど、xmlだったころのKeynote '09はともかく最新のKeynoteのフォーマットは生成するの大変だなあと思って諦めていた。

結局AppleScriptからKeynoteをGUI操作することにした

今週社内ですごいかっこいいKeynoteテンプレートが公開されて使いたくなったけど、GUI操作だけで1から資料作るの死ぬほどめんどくさいし本当にやりたくなかったので、もうこれはAppleScriptでなんとかするしかないと思った。

あまりにもKeynoteを操作したくなくて key2txt というmd2keyとは逆のことをやってる奴を見てたら、なんかTaskPaperというフォーマットからKeynoteを生成するスクリプトがあって、その中にスライドのタイトルとコンテンツにアクセスするコードと新しいスライドを作るコードがあったので、そのコードだけを組み合わせてmd2keyを作った。

作るの(AppleScriptの書き方調べるのが)めんどくさそうと思ってたけど結局このコードを見つけてから2時間くらいで書けてしまった…

markdownパーサ

Rubyでmarkdownをパースして(HTMLではなく)いい感じのASTを生成してくれるGemを探してたんだけど、たいていのmarkdown処理する奴はHTMLまで変換してしまっていて、そうじゃないのはRDoc::Markdownしか見つからなかった。RDoc::Markdownのパース結果がなんか使いづらそう(あんまちゃんと調べてないです)だったので、最初小学生並みのマークダウンパーサを書いた。

エゴサしてたら「*でリストかけないじゃん」という意見を見つけたので、マークダウンパーサ書くのしんどいし最終的にはRedcarpetからXHTMLに変換してOgaというXMLパーサを使って無理やりASTにした。

今後追加したい機能

  • 太字
  • リストのネスト
  • シンタックスハイライトされたコードの挿入

実装が簡単そうな順。プライオリティは逆順。これの開発にそこまで興味があるわけではないのでいますぐ拡張しようというモチベはなくて、次に自分が資料作りで困ったときに拡張すると思う。