読者です 読者をやめる 読者になる 読者になる

k0kubun's blog

railsへの執着はもはや煩悩の域であり、開発者一同は瞑想したほうがいいと思います。

Linux デスクトップ環境 2017

Linux デスクトップ環境 2016 - eagletmt's blogの人に影響を受けて自作PCでLinuxデスクトップを使い始めてから約1年半が経ち、僕の使う環境が一通り満足な状態になったので今どういう構成なのか書いておく。

僕はKeynoteを使う時とか会社のマシンでmacOSも割と使う都合、基本的に操作性がmacOSに近くなるようにしているので、macOSからLinuxに移行したい人の参考になるかもしれない。 *1

そもそも何故Linuxデスクトップを使っているのか

「苦労してmacOSに近づけるくらいなら最初からmacOS使えばいいじゃん」と言われそうだが、今この瞬間は大体以下の理由でLinuxデスクトップを使っている。

  • 趣味で作ったスペックが高めの自作PCmacOSが入れられない *2
  • 最新のmacOSではKarabinerが使えないが、Linuxでは自作のキーリマッパーが使える
    • Kababiner-Elements+keyhacに移行したけど、keyhacはたまにリマップされないことがあり困る
  • 仕事でサーバー用途にLinuxを使うことが多いのでLinuxに触る機会を増やしたい
    • パッケージ管理システムとか、strace, perfみたいにmacOSでは使えないツールの話
  • MacBookが高いので今後ノートPCを買う時にThinkpadとかにしたい

現在のLinuxデスクトップ環境

ディストリビューション: Ubuntu GNOME 16.04

去年はずっとArch Linuxを使っていたんだけど新年になってから気分転換にUbuntu GNOME 16.04を使っている。上述の「サーバー用途にLinuxを使う」勉強のため、サーバーやDockerによく使っているUbuntuにしている。

ArchはArchWikiが充実してるのと常に最新のパッケージが降ってくるのが便利だけど、パッケージをアップグレードするとたまに壊れる。Ubuntuはそこは安定しているイメージ*3だが、その分パッケージで普通に入るソフトウェアが基本的に古いので開発者にとっては多分Archの方が使ってて楽しい。

デスクトップ環境, ウィンドウマネージャ: GNOME, Mutter

普通のUbuntuだとデスクトップ環境としてUnityが入るんだけど、compizのウィンドウ切り替えがあまりにも遅くて苦痛だったのでGNOMEにした。ディストリビューションUbuntu GNOMEなのはそのため。

タイル型WMに興味があったのと動作が軽いので最初の半年くらいデスクトップ環境を使わずawesomeやxmonadを使っていたけど、僕はターミナルを半透明にしてTwitterとブラウザに重ねてVimとshellとTwitterとブラウザを同時に見るということをやっていて、ウィンドウを重ねるのがタイル型WMと相性が悪いのでやめた。

動作の軽さで言うとデスクトップ環境にxfceは試したことがあるが、僕は4KディスプレイをHiDPI(x2.0)で使っていて、HiDPIの対応がGNOMEに比べ微妙なので使っていない。GNOMEは綺麗かつまあまあ軽快なのでおすすめ。

使っているGNOME拡張 macOS風にするためにいくつかGNOME拡張をいれている。

  • Dash to dock: Dockを出しっぱなしにする
  • GNOME Shell Frippery: 日付と時刻を右端に出す
  • Panel osd: 通知の表示位置を右上にする

キーリマッパー: xmodmap, xremap

ErgoDox以外のキーボードを使う時にキー単位のリマップはxmodmapを使っていて、キーの組み合わせのリマップにはLinux向けの最強のキーリマッパーを作った - k0kubun's blogに書いたxremapを使っている。

xremapを使ってターミナル以外の場所でのEmacsバインディングを有効化*4していて、これがとても捗る。普通に設定するとデフォルトのC-a(全て選択)等が潰れてしまうが、macOSのCmd-aのようにAlt-aをC-aとして使っていて、大体macOSと同じように操作できる。xremapはDSLがシンプルなのでそういう複雑な設定を書きやすいのが利点だと思う。

ターミナル: rxvt-unicode

最初にrxvt-unicodeを選んでから他のを試していないけど、macOSでiTermを使っていた時に比べると高速に感じるし、特に困ることがないのでそのまま使っている。

日本語入力: ibus-skk

