#extend が遅い問題
ここで「#extend 遅い!DCI 狂ってる!」というような話があった。
DCI の例でよく出てくる Object#extend でメソッドを呼び出すとすげー遅いね、どうすんのこれという話で、じゃあまぁ妥当にモデルにいろんなものを include したほうがいいんじゃねという話になってくる。
それも一個の解決方法だと思うし、いいと思うんだけど、僕としては実装を別のところに切り出しておきたいので、どうするか考えた結果、 SimpleDelegator とか使って Delegate すればいいじゃないかと思って実装してみたんだけど、 SimpleDelegator って言ってみれば method_missing なのでそれもまぁ重い。
んで Forwardable で実装すればもうちょい早いのではみたいなことでそうやってみた。
結果はこんな感じ(僕の実装は Dicer)
benchmark: source code
instance method 890341.4 (±11.2%) i/s - 4417656 in 5.026077s with Delegate 342923.5 (±6.8%) i/s - 1712409 in 5.017309s with Forwardable 448447.5 (±6.5%) i/s - 2236027 in 5.009544s with #extend 210582.5 (±14.2%) i/s - 1040944 in 5.044065s with singleton 183500.3 (±15.2%) i/s - 901362 in 4.999332s with Dicer 397806.9 (±5.4%) i/s - 1986166 in 5.008770s
確かに SimpleDelegator や #extend よりは早くなったんだけど、通常のメソッド呼び出しに比べると半分以下くらいの速度しか出てない。
で、上のベンチマークはインスタンスを生成するところからやってるんだけど、インスタンス生成時間を抜きにして、単純にメソッドコールの呼び出し速度だけベンチとったのが以下
benchmark: source code
instance method 1517251.5 (±13.6%) i/s - 7452115 in 4.995762s with Delegate 638177.4 (±8.1%) i/s - 3168640 in 5.002841s with Forwardable 847826.7 (±14.6%) i/s - 4147938 in 5.011163s with #extend 1405662.8 (±20.6%) i/s - 6549998 in 5.006460s with singleton 1538310.4 (±21.7%) i/s - 7356960 in 5.002528s with Dicer 812213.2 (±10.0%) i/s - 4028684 in 5.014841s
これだけ見ると特異メソッドが最速ですごい!!という感じになってくる。#extend だって全然、むしろ速いほうだ。つまるところインスタンスの生成コストが高いという話だろう。ベンチのとり方が間違ってたので修正。特異メソッドと #extend 速い!
Rails などの Web アプリケーションで使うことを考えると、インスタンスの生成コストが高いのはいただけないので、 #extend や特異メソッドの選択肢はちょっと避けたくなってくる。 SimpleDelegator はどちらも遅いので論外。となってくると Forwardable などの選択肢を取ることになってくるかなぁという感じ。
ともあれそこそこ動く Dicer が出来上がったので、もうちょっと煮詰めたらリリースしたい。ORM は ActiveRecord しかサポートしてない状況なので、せめて Mongoid くらいはサポートしたいなぁ。
Dicer のリポジトリはこちら。使い方などはこのへんを見てもらえるといいかなと思う。ちょっと黒魔法が多すぎるので削るかどうか悩んでる。