鳩舎

レースしない

#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 のリポジトリこちら。使い方などはこのへんを見てもらえるといいかなと思う。ちょっと黒魔法が多すぎるので削るかどうか悩んでる。