Linuxデスクトップを始めた時にuim-skkが何故か動かなくてworkaroundとしてibus-skkを選択しそのまま使っている。ibusはたまになんか困るような気がする*5けどまあAquaSKKとかと同じ感覚で普通に使える。

ファイラ, ビューア, スクリーンショット: Nautilus, eog, gnome-screenshot

特にこだわりがないのでGNOMEに標準添付のものを使っている。スクリーンショットImageMagickimport(1)を使うと画像に枠線が入ってしまうことがあるのでそれはやめた。

フォント: Ricty Diminished

ArchだとRictyを使っていたが、UbuntuだとRictyを入れるのが面倒なのでRicty Diminishedを使っている(雑)。4KディスプレイかつHiDPIだとフォントに関わらず文字が綺麗に見えるので金パワーは便利。

ブラウザ: Google Chrome

ほとんど説明不要だと思うけど、インストールが楽なchromium-browserを使っていないのは、「論理行単位の移動」を使うためにgtkrcのparagraph-endsを活用したくて.gtkrc-2.0が使える方を選んだため。

Twitterクライアント: Nocturn

LinuxだとYoruFukurouが使えないので、ElectronでYoruFukurou風のTwitterクライアントを作った - k0kubun's blog *6に書いたNocturnを使っている。自分好みに作ったので全OSで常用していて、そこそこメンテしている。

パスワードマネージャ: 1Password

他にLastPassとKeePass(X)を微妙に試したんだけど、今節約中なのでLastPassに課金したくなかった*7のと、KeePass(X)はHiDPI対応が微妙で1Passwordと比較して特にメリットがなかったので、結局使い慣れている1Passwordを渋々wineで使っている。インストールは面倒なので自動化してある

ブラウザ拡張も動くしDropboxでVaultを同期すればまあ普通に使えるが、挙動が遅めなのでLastPassへの移行も考えている。

音楽プレーヤ: Google Play Music

iTunesから移行した。たまたまiTunes Storeで買ってた曲が全てm4a(DRM free)だったので、曲のフォルダをまるごとアップロードするだけで移行が完了した。iTunes Matchと違って50,000曲まで曲のアップロードが無料だし、iOSアプリのクライアントも使いやすいので普通に便利だと思う。

気持ち

UbuntuとかGNOME使ってて大分ミーハーな感じだけど普通に使いやすい。MacBook高いしやめたいと思っていて、iOSアプリ作るみたいな用事もないみたいな人はLinuxデスクトップは試す価値があると思う。

*1:実際の設定手順は http://qiita.com/k0kubun/items/d2359ad51cf1cf783f4d に書いてメンテしている。自動化しない方が運用コストが低いものは手で入れている。

*2:スペックが高いので普段使いしたいが、自作している以上Appleのハードウェアではないので利用規約macOSを入れてはいけないという話 id:otituke

*3:Ubuntuを使ってる期間はまだ浅くて実際はどうなのか知らないので、あくまでイメージ

*4:ちなみに、C-aがC-Alt-aになっているのは、これを単にHomeにすると表示行移動になってしまうからで、gtkrcと連携して論理行移動できるようにしている dotfiles/.gtkrc-2.0 at 98aede78a0dc2c30f24d34e1452498d0d1f7fe85 · k0kubun/dotfiles · GitHub。これをgtkrcだけでやると、例えばC-kがGitHubに奪われるので、一度Altつきにリマップすることでそれを回避している。

*5:ちゃんと現象と原因を分析できてないので書くのは控えておく

*6:当時の記事と違い、今はデザインはオリジナルになっている

*7:1Passwordは既に買い切りのライセンスを持っている

2016年にやったこと

クックパッドで働くのは4年目、社会人としては2年目になった。2015年にやったことと同じフォーマットでまとめておく。

発表

今年は6本発表した。去年RubyKaigi前後にいろいろ集中してて死にかけたので、2か月に1回というのが僕にとってはちょうど良いペースだと思う。

RubyConf 2016

今年は海外のカンファレンスで登壇してきたというのが一番大きいと思う。英語は一応どうにかなったけど、うまい表現ができずもどかしいことがあるのでもうちょっとマシにしたい。あとこの成果で初めてクックパッドの業務にmrubyが導入されたように思う。

RubyKaigi 2016

