八発白中

はてなブログに引越しました。

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宣言や関数のインライン化などもすればもっと速くなる余地がある状態でです。

これは当時の意気揚々としたツイートです。

高速に動くということの優位性

Common LispでWebアプリケーションが書けると言っても、じゃあ他と比べたときの優位性とはなんだろうという答えの一つがこれでした――Common Lispは速い。

これだけ抽象度の高い言語でありながらGoやC++と比べられるほどのパフォーマンスが出るのだから、これは強みになります。起業を考えていた自分はこの強みを活かして技術優位性としようと考えていました。

けれど、まだ道のりは始まったばかりです。

やっぱりCommon Lispは遅い?

河西くんTwitterで会話するようになったのはこの頃でした。先程のツイートを見てリプライをくれています。

twitter.com

Wookie *1がNode.jsのhttpモジュールよりも遅い、というのです。まじかよ、と思って実際に計測してみると、Node.jsはWookieよりも2倍も高速でした。

twitter.com

Wookieを高速化する

Common Lisp自体はNode.jsより遅い言語ではないというのはwebsocket-driverで知れたことです。ということは、何か遅い原因があるはずです。僕はWookieのソースコードを見てどうにかNode.jsより高速にしてやろうと決めました。

Wookieをプロファイリングすると内部で使っているHTTPパーサーのhttp-parseボトルネックとなっていることがわかりました。

twitter.com

中身を見てみると納得です。パーサー部分はナイーブに正規表現で行っていました。subseqなどの呼び出しも見られ、何度もメモリアロケーションがされているのがわかります。

このときの開発の状況はツイートによく残っています。http-parseのベンチマークを取って高速化してPull Requestを投げることを繰り返していると、じきにκeenも参加し始めました。

そして最終的にhttp-parseはWookieの最大のボトルネックではなくなりました。

でもまだ遅い

それでもWookieはまだNode.jsより少し遅い。そこで抜本的に改善するために一からHTTPパーサーを書きました。それが「fast-http」です。

twitter.com

リリース当時はhttp-parseの10倍高速でしたが、現在はさらに高速化されて120倍以上高速になっています。最終的にCで書かれたhttp-parser (Node.jsで使われているもの) よりも高速になったため、それをブログとしてまとめ、それなりの反響を得ました。

blog.8arrow.org

高速かつ抽象度の高いプログラム言語としてCommon Lispは十分に戦えそうだ、という実感を持った瞬間です。

上のエントリーからCommon Lisp界隈で「高速なプログラムを書こう」というムーブメントが起きたように思います。実際にCよりも速いコードが書けたぜ、っていうのはインパクトがあるのでしょうね。

でもまだまだまだまだ遅い

けれど、ここまでやってもWookieはNode.jsに勝てませんでした。

twitter.com

使っているイベントライブラリがlibevent2であることが問題なのだろうか?というツイートを残しており、Wookieの作者から「libuvバックエンド (Node.jsと同じもの) にするつもりだよ」とリプライがきています。

ここからまた面白くなるのですが、詳しくは明日のWooの回に取っておくとします。

おわりに

fast-httpはGitHubで公開されており、Starは252です。

明日のアドベントカレンダーは15日目のWooについてです。お楽しみに。

*1:Common Lispの非同期Webサーバー。当時はlibeventベース、現在はlibuvベース。

Day 13: websocket-driver

これは fukamachi products advent calendar 2016 の13日目の記事です。ようやく折り返し地点ですね。

今日はwebsocket-driverについて話します。

小さなライブラリではありますが、その後の話の発端となるライブラリなので紹介します。

WebSocket

Clackにより通常のHTTP通信を行うWebアプリケーションは作れるようになりました。しかし、よりリアルタイム性の強いもの、たとえばチャットやゲームのようなアプリケーションを作るには貧弱です。

たとえばWebSocket。TCP上のプロトコルであり、通信時の不要なオーバーヘッドが少なく効率的でリアルタイム性の高い通信プロトコルです。

ClackでWebSocketを使う方法はなく、使えるとすればHunchensocketというHunchentoot拡張くらいでした*1

Socket.IO

少しWebSocket周りの話もしておきます。

WebSocketの欠点は環境による通信状態の不安定さです。環境によってWebSocketが使えなかったり、使えてもすぐに切断されてしまったりします。この管理が難しくWebSocketの一般的な利用が妨げられているように思います。

この一つの解決策としてNode.jsのライブラリにSocket.IOというものがあります。

これはWebSocketが使えない環境のために通常のHTTP通信やHTTPのlong pollingなどで通信することもできます。最初はHTTPを試し、可能であればWebSocketに暗黙的にアップグレードします。ユーザー側もアプリケーション側も、何で通信しているのかということを意識せずに使えます*2

