Day 18: trivial-signal
これは fukamachi products advent calendar 2016 の18日目の記事です。
今日はtrivial-signalについて話します。土日に細かいライブラリ紹介が並んでいるのはただの手抜きです。
Common LIspとスクリプト
継続的なCommon Lispの課題として、スクリプトを書きづらいというものがありました*1。
開発時はREPLなので問題にならないのですが、アプリケーションの起動などを考えればコマンドラインで起動できなければいけません。他のUNIXコマンドとの連携を考えてもCLIから離れられるわけではありません。
これは8日目に紹介したShellyで解決しようとした課題でもありますが、それでもまだその他の軽量プログラム言語ほどの手軽さはありませんでした。
その理由の一つがシグナルハンドリングでした。
POSIX シグナルハンドリング
シグナルハンドリングはUNIXでのプロセス間通信の方法の一つです。プロセスが特定のシグナルを受け取ったときにどうするかを指定しておけば他プロセスから制御することができます。
たとえば、スクリプトをターミナルで起動して、C-cを押すとINT
が送られます。コマンドラインだとkill -INT <pid>
のように送ることができます。
Common LispではPOSIX APIが仕様に含まれておらず *2、当然シグナルハンドリングもできません。C-cをするとSBCLではSB-SYS:INTERACTIVE-INTERRUPT
というコンディションが発生できSIGINT
のハンドリング自体はできますが、これでは2つ問題があります。
- 処理系依存である
- 他のシグナルはハンドリングできない
処理系可搬で、SIGINT
以外のシグナルもハンドリングしたい。
それを解決するために生まれたのが「trivial-signal」です。
誰か作ってくれ
trivial-signalに関しては、他に誰か得意な人がやってくれたほうがいい、と当初から考えていました。あまり自分自身が使う機会も多くありません。とはいえ誰かが都合よく書いてくれるわけでもないので今必要な自分がとりあえず書く、けれどいつか誰かに引き継げるなら引き継ぎたいと思っていました。
名前に"trivial"と含まれるライブラリはAPIが自明の小さなライブラリがほとんどで、言ってしまえば標準ライブラリのような立ち位置の名前です。"Woo"や"Envy"のようなユニークな名前でなく、公共性の高い名前を敢えてつけたのは、やっぱりあまり思い入れがなくメンテナンスも頑張るつもりがなかったからでしょうかね。
ライセンスも珍しく"public domain"にしており、誰でも自由に使ってもらうようにしました。
浅井さん登場
そしてGitHubに置いてQuicklispに申請してほんの2ヶ月ほど。浅井さん(@guicho271828)からPull Requestを2つもらいました。
浅井さんと言えばCommon Lispやclfreaks界隈をウォッチしている人はご存知かもしれません。eazy-processなどGitHubでいくつかCommon Lispライブラリを公開しています。
Pull Requestをもらったとき、もはや早くもtrivial-signalの存在さえ忘れていました。この機会だから浅井さんにメンテナお願いしよーっとと思って声をかけます *3。
深町: 実はこのライブラリあんまり使わないし、質を高く保てる自信ないんだよね。よかったらメンテしてくれない?
浅井: わかった
というような二つ返事であっさりメンテナを引き受けてくれました。ありがたし。
そういった経緯で、trivial-signalはこのアドベントカレンダーで紹介するライブラリで唯一僕がメンテナではありません。作者にあまり愛されずにドナドナされた不遇なライブラリだと思うとかわいそうなのでここで紹介して罪滅ぼしとします。
おわりに
trivial-signalはGitHubで公開されています。
明日のアドベントカレンダーは19日目のclfreaksについてです。お楽しみに。
Day 17: smart-buffer
これは fukamachi products advent calendar 2016 の17日目の記事です。
今日はsmart-bufferについて話します。裏側で使っているライブラリなのでたぶん知らない人がほとんどかと思うので紹介です。
Wooの成功
15日目に話したWooは想像以上に受け入れられ、すぐに実際に使ってみようという人が多く見られました。そしていくつか質問も飛んできます。
よくあった質問がこれでした。「ファイルアップロードしたときにファイルが全部メモリに載ってるよね?大きいファイルアップロードしたらやばくない?」
twitter.com@nitro_idiot For woo/http-body/fast-http, how are large files per multipart/form-data handled? Are they stored on disk or in memory?
— NIL (@Shinmera) 2015年7月1日
大きなリクエストの保持方法
もちろん、全部メモリに載せてたら問題です。Wooではこの問題に対してスマートな方法で対処しています。
リクエスト本文を読み込んでいき、サイズが小さいうちはインメモリバッファにロードします。もしサイズがしきい値を超えたらバッファの内容をファイルに書き込み、ファイルバッファに切り替えます。
つまり、小さいリクエストのときはインメモリで高速に処理し、大きいリクエストはファイルに書き出してメモリ使用を抑えます。
twitter.com@Shinmera Keeps them on memory first and dumps them to disk when their size exceeds the limit. https://t.co/4da7ZQkWwv
— fukamachi (@nitro_idiot) 2015年7月1日
元々fast-httpのmultipartパーサーの機能だったのですが、これは汎用的に使えるな、と思ってのちに別ライブラリにしました。それが「smart-buffer」です。
smart-bufferのアイデア
このアイデアは僕のオリジナルではありません。
Pythonにmultipartというmultipart/form-dataのパーサーライブラリがあります。これが全く同じ仕組みで動作し、小さいファイルアップロードにはメモリで、大きいファイルアップロードにはディスクで対応しています。
おわりに
smart-bufferはGitHubで公開されており、Starは7です。使っているライブラリはfast-httpとWooだけですね。
明日のアドベントカレンダーは18日目のtrivial-signalについてです。お楽しみに。
Day 16: QURI
これは fukamachi products advent calendar 2016 の16日目の記事です。
今日はQURIについて話します。
PURIへの不満
Common LispにはURIを扱うライブラリとしてずっとPURIが使われていました。
僕も詳しい経緯は知らないのですが、元々Allegro CLにあったURIライブラリを処理系可搬にしたのがPURIのようです。元となったAllegro CLのURIライブラリはCopyrightが1998年からになっており、かなり古くからあるようですね。
古くからあり広く使われてきたからと言って、不満がないわけではありません。
最もよくある不満は、Unicode対応がないこと。PURIで、URLエンコードされているUnicode文字列をデコードすると文字化けするのです。
これだけならばURLデコードにPURIを使わなければいい、という話ですが、PURIはしかもそれをデフォルトでデコードするという意味のわからない仕様なのです。
puri-unicode
これに対して各自いくらかパッチを当てたりして使っていましたが、@archimag *1がPURIのUnicode対応版のpuri-unicodeを作ってそっとGitHubに置いていました。これが事の発端です。
するとそれを見つけた第三者が、puri-unicodeをQuicklispに入れてくれと申請を出します。
※以下は完全に意訳です。
HiTECNOLOGYs: puri-unicodeをQuicklispに入れて。
Zach: puri.asdが本家と競合するから入れられない。
それを聞いて今度は@archimagに「システム名がPURIと競合してるよ (ロシア語)」と送ります。
HiTECNOLOGYs: システム名がpuriと競合してるよ。
archimag: これはわざとだよ。puriはUnicode周りで問題がある。puriの代わりにpuri-unicodeをロードすればいい。
HiTECNOLOGYs: でもどうにかしてQuicklispに入れられないかな?
archimag: それはZachに言ってくれ。
という感じで全く話になりません。
Zach: 本家PURIにマージできたらいいと思うんだけど。
(一同): 確かに。
Andrew (Wookieの作者): メンテナにメールしてみました。返事ないです。
みたいなことを一年以上かけてやってるわけです。
通常、メンテナに連絡取れないときは新しいメンテナとなってマージしたものをQuicklispに置き換えて登録することが可能です *2。しかし出て来る人みんな非協力でどうにも進まない。まあ僕は何も手伝ってないので文句言える立場じゃないけど。
twitter.compuri使うたびにハマるし機能足りてないから新しく作るかぁ、ライブラリ名は何にしようかなぁ、Qualified URI library、略してquri (キュウリ) とかどうか、まで考えて面倒になったので誰か代わりに作っていいですよ
— fukamachi (@nitro_idiot) 2014年9月15日
こういうことを半分本気でツイートしたんだけど、冗談と思われたのか意外とfavられずにタイムラインの底に沈みました。
Wooの副産物
そんなツイートも忘れて数ヶ月後のこと。Wookieの高速化やWooの開発などに没頭していた最中。
昨日の記事でWooを高速化する過程でURIパース部分にそれなりのボトルネックがあることがわかってきました。使っているライブラリはあのPURIです。
機能だけでなく速度にも問題がある。これは単純にUnicode対応すればいいという話ではなくなってきました。そういう経緯で、やっぱり自前で高速かつ高機能なURIライブラリを作る必要があろう、と考えました。
それが「QURI (キュウリ)」です。
QURI
QURIに求められていたのは、Wooで使うので可能な限り高速であること。PURIの代替機能をすべて持つこと。それからPURIに足りない機能を補完することです。
具体的に足りなかったのは以下です。
- UTF-8対応
- userinfo対応 (
git@github.com
のgit
の部分) - IPv6対応 (
ldap://[2001:db8::7]/
など) - URLエンコーダ・デコーダ
- URIライブラリにまとめてしまうほうがわかりやすい
- 別でいくつかライブラリがあったがどれも遅い
QURIではfast-httpで得た、高速なパーサを書く知見を活かしてURIをパースしました。つまりtagbodyとgoのオンパレードです。
最終的にURIパーサとしてはPURIより6.6倍高速にできました。
例によってRedditにも投稿されて議論されていました。「きゅうりってのはキューカンバーのことだよ」とか書いてあって面白いです。
DrakmaのQURI利用騒動
もはやPURIを使うモチベーションはなくなりました。僕の立場としてはPURIを問題のあるメンテナンスのされていないプロダクトとして喧伝し、PURIを使うプロダクトを減らしていく必要がありました。けれどWooの開発もあったのでなかなか宣伝して回るのも手が足りません。まず手始めに、僕のプロダクトでPURIをQURIに置き換えたりしていました。
そんなある日、僕の知らない間にCommon Lispの代表的なHTTPクライアントのDrakmaに「PURIをQURIで置き換える」というPull Requestが来て、トントン拍子でマージされました。
これによりDrakmaはUTF-8対応できました。Drakma自体は置換がうまくいったのです。けれど、Drakmaに依存する多くのライブラリが返り値としてPURIのオブジェクトが返ってくることを期待しているコードがあるようで、Quicklispの中に多くのライブラリが影響を受けて壊れたようです。そのためこのPull Requestはやむなくrevertされました。
メンテナンスはされていても広く使われていることで必要な改善もできずに朽ちていく過程です。
ちなみに僕はこの話の4ヶ月前にDexadorという新しいHTTPライブラリも作っています。こちらはもちろんfast-httpとQURIを使っており、Drakmaを置き換える新世代のライブラリを目指したものです。
Dexadorについては20日目に詳しくお話するので楽しみにしていてください。
おわりに
QURIはGitHubで公開されており、Starは44です。
明日のアドベントカレンダーは17日目のsmart-bufferについてです。お楽しみに。
*1:Andrey, RESTASの作者。https://github.com/archimag
*2:named-readtablesでは実際にそれが起きた
Day 15: Woo
これは fukamachi products advent calendar 2016 の15日目の記事です。
今日はWooについて話します。
これがあらすじです。
twitter.comWookieがNodeより遅くね?→プロファイル→http-parseが遅い→PRいくつか投げる→でも遅い→fast-http公開→Wookieまだ遅い→ClackベースのサーバWoo公開→まだ遅い→これってlibevent2で詰まってね?→libuvへ、みたいな流れでした
— fukamachi (@nitro_idiot) 2014年11月2日
Wookieの高速化
Wookieのボトルネックを解消してもNode.jsのパフォーマンスには今一歩届かない。そこで使っているイベントライブラリのlibevent2が遅いのではないか?という疑念が生まれました。libevent2がボトルネックならば、libevや、Node.jsの使っているlibuvに切り替えることでNode.jsと対等に戦えます。
するとWookieの作者のAndrewが、libuvに切り替える予定だ、というリプライをくれました。
twitter.com@nitro_idiot Working on libuv backend for cl-async. Should be faster soon. Needs streaming SSL though which libevent gives for free
— andrew lyon (@killltheradio) 2014年10月11日
その頃、libuvとlibevent2の有効なベンチマークは知る限りありませんでした。しかし、Node.jsがlibevから乗り換えたことを考えるとlibuvもlibev程度は高速であることが見込めます。そしてベンチマークによってはlibevはlibevent2より2倍近く高速ということがわかっていました。
つまり、黙って見てるだけでも2倍近く高速になるかもしれないという状況です。
Wookieのスリム化
で、黙ってみているのもつまらない。その間にできることはやっておきましょう。
WookieにはWebサーバーとしては不要の機能がいくつかついていました。URLディスパッチャやプラグイン機構などがそうです。これらがプロファイリングには出てこずとも積み重なって有意な差を生んでいるかもしれません。
それを検証するために、余分な機能を削ぎ落としたバージョンを作ろうと考えました。そのサーバーがClack-compatibleなAPIを持つならばClackハンドラのボトルネックもありません。
そして完成したのが「Woo」でした。削ぎ落としたと言ってもフォークではなくスクラッチから書き直したのですが、2日程度で完成しました。
twitter.comPublished a non-blocking HTTP server for Common Lisp https://t.co/a9atuF7N0e It handles 17.5k req/sec on my laptop with one CPU core.
— fukamachi (@nitro_idiot) 2014年10月14日
twitter.comFor the record, Node.js handles 17k req/sec on the same environment. A little bit better, but not much difference.
— fukamachi (@nitro_idiot) 2014年10月14日
ここで実験して得た知見をWookieにフィードバックしたいという程度のおもちゃのようなプロダクトでしたが、最初のバージョンでもはや少しNode.jsより高速になりました。
URIパースの高速化
また、何度かのWooのプロファイリングでURIのパース部分に時間がかかっていることがわかってきました。当時広く使われていたPURIというライブラリは遅くはなくとも十分に高速と言えるものではありません。
そこで「QURI」という高速なURIパーサーを書き、これをPURIと置き換えました。
twitter.comJust published "QURI", a URI library for Common Lisp which is 82% faster than PURI. https://t.co/jhQSCGkl1B
— fukamachi (@nitro_idiot) 2014年10月25日
QURIについては明日のアドベントカレンダー16日目で詳しく書く予定なので今回は割愛します。
サムライトに入社
河西くんに声をかけられたのはこの頃でした。彼は当時サムライトのCTOをやっており、Node.jsの広告配信サーバーを開発していました。個人的にはCommon Lispが好きだったにも関わらず、Common Lispが遅かったため、やむなくNode.jsで開発を行っていたのです。不幸な境遇です。
しかし、僕がCommon Lispは速いということをNode.jsとWooの比較で実証しました。1.2倍程度の差ではありますが、言語として比較してもJavaScriptより抽象度の高く開発効率も良いCommon Lispで書かない理由はもはやありません。
最初はCommon Lispへの移行を手伝う程度に考えていたのですが、話を聞いているうちに入社したほうがよかろうと思い、サムライトに入社することになりました。
サムライトに入社しても僕の仕事が大きく変わるわけでもありません。引き続きWooの高速化と安定化、他のWeb系ライブラリの開発、不具合や質問があれば迅速に対応するといったことが僕の仕事でした。
とはいえ、のんきにはしていられません。
サムライトは広告配信システムを開発しています。同時に大規模なリクエストがサーバーに飛んできます。つまりは思っていたよりも早くWooのパフォーマンスを試せる環境を得たわけで、これが対外的にCommon Lispの優位性をアピールできる舞台でもあるわけです。
このチャンスを逃すまいと、僕は一層Wooの高速化と安定化に励みました。
Wookieの脱落
そうこうしているうちにWookieの使っているcl-asyncのlibuv化が終わりました。
twitter.comcl-async is now using libuv: http://t.co/2eiXwbfGNF. discuss on reddit! https://t.co/fiFPncOOF9
— andrew lyon (@killltheradio) 2014年12月9日
けれど、これは失敗でした。libuvにすることでWookieのパフォーマンスはがくっと下がり、もはやNode.jsどころかHunchentootにも負けるような状態になりました。
同じバックエンドを使っていたWooも当然パフォーマンスの劣化が見られました。けれど、それまでの十分なパフォーマンス向上により、なんとかNode.jsより1.3倍高速というラインを保っていました。とはいえここにきてのパフォーマンスの大幅な劣化は気分が沈みます。
僕は、Andrewがパフォーマンスが落ちるということを知りながら早急にmasterにマージしたのは失策だと思います。
当時の僕はcl-asyncのuvブランチの開発状況を確認しつつ、ベンチマークを取ってIssueでフィードバックしていました。そしていくつもの改善も虚しく結果は芳しくありませんでした。なのでパフォーマンスが落ちるということを知らなかったはずがありません。
libuvのほうが開発が活発だからその未来に賭けたいと思ったのかもしれません。けれど、パフォーマンスが重要なWebサーバーというプロダクトで、現状数十パーセントもパフォーマンスが劣化するならばそのコードはお蔵入りで当然でしょう。
せっかく苦労して書いたものを捨てるのは勿体ないと思ったのかもしれませんね。けれど、パフォーマンス・チューニングなんてそもそもそんなものです。速くなるかどうかはある程度コードを書いて動かしてベンチマークを取らなければわからない。結局遅ければ全部捨てて他の可能性を試す、ということの繰り返しです。
Wooの爆速化
Node.jsを圧倒的に引き離すにはもはや細かいチューニングではダメということは自明でした。数十パーセントの変化は設計上や基幹ライブラリなどを大きく変えるしかありません。
この頃から、Wooの安定化と並行して、libevへの移行を行いました。
libevならばlibevent2とのベンチマークも公式で出ており、2倍近くのパフォーマンス向上が見込めます。もはやライバルはNode.jsではなくGoに移りつつありました。
そしてcl-asyncからlibevバックエンドへの移行が完了しました。
twitter.comI'm switching the backend of Woo from cl-async (libuv) to libev at 'ev' branch. I would appreciate your feedback https://t.co/Gip9vAE59M
— fukamachi (@nitro_idiot) 2014年12月17日
移行により予想通りパフォーマンスは跳ね上がり、Node.jsの約1.9倍のパフォーマンスが出ており、Goに迫る勢いです。詳しくは当時のエントリーを御覧ください。
高速なCommon LispのWebサーバ「Woo」を作りました - 八発白中
ちなみに最新のベンチマーク結果では2.3倍のパフォーマンスが出ています。
反響1: 実アプリケーションでは遅いのでは
Common LispのWebサーバーがNode.jsより2倍近く高速で、かつGoに迫る勢いだ、という主張がベンチマークもついて公開されたというのはインパクトがあったのかもしれません。
Reddit*1 や HackerNews にも投稿され、それなりの反響がありました。
反響の中には驚きや賞賛も多かったですが、否定的なものも目立ちました。
その中でもよく目についたのが、「こんなHello, Worldを返すだけでは実際のアプリケーションとは言えない (からこのベンチマークは無効だ)」というものでした。
仰る通り。けれど、Wooは「Hello, Worldベンチマーク」に最適化したWebサーバーではありません。実環境でも当然ながらNode.jsよりは高速です。
これは僕の空想ではありません。その後サムライトでは1ヶ月を費やして広告配信システムをCommon Lispに書き換えて運用を始めました。
早期導入を優先したため、SQLの最適化などWebアプリケーション側のチューニングは十分に行わなかったにも関わらず、以前のNode.jsより1.6倍のパフォーマンスが出ています。
以下はEuropean Lisp Symbopium 2015で僕が発表したスライドの一つです。このスライドで会場から拍手をいただいたときは、やっぱりうれしかったですね。
Woo: Writing a fast web server @ ELS2015
反響2: 本当に最速?
もう一つあったのが、「teepeedee2を知ってるか?」というものでした。
teepeedee2とはJohn Fremlinの作った「10k requests / secを超えられる」という触れ込みのCommon LispのWebサーバーです。公開された2009年当時ではC10k問題への解決策としてかなり反響があったはずです。
John Fremlin's blog: teepeedee2 achieves 10k requests/second (キャッシュ)
ベンチマークグラフにはnginxも並んでいますが、そのnginxよりも1.5倍良いパフォーマンスを出しており、C++で書かれたWebサーバーに迫っています。
もちろんteepeedee2は知っています。けれども言ってしまえば、「teepeedee2はプロジェクトが死んでる」。
twitter.com@snmsts @eudxa I know tpd2 is pretty fast, but it is abandoned.
— fukamachi (@nitro_idiot) 2014年10月7日
Quicklispには入っていますが、最新のSBCLではLinuxでもビルドできない状態になっています。
動かないものとベンチマーク比較しろって無理じゃないですか。
twitter.comIf no one can build tpd2, no one can beat the performance. It's like a legend :p
— fukamachi (@nitro_idiot) 2014年10月13日
河西くんが調べてくれたところによると、CFFIのAPI変更によるものだろうということでした。そして親切なことに、彼は手元で動くように簡単に直してベンチマークを取ってくれました。その結果は残念ながら、Node.jsより少し遅い程度だったという平凡なものでした。
IOLibも高速だとよく聞きます。実はcl-asyncを一度IOLibで置き換えたこともありました。しかし、パフォーマンスは下がり、Node.jsより20%も遅かったです。
この辺りで「Common Lispは速い」と言っている人の中に盲信者が含まれていることに気づきました。実際のところ誰も何がどれくらい速いかなんてわかっていなかったのです。teepeedee2はリリース当時は確かに速かったのかもしれませんが、現在では他に追い抜かれて朽ち果てたプロジェクトとなっています。
Goに勝てる余地はあるのか
最近は「Goと比較してWooは少し遅いようだが、導入メリットはあるか」という質問をよくされます。はい、あります。
理由は利用言語の違いです。Common LispとGoを比較すればCommon Lispのほうが抽象度が高く、機能も豊富です。たとえばCLOSやコンディションシステムなどです。これは開発効率にも影響しますし、長期的にそれなりの規模のWebアプリケーションを運用していくならメリットは十分にあります。
今は、最近Goを使い始めたばかりという会社ばかりなのでチーム開発や長期運用面でのつらさなどは表に出てきていませんが、2年後、5年後、10年後にどうなっているのか少し楽しみです。
パフォーマンス面でWooはGoのサーバーに勝てるのか、という話であれば、まだわかりません。ただ打てる策はあります。libevをやめるということ。libevではなくpoll, epoll, kqueueのバインディングをそれぞれ作ってそれを呼び出せばまだ5%程度なら速くなる余地があります。
これをやらない理由は、現状でそこまでのパフォーマンスを求められていないということと、時間がないからという消極的な理由です。興味がある方はPull Requestは歓迎します。
おわりに
WooはGitHubで公開されており、Starは475です。これは僕のプロダクトの中ではClackに次ぐ2番目の評価です
明日のアドベントカレンダーは16日目のQURIについてです。お楽しみに。
Day 14: fast-http
これは fukamachi products advent calendar 2016 の14日目の記事です。
今日はfast-httpについて話します。埋め込みツイートが多いですが手抜きではありません。
Common Lispは高速か
Common Lispは高速だという話を界隈ではよく聞きます。噂によればC++やCよりも高速なプログラムを書くことができるとさえ言われています。
Cで書くコードの方がCommon Lispで書くより速いって人がいたら、それは彼のCの技量が高すぎるってことだね。
“If you can't outperform C in CL, you're too good at C.” — Eric Naggum
昨日紹介したwebsocket-driverを書いたとき、好奇心からCommon Lispの実行速度というのはどれほどのものだろうと計測してみました。比較対象はNode.jsのwebsocketモジュールです。
勝負はあっけなく終わりました。書き上げてとりあえずベンチマークを取ってみようという段階で、僕が書いたCommon Lispの実装のほうが圧倒的に速かったのです。しかも、型宣言やoptimize宣言や関数のインライン化などもすればもっと速くなる余地がある状態でです。
これは当時の意気揚々としたツイートです。
Though I heard Node.js is fairly fast, My Common Lisp code is about 35 times faster without any declaims...
— fukamachi (@nitro_idiot) 2014年9月26日
高速に動くということの優位性
Common LispでWebアプリケーションが書けると言っても、じゃあ他と比べたときの優位性とはなんだろうという答えの一つがこれでした――Common Lispは速い。
これだけ抽象度の高い言語でありながらGoやC++と比べられるほどのパフォーマンスが出るのだから、これは強みになります。起業を考えていた自分はこの強みを活かして技術優位性としようと考えていました。
けれど、まだ道のりは始まったばかりです。
やっぱりCommon Lispは遅い?
河西くんとTwitterで会話するようになったのはこの頃でした。先程のツイートを見てリプライをくれています。
twitter.com@nitro_idiot Actually I tried to get benchmark s of Node.js HTTPserver and Wookie to get the result that Node.js is sadly faster.
— (rudolph-miller) (@Rudolph_Miller) 2014年9月26日
Wookie *1がNode.jsのhttpモジュールよりも遅い、というのです。まじかよ、と思って実際に計測してみると、Node.jsはWookieよりも2倍も高速でした。
twitter.comNode.js HTTP module is approximately 2 times faster than Clack and Wookie. https://t.co/ru1xkSwhRi
— fukamachi (@nitro_idiot) 2014年10月5日
Wookieを高速化する
Common Lisp自体はNode.jsより遅い言語ではないというのはwebsocket-driverで知れたことです。ということは、何か遅い原因があるはずです。僕はWookieのソースコードを見てどうにかNode.jsより高速にしてやろうと決めました。
Wookieをプロファイリングすると内部で使っているHTTPパーサーのhttp-parseがボトルネックとなっていることがわかりました。
twitter.comAlthough WOOKIE:READ-DATA is taking time most, it just 'funcall's a 'http-parse' parser. Functional style makes it harder to profile.
— fukamachi (@nitro_idiot) 2014年10月5日
中身を見てみると納得です。パーサー部分はナイーブに正規表現で行っていました。subseq
などの呼び出しも見られ、何度もメモリアロケーションがされているのがわかります。
このときの開発の状況はツイートによく残っています。http-parseのベンチマークを取って高速化してPull Requestを投げることを繰り返していると、じきにκeenも参加し始めました。
そして最終的にhttp-parseはWookieの最大のボトルネックではなくなりました。
でもまだ遅い
それでもWookieはまだNode.jsより少し遅い。そこで抜本的に改善するために一からHTTPパーサーを書きました。それが「fast-http」です。
twitter.comThough it's not completed yet, I published another library 'fast-http'. https://t.co/oyDBCeojG2 It's about 10 times faster than http-parse.
— fukamachi (@nitro_idiot) 2014年10月10日
リリース当時はhttp-parseの10倍高速でしたが、現在はさらに高速化されて120倍以上高速になっています。最終的にCで書かれたhttp-parser (Node.jsで使われているもの) よりも高速になったため、それをブログとしてまとめ、それなりの反響を得ました。
高速かつ抽象度の高いプログラム言語としてCommon Lispは十分に戦えそうだ、という実感を持った瞬間です。
上のエントリーからCommon Lisp界隈で「高速なプログラムを書こう」というムーブメントが起きたように思います。実際にCよりも速いコードが書けたぜ、っていうのはインパクトがあるのでしょうね。
でもまだまだまだまだ遅い
けれど、ここまでやってもWookieはNode.jsに勝てませんでした。
twitter.comHmm.. Wookie is still a little bit slower than Node.js. libevent2 might be a bottleneck of it?
— fukamachi (@nitro_idiot) 2014年10月10日
使っているイベントライブラリがlibevent2であることが問題なのだろうか?というツイートを残しており、Wookieの作者から「libuvバックエンド (Node.jsと同じもの) にするつもりだよ」とリプライがきています。
ここからまた面白くなるのですが、詳しくは明日のWooの回に取っておくとします。
おわりに
fast-httpはGitHubで公開されており、Starは252です。
明日のアドベントカレンダーは15日目のWooについてです。お楽しみに。
*1:Common Lispの非同期Webサーバー。当時はlibeventベース、現在はlibuvベース。