100%クックパッドの業務時間で作ったOSSを題材に、今年は1人でRubyKaigiに登壇した。Barbeque自体はまだまだ改善点があるものの、ECSを活用してジョブ単位のオートスケールができ、マルチテナントで運用コストが低いシステムを作って実際に導入されるところまで持っていけたのはよかった。

Cookpad TechConf 2016

今年から弊社も社の技術カンファレンスをやるようになったので、アルバイト時代からずっとやりたいと思っていた仕事とその成果について話した。クックパッドの開発基盤グループにいると、難しい問題のデバッグや高速化にじっくり取り組めるのが楽しいと思う。

Rails Upgrade Casual Talks

大変ありがたいことに、発表しませんかというお誘いを初めていただいた。Rails 5が出る前だったので、cookpad.comのRails 4.1→4.2の話をした。

How to safely upgrade Rails // Speaker Deck

TokyuRuby会議10

Rails 5が出たのに合わせて、僕が送っていたパッチの紹介をした。東急は高速で喋っても誰にも怒られないのが楽しい。

My patches for Rails 5 // Speaker Deck

Roppongi.rb #2

toshimaruさんにお誘いいただいてMItamaeについて話した。何かOSSを作って、便利さを発表して、いろんな人に使ってもらう、という体験はとても楽しいので今後も続けたい。

MItamae Hacking Guide // Speaker Deck

かなりどうでもいい余談 今まで使った発表資料azusa colorsの色を全て使い切った。東急でやったけど、資料全体で赤とかを使うのは大分激しいのでちょっとやめた方がいいと思う。

ホッテントリ

ブログは1ヵ月に1記事くらい書くのを意識しているけど、はてなブログが11記事、Qiitaが6記事という感じだった。「発表」と重複があるけどブクマを貰えたのは以下の通り。

タイトル
1. 安心してRailsアップグレードを行うための工夫 - クックパッド開発者ブログ
2. Coffee, jQueryで書いていたElectronアプリをES6, React, Reduxで書き直した - k0kubun's blog
3. Linux向けの最強のキーリマッパーを作った - k0kubun's blog
4. Railsアプリ開発環境の高速化 // Speaker Deck
5. RubyなしでItamaeレシピを実行できる「itamae-go」を作った - k0kubun's blog
6. SSEを使ってHTMLエスケープを高速化してみた - k0kubun's blog
7. Itamaeのmruby実装「MItamae」が大体いい感じになった話 - k0kubun's blog
8. Scalable Job Queue System Built with Docker // Speaker Deck

いまだに400 users越えたことないので何かもっとバーンて感じの成果欲しい。

OSS活動

今年リリースしたOSS

Starだけ見ると去年に比べるとウーンという感じ。mitamaeを使ってくださっているみなさまありがとうございます。

Star リポジトリ
★65 k0kubun/mitamae
★64 cookpad/barbeque
★48 k0kubun/xkremap
★46 k0kubun/itamae-go

今年は↑でmruby, Docker, Go, Xlibあたりを触っていて、他にhescapeSIMDプログラミングで少し遊んだのと、もうちょっとC言語に強くなるためにCでCコンパイラを実装するのを途中までやり、そこでLLVMも触った。

今年活発にメンテしていた既存OSS

Star リポジトリ
★558 k0kubun/md2key
★407 k0kubun/Nocturn

md2keyは、ずっと欲しかったけど実装できなかったnested listが作れたのが大きい。あとプルリをいただいて、シーケンス図、表、発表者ノートなどがサポートされた。 Nocturnはデザインを変えReact/Redux化してから300くらいStarが増えた。既存の設計を真似たことでメンテが楽になった。

毎年新しいプロダクトをリリースしていきたいと思っているけど、既存のものを改善していくのも大切にしたい。

contribution

今年はmrubyを触り始めていろいろ踏んだのでmruby周りにいろいろ貢献した気がする。RubyとかRailsはもうそこまで不満がないけど、mrubyの方はまだまだやれることがたくさんありそう。mruby本体にも1回だけ貢献できた。

ポッドキャスト

timakinさんに誘われてboot.fmというポッドキャストを始めた。4話配信済み。2人でやっていても、ネタと収録する時間とゲストを呼ぶ人脈とコミュ力がなくてかなり大変で、miyagawaさんはすごい!!!!という気持ちになる。一方、自分がホストをやると、話したいことを自由に話せる楽しさがある。