Socket.IOはプロトコルが公開されているので、Common Lispのサーバー実装を作れば公式のフロントエンド実装と通信できるはずです。

ではこれを作ろうと思いました。Common Lispは高速ですし非同期WebサーバーのWookieもあり、言語としての特性を活かせそうです。

これを作るためにはまずはWebSocketから。WebSocketのサーバー実装とクライアント実装を作りました。それが「websocket-driver」です。

EventEmitter

websocket-driverのインターフェイスとして参考にしたのがNode.jsのEventEmitterです。

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();
myEmitter.on('event', () => {
  console.log('an event occurred!');
});
myEmitter.emit('event');

onでイベントのコールバックを指定しておき、emitで登録されているイベントコールバックを呼ぶものです。

ClackがPlackから実装をコピーしたように、websocket-driverも真似ています。Common Lispならばメソッドを使ってhandle-messageなどでもいいではないか、と思う人もいるかもしれませんが、イベント名が固定でない以上はこうするしかありません。

馴染んでいるのか何なのかまだわかりませんが、これはClackでWebSocketを使える唯一のライブラリです。

おわりに

websocket-driverはGitHubで公開されており、Starは28です。

明日のアドベントカレンダーは14日目のfast-httpについてです。お楽しみに。

*1:しかし僕の意見ではこれも実用に耐えうるものではありません。Hunchentootはクライアント毎にスレッドを立ち上げるため同時接続数に制約があります。1サーバーにつき100ユーザー程度が限界でしょう。

*2:ちなみにこの部分はv1.0からEngine.IOという別プロダクトに分離されました。

Day 12: Qlot

これは fukamachi products advent calendar 2016 の12日目の記事です。

今日はQlotについて話します。

背景

はてなを退職することが決まってから、今までできていなかった開発時間を取り戻すかのようにCommon Lispを書き続けました。当時は起業する気でいたのですが、まだまだWebサービスを作るために足りないライブラリはいくつもあったからです。

ここから数日間のアドベントカレンダーのプロダクトはこの期間に作られたものです。どれも必要でありながら手がつけられていなかったものです。

Quicklispをプロジェクト毎に

Quicklisp のβリリースから3年以上経ち、もはやQuicklispはCommon Lispの開発にはなくてはならないものにまでなりました。

しかし、問題もあります。アップデートが月に一回なので最新のリリースを使えるようになるまで待たなければいけません。また、いくつかのライブラリだけを古いライブラリを使い続けるといったこともできません。使えるライブラリ群は毎月のQuicklispのdistリリース時のスナップショットであり、柔軟なバージョン指定などはありません。

さらにQuicklispのインストールはユーザーローカルであり、プロジェクトごとに分けることができません。このため複数のプロジェクトを同時に動かしたい場合にも共通のライブラリバージョンを使わなければいけません。お互いのプロジェクトに依存関係がなくとも。

そして、複数人で開発するときにも問題になります。ある人は最新のライブラリを、またある人は数ヶ月前のライブラリを使っているみたいな状況では同じ挙動と保証できません。

これらを解決するのが「Qlot」です。

まあ長々説明せずとも、Rubyで言うBundlerですって言えば重要度がわかる人も多いかと思います。

QlotではQuicklispの個別バージョン指定だけでなく、gitリポジトリやHTTPでのtarballからもインストールできます。

Qlotのやり方

説明するとシンプルな仕組みに思えるかもしれませんが、中では少し複雑なことをやっています。

Quicklispはライブラリを配信する機構だと思っている人も多いかもしれませんが、実はQuicklispのdistを自分で作ってインストールすることもできます。フォーマットさえ合っていればinstall-distupdate-distでQuicklispではないライブラリ群をQuicklisp経由でインストールさせることが可能です。

Qlotはこの仕組みを使っています。指定されたgitリポジトリなどを一つのQuicklisp distにしてしまい、ローカルでClackサーバーを立ち上げてinstall-distをします。

ここまでしてQuicklispに寄せる必要があったかは作ってみては疑問ですが、依存関係の解決やdist間の優先順位付けなどはQuicklispの標準機構を使えるのでそれほど悪くない設計だと思います。

ASDF拡張という可能性の否定

たまに、独立したqlfileではなくASDF:depends-onに書けたほうがいいのではないかという意見を聞きます。そのほうが依存関係を明示する場所が1ヶ所になるというメリットがありますが、ASDファイルにそのような情報を記述するのは難しいです。

おそらく指定のgitリポジトリを指定するだけを想定しているかもしれませんが、ではそのgitリポジトリが更新されたときはどうでしょうか。ロードする度に使うバージョンが結局固定できません。つまり、qlfile.lockに当たるものがないのです。