bootfm.github.io

2017年は

  • Ruby以外で手になじむ言語を増やしたい
    • 今後もコードを書き続けたいが、このままだとRuby以外で仕事できなくなり失職しそう
  • 業務で使って困ることがないレベルまで英語の語彙や表現力を向上したい
    • ドキュメント読むのとOSS活動をする時に使うし、いつか海外でも働けるようにしたい
  • 執筆活動をしたい
    • というかやってるんだけど、ちゃんと出すところまで持っていけるようにしたい

私生活と両立して楽しくやれればいいなと思っています。来年もろしくお願いします。

広告を非表示にする

Linux向けの最強のキーリマッパーを作った

X Window Systemで動作するキーリマッパー「xremap」を作った

2017/1/9追記: xkremap→xremapにリネームしました

僕はKarabiner用のRuby DSLを作ったりそれを使って大量の設定を既述する程度にはKarabinerのヘビーユーザーなんだけど、デスクトップ環境にLinuxを使い始めてからもう1年以上経つ今でもLinux環境で使えるKarabiner並にリッチなキーリマッパーを見つけられずずっと不便していたので、ユースケースを満たす最低限のものを自分で作った。

github.com

ちなみにX用であって別にLinuxの何かに依存しているわけではないので、タイトルは釣りである。

これは何

RubyDSLかつシンプルなキーの指定方法によりキーリマップを設定することができる。例えば、以下の設定ファイルを書いてxremapに渡す*1とターミナルやEmacsの外でもEmacsライクなキーバインドが使えるようになる。

remap 'C-b', to: 'Left'
remap 'C-f', to: 'Right'
remap 'C-p', to: 'Up'
remap 'C-n', to: 'Down'

remap 'C-a', to: 'Home'
remap 'C-e', to: 'End'

remap 'C-k', to: ['Shift-End', 'Ctrl-x']

また、xbindkeys等のツールより優れている点はアプリケーション*2ごとのキーバインドを設定できるところにある。普段Emacsバインディングを使っていると、Slackが割りあてるC-kとコンフリクトするわけだけど、例えば以下のように書くとSlackでのみ下記の設定を適用し問題を回避することができる。

window class_only: 'slack' do
  remap 'Alt-n', to: 'Ctrl-k'
end

あと、任意のキーからシェルを起動することができるので、ランチャーとしても使える。

remap 'C-o', to: execute('nocturn')
remap 'C-u', to: execute('google-chrome-stable')
remap 'C-h', to: execute('urxvt')

補足だけど、ある単一のキーを別のキーにリマップするみたいなことはXmodmapとかでやったほうがいいと思う。その方が多分速いので。僕はErgoDoxのファームウェアでそういうリマップはできるので不要なんだけど、xremapはEmacsバインディングみたいな何らかのキーの組み合わせをキーの組み合わせやシーケンスに変換したい時に使う。

なぜ作ったのか

GitHub等でキーバインドが奪われるし、その度にJavaScriptを読みたくない

GitHubというサイトはC-k, C-bを奪ってくる。社員は誰もEmacsを使ったことがないに違いない。 僕はいままでEmacsバインディングinclude "/usr/share/themes/Emacs/gtk-2.0-key/gtkrc"によって設定していたんだけど、これだと普通にブラウザに奪われるため、user.jsを書く必要があって面倒だった。

xremapを使うとXのルートウィンドウレベルでキー入力イベントをフックしてリマップするので、例えばGitHubのコメント欄とかでブラウザにキーバインドが奪われない点が便利。

rbindkeysがRuby >= 2.2で動かない & wineを使うとSEGVする

類似のツールにrbindkeysというものがあり、というかほぼ要件を満たしていたので長い間使っていた。が、上述したようなランチャーの用途には使えず別のツールで解決していたのとか、単純に不安定なのとか、DSLが複雑などの理由でずっと自分向けの奴を作りたいと思っていた。仕様も内部実装も仕組みも全て僕の好みに変えたrbindkeysがxremapといえる。いままでお世話になりました。

rbindkeysと比較して、xremapは下記のような違いがある。

  • キーイベントの取得やリマップにLinux Input Subsystemを使わないので、Linuxじゃなくても(多分)動く & rootじゃなくても実行できる
  • キーボードを抜き差ししてもそのまま使える
  • DSLの違いにより、キーの指定がシンプルに書ける
  • C-kとかのリマップの挙動が安定している

あと、この場合別にRubyでもmrubyでもどっちでもいいなあとは思ったんだけど、最近慣れてきたmrubyを採用している点も異なる。

macOSと違ってCommandキーがないから色々コンフリクトするのを解決したい

コピーやペーストがC-c, C-vなどControlに割りあてられているため、例えばEmacsC-fとブラウザの検索のC-fが被ってしまっていた*3

アプリケーションごとにキーバインドを変えられる程度にはリッチかつ十分に安定したキーリマッパーがあると、macOSみたいにCommandキー相当のキーを作ることができる。会社で貸与されてるノートPCがMacBookなので今でもmacOSは使うのだけど、できれば複数の環境の差異をなくしたいというのがあった。

気持ち

デスクトップ環境でLinuxを使っている人はNocturnxremapをよろしくお願いします。

*1:systemdとかで起動するのがよい xremap/xremap.service at 6e8e1f21285ecedfa7ac88d703ad80d25a2699dd · k0kubun/xremap · GitHub

*2:正確にはウィンドウのclass

*3:gtkrcによる設定だと、テキスト入力欄かどうかで挙動が変わっていたが、検索とかは常に使えて欲しかった

RubyConf 2016 で話してきた & MItamae v1.0.0をリリースした

RubyConf 2016で登壇してきた

2016/11/10〜11/12にアメリカのオハイオ州シンシナティRubyConfというイベントがあって、Ruby DSLによって設定できるCLIツールをRubyインタプリタやgemの存在に依存しないシングルバイナリとして実装するための知見を「Evaluate Ruby Without Ruby」というタイトルで発表してきた。

発表資料

発表動画

RubyConfってどうなの

RubyConfはRubyKaigi並に規模が大きいもののあまりRubyのDeepな部分には期待できないカンファレンスなんだけど、当時行ったことがなかったアメリカに行ってみたいという思いがあって去年も参加していた。あと、RubyKaigiとは違った層の海外のエンジニアと話せる *1 のも良い点だと思う。

去年はRubyKaigi 2015で話したものと同じ内容のCFPをRubyConfに出してリジェクトされ悔しい思いをしたが、今年はRubyKaigi 2016で話したものと今回の新作トークの2つのCFPを出したら新作の方が通って話せることになったので、今回は海外で登壇するという目的もあった。

海外で登壇した感想

英語むずい。

僕は基本的に発表スクリプトを書かないで発表するんだけど、英語の勉強が不十分なので、英語で話すことに脳のリソースがかなりもっていかれるのと緊張でスライドに書いてあること以外に何を話そうと思っていたのか忘れてしまい、発表練習していた時に比べ発表時間が短くなった。慣れが必要なのでまたやりたい。

一方、英語ネイティブの人たちを相手に英語でトークをするという点での精神的障壁に関しては、過去のRubyConfやRailsConfの動画を見ているとそこまで流暢でない英語を話す人もいたので開き直ったら意外と大丈夫だった。やっていきがあれば何でもできる。

MItamae v1.0.0をリリースしました

上記の発表でモチベーションや実装方法を紹介しているMItamaeItamaeという構成管理ツールの別実装なんだけど、RubyConfから一週間以上経った今、本家の機能のうちMItamaeにいれようと思っていた機能が一通り揃ったのでv1.0.0をリリースした。ついでに使われてるmrubyも最新にしてある。

初めて知った人向けに書いておくと、MItamaeはitamae sshなどItamaeの全ての機能を置き替えるものではなく、ツールの特性や用途は少し異なる。MItamaeについて日本語で紹介している記事はこちら: Itamaeのmruby実装「MItamae」が大体いい感じになった話 - k0kubun's blog

github.com

現在の導入実績について

社内での導入はWIPなんだけど、既に社外での利用事例がある模様。ありがとうございます。

qiita.com

tech.sideci.com

お気持ち

というわけで機能も揃って導入実績も増えてきたMItamaeをよろしくお願いします。

*1:実際にはRubyKaigiにも参加してるような人と話すことの方が多いけど、RubyKaigiでは見たことないがGitHubとかでは見たことある人と話すこともあったし、あとRubyKaigiと違って場の雰囲気的に英語で会話する行為へのハードルが低い

Itamaeのmruby実装「MItamae」が大体いい感じになった話