リビジョンまで指定してしまえばいけないことはないかもしれません。けれど更新が面倒ですし、ASDファイルのロード時にどのQuicklisp distを使うかという情報が決まらないならばどのバージョンがロードされるかは環境依存になってしまい危険です。

おわりに

QlotはGitHubで公開されており、Starは78です。

Common Lispでチーム開発をするところやデプロイの必要なWebサービスなどを作っているところでしか必要にならないのでそれほど使われている印象もなかったのですが、最近はプロキシ対応のPRがきたりして意外にもこっそり使われているのかもしれません。

明日のアドベントカレンダーは13日目のwebsocket-driverについてです。お楽しみに。

Day 11: Integral

これは fukamachi products advent calendar 2016 の11日目の記事です。

今日はIntegralについて話します。

このライブラリは現在非推奨ですが、歴史として重要なので取り上げます。

Common Lispとデータベース

Cavemanを作ってから課題に感じていたデータベースとの連携についてですが、それまでのCommon Lispコミュニティにもいくつかプロダクトがありました。

一つはElephant。これはバックエンドがBerkeley DBであることが想定された設計で、CLOSオブジェクトを永続化できるという高度な抽象化を行なっています。欠点はBerkeley DBがフリーなライセンスではないこと。バックエンドとしてMySQLPostgreSQLも選べますが、実行効率は落ちるでしょう。

さらにはRucksack。これもElephantのような永続化ライブラリですが、Common Lispで書かれており依存する外部ライブラリがありません。ただ、開発は止まっており、データベースとしての質にも問題があるようです。

そしてPostmodern。これはいわゆるO/Rマッパーであり、抽象レベルは先の二つよりも低いです。PostgreSQLしか対応していませんがメンテナンスは続いているようですし、コードの質も高く人気もあります。

これらに比べると7日目に紹介したCL-DBIなどはそのずっと後継です。抽象度もずっと低く、文字列をSQLで投げるだけです。

その後SQLをS式にするSxQLを作り、データベース連携の最後のピースとなるのが、今日紹介するのが「Integral」です。

なぜ僕がまた新たにデータベースライブラリを書き揃えることになったかというのは7日目の記事を見てもらうとして、今回はIntegralの設計思想について話をします。

Integralの見た夢

Common Lispが持つ新たなデータベースライブラリはどうあるべきか。

CLOSという他の言語よりも高度なオブジェクトシステムがあるのだからこれを利用しないわけにはいきません。

とはいえ、MySQLPostgreSQLのような人気のRDBMSをバックエンドとするならElephantほどの永続化機構は作れないでしょう。

マイグレーション機構も重要です。他の言語では当たり前になっている一方で、Common Lispでは標準でマイグレーション機構を持つものがありません。

つまりは、Elephantのような使いやすいインターフェイスだが、一般的なRDBMSをバックエンドとして使え、抽象度が高すぎず、マイグレーション機構を持つもの。これがIntegralが満たすべき条件でした。

ちょうどはてなも退職しようかという頃で京都から関東に戻ってきており、これからCommon Lispでガンガン開発しようという冬休みでした。

公開後はLisp Meetup #13で紹介してブログエントリーを公開し、Shiroさんにコメントをもらったりしてそれなりに悪くない評価でした。

twitter.com

マクロレス主義

少し話は逸れますが、重要な話なのでここで触れておくことにします。

Lispの代表的な機能としてよく挙げられるマクロですが、同時に「本当に必要なときしかマクロは使うべきではない」とも言われています。

僕も同意です。実はマクロを使うことよりもマクロを使わないことのほうが難しいんじゃないかと思っているくらいです。

ただし、マクロを使うべきではない理由として「読みづらくなる」ということを挙げている人がいますが、僕はこれには違う意見です。それもありますがより重要なのは、マクロを使うと抽象化レイヤーを適切に分離したり組み合わせたりすることができないことです*1。マクロは関数呼び出しのように他の関数に渡すことはできませんし、マクロの引数を実行時に評価したいなどというときに不便になります。

具体例を挙げるならばSxQLです。SxQLのAPIの多くはマクロが使われています。