Roppongi.rb #2 で「MItamae」について話してきた

Roppongi.rb #2が "Infrastructure x Ruby" をテーマに開催され、そこで RubyなしでItamaeレシピを実行できる「itamae-go」を作った - k0kubun's blog 話と pure mrubyで実装されたItamae「itamae-mruby」を作った - k0kubun's blog 話をしてきた。

いいたかったことはスライドの通りだけど、枠が15分でいろいろ漏れた話を書いておく。

MItamaeの現状について

なんでitamae-mrubyからMItamaeに変えたの

というか一昨日までMItamaeはitamae-mrubyという名前だった。エエー。変えた理由は真面目な奴がいくつかあるんだけど、あえて不真面目な奴だけ書くと、名前が微妙なソフトウェアは流行らない気がしていて、itamae-goよりitamae-mrubyの方が筋が良いのにitamae-goの方がウケが良かったので、名前を変えてみることにした。

ちなみに僕はリネームの常習犯で、Hamlitは元々違う名前だったし、activerecord-precountは最初にrubygemsにプッシュしてから3回リネームしているし、他にもいろいろやらかしているので、MItamaeもあと2回くらい変身の可能性を残している。

原案はmrubyで何かとお世話になっているksssさんの発言なんだけど、mitamaeだと「見給え感」が強すぎるのでMItamaeということにしている。

プラグイン機構について

github.com

resourceプラグインをサポートした。社内でItamaeになってる奴をスッとMItamaeにしようとした時に、portageとかはプラグイン化したいみたいなことを言われたのでサポートした。

で、まあmrubyを使う以上rubygemsは利用できないので考える必要があって、普通にmrbgemを使うとプラグインを組み込む度にMItamaeをコンパイルし直すという話になるのだけど、シングルバイナリで配布してる意味が薄れるので、pluginsディレクトリにmrbgemと同じディレクトリ構造 *1Rubyスクリプトを置いておくと動的にロードされるようにした。

なので、リポジトリにgit submoduleでプラグインをいれるか、最悪vendoringするというのが想定された使い方になっている。これに関してはいろんな人の意見を聞いてみたい。

来週の話

今日はItamaeを軸に話をしたけど、来週の金曜にitamae-go, MItamaeを題材にしつつmrubyを使う側を軸にRubyConfで登壇するので、興味がある人はシンシナティで僕と握手。

*1:具体的には ./plugins/*/mrblib/**/*.rb がファイル名順に読まれます

SSEを使ってHTMLエスケープを高速化してみた

高速なHTMLエスケープをするライブラリを作った

ある日HTMLエスケープを速くしたくなって、hescapeというライブラリを作った。

github.com

とにかく速いHTMLエスケープがしたい

Railsアプリのビューのレンダリングにおいて、CGI.escapeHTMLを高速化*1することでRailsのデフォルトのテンプレートエンジンが大きく高速化されたり*2、GitHubでもHTMLエスケープが全体のパフォーマンスに影響が大きかった事例もある*3など、常に自動でHTMLエスケープが行なわれるRailsの環境ではHTMLエスケープの速度が割と大きな意味を持っている。

従って、Hamlitの最速性を維持するためにHTMLエスケープのパフォーマンスを極めておきたかった。

vmg/houdini を倒したい

前述したGitHubの人が既にhoudiniというかなり速いエスケープライブラリを作っていて、escape_utilsというgemを使うとHamlやSlimでそれが使え、HamlitやFamlはデフォルトでそれが使われるようになっている。Rubyにおいてこれより速くHTMLエスケープを行う手段を知らないので、これを越えることが目標になる。

Streaming SIMD Extensions

とはいえhoudiniはよくできているので、一緒に協力してテンプレートエンジンを作っている*4 id:eagletmt さんと去年「(HTMLエスケープは)まああれより速くしようがないですよね」みたいな話をしたのだけど、この前Tokyo RubyKaigi 11の懇親会で id:nurse さんが「HTMLエスケープはSSE使えば速くできるよ」とおっしゃっていた。

h2oのHTTP/1パーサに使われているpicohttpparserもSSE命令(PCMPESTRI)で速くなっていて *5、ループで1文字ずつチェックしていたのが1命令で16文字同時にチェックされるようになっている。HTMLエスケープでも、エスケープすべき文字がない時特に速くなりそうな感じがしたので、試してみることにした。

一人チューニング大会

SSEを使う以前にそもそもCで速いコードを書くのがあまり得意ではないので、事前にテストとベンチマークを用意して、ひたすらベンチマークの結果をよくする、という一人ISUCONみたいなことをやっていた。結構面白いので、やってみたい人は以下を読まずにHTMLエスケープ(CGI.escapeHTMLコンパチのもの)を実装してみると良いかもしれない。

ベンチマークには、一切エスケープをやらないもの全部エスケープするもの、あとライバルのhoudiniが使っているものを用意し、CIでhoudiniの何倍速いかというのを出していた*6。以下は実装ログとwercker上でのベンチマークの結果(houdiniより何倍速いか)の推移である。

コミット 概要 no escape all escape houdini bench
Implement HTML escape 素直に実装した奴。遅い。 0.04x 0.43x 0.08x
Reduce allocation times エスケープが発生した時のみreallocするようにした 0.53x 0.43x 0.85x
Lazily copy unescaped characters 非エスケープ文字はエスケープが発生した時にまとめてコピーするようにした 0.62x 0.35x 0.93x
Skip allocation when nothing is escaped エスケープが行なわれない時はmallocが起きないようにした 1.13x 0.34x 0.91x
Increase allocation size by 1.5 houdiniのパクリ。2回目以降のエスケープ時、メモリが足りない時に必要分の1.5倍メモリを確保している。いいのか…と思うけど対houdiniなら公平。 1.12x 0.61x 1.01x
Optimized strlen of escaped string strlen対象が限られてるのを利用してstrlenを四則演算に変えている。やんちゃチューニング感ある。 1.08x 0.99x 1.11x
Change ensure_allocated to be static ある関数をstaticに変えただけ 1.08x 1.16x 1.13x
Skip non-escaped characters fast この辺で語られてるテクニック(をやっているつもり)。エスケープしない時に局所的にループを回している。 1.17x 1.16x 1.14x
Optimize by pcmpestri intrinsics ここでSSEを使った。全部エスケープだと遅くなるが、エスケープがない場合は本当に速い。 7.70x 追記 0.83x 1.63x

結果

普通のWebアプリのほとんどのケースでは実際のエスケープは走らないわけだけど、そのケースにおいて PCMPESTRIを使っただけで6〜7倍になった(下記の追記を参照)。ただ、あまり何も考えないで作った非現実的なベンチなので、参考にするなら例えばURLくらいの長さの文字列のエスケープを見た方が良いと思う。

本当はHamlitにいれようと思ってたけど、保守性が下がる割に全てのケースで速くなるわけではない*7のと、ビルドの時に考えることが増えるので、HamlitでSSEを使うのは一旦見送ることにした。

08/16 23:35 追記

shinhさんににご指摘いただいた致命的なバグを直したことによりエスケープなしのケースが 7.29x→2.78x くらいに変化している。いろいろ指摘をいただいたので直している最中で、それが終わってみないと実際どのくらいの速さになるものなのか不明。

感想

最初どうやって使うのかが全然わからず難しいなと思ったけど、その難しさに見合う結果は得られたのでよかった。picohttpparserのコードを参考にしてたのでSSE4を使っているけれど、後で後継のAVXも試したい。

*1:https://github.com/ruby/ruby/pull/1164

*2:http://k0kubun.hatenablog.com/entry/2016/05/29/215851

*3:https://github.com/blog/1475-escape-velocity

*4:http://k0kubun.hatenablog.com/entry/2015/12/12/000037

*5:http://blog.kazuhooku.com/2014/12/improving-parser-performance-using-sse.html

*6:10%エスケープというのも見てたけど、houdiniのベンチとあんま変わらんのと横幅の関係でこの記事からはカット

*7:これもうちょっとなんとかならないかな…

pure mrubyで実装されたItamae「itamae-mruby」を作った

itamae-goを作り直してitamae-mrubyを作った

先週Goからmrubyを使ってRubyなしでItamaeレシピを実行できる「itamae-go」を作ったんだけど、全く同じコンセプトの、RubyなしでItamaeレシピを実行できる「itamae-mruby」を作った。

github.com

itamae-goの問題点