(alter-table :tweet
  (add-column :id :type 'bigint :primary-key t :auto-increment t :first t)
  (add-column :updated_at :type 'timestamp))
;=> #<SXQL-STATEMENT: ALTER TABLE tweet ADD COLUMN id BIGINT AUTO_INCREMENT PRIMARY KEY FIRST, ADD COLUMN updated_at TIMESTAMP>

ではもしここで、add-columnのリストを動的に作り、それをalter-tableに渡したいというときはどうでしょう。関数ならばapplyするだけでいいのですが、マクロなのでそれはできません。

SxQLにはその場合のためにより低レイヤーのmake-statementを使うことができます。実際、マクロを展開すると同じであることがわかります。

;; 上のalter-tableマクロの展開式
(make-statement :alter-table :tweet
                (add-column :id :type 'bigint :primary-key t :auto-increment t
                            :first t)
                (add-column :updated_at :type 'timestamp))

マクロは簡易なインターフェイスの提供のために使い、そこから下のレイヤーではマクロは使わない。これならば察しの良いユーザは薄い抽象化レイヤーであることに気づいて安心してハックするでしょうし、簡単なことはマクロで済ませられます。

こういったことは昨日のSxQLの記事に書けばよかったですね。

この思想はClackの開発当時から持っていたもので、当然Integralもこの思想に沿っています。Integralでは特にCLOSとMOP (Meta Object Protocol)を多用して実現しています。MOPはすごいといいつつ実際使うことのほとんどない技術の一つですが、Integralやその後継のMitoでは積極的に使っています。興味があれば実装もご覧ください。

Crane

ちょうど同時期に開発されたO/Rマッパーに「Crane」があります。リリースしたあとに「あ、作ってたんだ」って感じでTwitterで会話しました。

twitter.com

結局APIも設計も異なるのでプロジェクトマージはできずに、今でもCompeteは続いています。

抽象化の失敗

さて、Integralを作ってしばらく開発を続け、ついに現職のサムライト株式会社でも導入されました。けれどもすぐに現在の設計では抽象化が不十分であることに気づかされることになります。

特にMySQLPostgreSQLの実装の違いを過小評価しており、PostgreSQLの対応が完全にはできないことがわかりました。

あまりに致命的だったためやむなくPostgreSQLをサポート対象から外し、MySQLとSQLite3をメインターゲットとして公開し続けています。

後継のMitoは主にこの問題を解決するために作られたプロジェクトです。24日目に紹介しますのでそのときに詳しくお話します。

おわりに

IntegralはGitHubで公開されていますが、非推奨なので新規のプロジェクトでは使わないでください。

明日のアドベントカレンダーは12日目のQlotについてです。お楽しみに。

*1:他にも学習コストが上がるという欠点もマクロにはあります。ライブラリ独自のマクロを覚えるより、既に知ってる標準の機能を組み合わせたほうが馴染み深く扱いやすいです。

Day 10: SxQL

これは fukamachi products advent calendar 2016 の10日目の記事です。

今日はSxQLについて話します。

Common LispのWeb開発に足りないもの

Cavemanをリリースしてから継続的な課題として考えていたのは、Common LispRDBMSとの連携が貧弱なことでした。CL-DBI (7日目参照) はできましたが、未だにSQLは文字列で書いており抽象度が足りません。

O/Rマッパーに相当するものが最終的に欲しいのですが、一足飛びには行きません。そこでまず何が必要かというと、やはりSQLを抽象化してCommon Lispで扱いやすくすることでしょう。文字列ではなくS式で書けるSQLビルダーが理想です。

SxQL

SQLのS式表現としては以前からs-sqlというものがありました。ただしこれはPostmodernに付属しており、名前に反してPostgreSQLで使うことを意図しています。

使えないこともないのですがPostmodernにバンドルされているというのも気に入りません。新しいO/Rマッパーを作ったとしても、依存ライブラリがPostmodernなんてマヌケな話になりかねません。

そこで例によって新しく作ることにしました。それが「SxQL」です。

Optimaと設計

当時、友人の@m2ymさんがOptimaというCommon Lispのパターンマッチングライブラリを公開して話題になっていました。

実を言うとSxQLの実装はOptimaの実装を真似しています。Optimaは各パターンを構造体で定義しており、提供するAPIはマクロでラップしています。こういう設計にするとインターフェイス部分のマクロが無駄に複雑にならないですし、裏側を知っている人はパターンが自由にプログラム可能という利点があります。

(select :* (from :user) (order-by :created_at))
;=> #<SXQL-STATEMENT: SELECT * FROM user ORDER BY created_at>

;; 単純な関数呼び出しに展開される
(macroexpand-1 '(select :* (from :user) (order-by :created_at)))
;=> (MAKE-STATEMENT :SELECT (FIELDS :*) (FROM :USER) (ORDER-BY :CREATED_AT))
;   T

また、SxQLはSQLをclauseやstatementなどに分離し、それを組み合わせることで最終的に文字列を作り出すという仕組みになっています。

たとえばSELECTFROM部分だけを作ることもできます。

(from :user)
;=> #<SXQL-CLAUSE: FROM user>

これによりWHERE部分だけを作っておいていくつかのSELECT文で使いまわしたりすることも可能になっています。

おわりに

SxQLはGitHubで公開されており、現在Starは81です。

明日のアドベントカレンダーは11日目のIntegralについてです。お楽しみに。