mrubyは組み込み言語だしこれは本来想定された使い方であり、go-mrubyの実用的な例として普通に作ってよかったと思っているけど、ことItamaeを実装することに関しては以下のような問題があった。

  • レシピを読む部分以外をGoで実装していたので、specinfraのコードの移植に手間がかかる
  • 主にstandaloneなバイナリを吐く目的にGoが使われているが、mruby-cliでもできるのでGoを使っているメリットがそれほどなく、2つの言語をブリッジするコードを書く労力に見合わない

僕としてはRubyなしで動くItamaeは環境構築の用途にやっぱり欲しいし、ちゃんとメンテし続けたいので保守がしやすいように作り直した。

mruby-cliとは

Goと違ってmruby自体には自分が書いたスクリプトをstandaloneなバイナリにする手段はない(多分)。そこで、mrubyのC APIを使ってmrubyで書いたコードの__main__メソッドにargvを渡して呼び出すCのコードと、それをクロスコンパイルするDocker環境を提供しているのがmruby-cliである。 なので、mruby-cliを使えばRubyの存在に依存しないバイナリをRubyで書くことができる。

itamae-mrubyの現状

  • 現時点で基本的な機能はitamae-goのスーパーセット
    • mruby-yamlのクロスコンパイルがうまくいかないので一旦--node-yamlは外している
  • OSX, Arch, Debian, Ubuntuに加えCentOS, Gentooとかもサポートするようにした
  • 多分動くけどまだちゃんと検証できてない

itamae-mrubyの設計

ryotaraiさんがmrubyで実装した奴をItamae 2にしようとか言ってたので微妙に手間をかけて作っている。まあそんな簡単には置き替えられないと思うけど。

  • resourceからresourceを実行する部分を分離し役割を減らしている
  • ryotaraiさんのアイデアで、actionを実行するのではなく事前状態と事後状態を受けて適用するようにしてみている
  • specinfraは移植しやすいよう完全にそのまま

mrubyだけでCLIを実装した知見

mrubyはまだまだ未整備な部分が多くフロンティア感がある。普通にRubyで実装するのとはいろいろ違うところがあったので、そのへんの感想を書いておく。

Rubyにはあるがmrubyに存在しなかったもの

rubygemsが使えないというのがやっぱり大きい。そして標準ライブラリは大体ないし、本家だと言語機能になっている部分がないこともあり、やっていく必要がある。以下に私のやっていきにより生まれたものを書いておく。

mruby-hashie

ItamaeはHashie::Mashをガッツリ使っているし、社内のレシピでも使われてるのでmrbgemにしておいた。元のコードがMIT Licenseなので、ライセンス表記やauthorsとともに実装をいただいたらほとんどそのまま *1 で動いた。なお、Hashie::Mashしかない。

mruby-shellwords

標準ライブラリ。かなりコマンドをガチャガチャやるので普通に必要だった。Itamaeで必要だったメソッドはそのままのコードで動いた。

mruby-open3

標準ライブラリなんだけど、これを作るのはコピペでは全然動かないし結構大変。まず既存のmrbgemにspawnがないので、それを自分で書く必要がある。spawnは本来mruby-processとかで実装すべきだけど、spawnのオプションをいろいろサポートするのは実装するのは割と大変なので、outとerrオプションを必ずredirectするインチキspawnを内蔵している。

意外とmrubyにも存在したもの

Rubyに比べてしまうともの足りなく感じるけど、mrbgem自体は結構ある。個人的にはmruby-threadとかmruby-tempfileがあるのが助かった。

GitHub - mruby/mgem-list: A list of all GEMs for mruby to be managed by mgem

ファイル名順にソースが読み込まれる

Dir.glob("#{dir}/mrblib/**/*.rb").sortの順に読まれるので、主に継承が必要な時にファイル名の先頭に数字をつけるみたいなことをやる必要がある。

mruby-requireを使うと綺麗に書けるんだけど、実行時にファイルを探しにいくのでワンバイナリを作る用途では使えなそう。

bundle gemコマンドみたいな奴

あった。便利。

github.com

まとめ

Rubyなしで実行したいコマンド作る用途にもmrubyは便利なので、この知見を参考にmrubyを使う人が増えてmrubyのエコシステムがよりよくなってほしい。あとitamae-mrubyもよろしくね

*1:&:sym でto_procする奴がなんか動かなかった。mirbでは動くので多分サポートはされてるんだけどmrbgemの中だとなぜか…。あとでちゃんと調査する