鳩舎

レースしない

extend / unextend を使った際の問題

uninclude を使って DCI をやるとして、現実的なのかという話になってくるのだけど、これがまた曲者だなぁと思っていたり。

コード的な可読性とかは一旦置いといて、速度はどうなのってことだけとりあえず考えた。

現状、 extend 及び unextend はそれを実行した瞬間に Ruby VM 上のメソッドキャッシュを全部飛ばす(これ認識あってるかな、多分あってる)全部というのは本当に全部で、

class A; end
class B; end
module C; end

a = A.new
b = B.new
b.extend(C)

としたとき、 A のクラスのメソッドキャッシュすら飛ぶはず(認識違いかもしれないけど)。これは結構バカにならなくて、問題だと思う。で、 SimpleDelegator や Forwardable を使ってラッパーをはさめばいいだろうという結論にいたって、ベンチを走らせてみたのだが、まぁ一枚層が重なる訳なので、これもまた遅くなる。というのも、オブジェクトの数が単に2倍(実オブジェクトとロール用のメソッドを持ったラッパーオブジェクトで2つ)という具合に増えるし、メソッド呼び出しもラッパーオブジェクトのメソッドから元オブジェクトのメソッドを呼び出すといった有様で、なかなかどうして厄介な話だ。

試しに

  • SimpleDeleagator
  • Forwardable
  • Unextendable(super が使えないので邪道っぽくはあるが)
  • uninclude

の4つでオブジェクト生成数/メソッド呼び出し数を 1000/1000 と 10/1000000 と 1000000/10 でベンチスコアと取ってみたけど、どれでも基本的に成績がいいのは uninclude で、ついで成績がいいのが Forwardable という結果に落ち着いた。

メソッド呼び出し回数が増えると SimpleDelegator は目に見えて成績が悪くなり、オブジェクト生成数が増えると unextendable の成績が目に見えて悪くなった。どのベンチでも良いスコアを出し続けたのは uninclude で、 オブジェクト生成数が多い時は些少だけど、 Forwardable のほうが uninclude より成績がよかった。オブジェクト生成数とメソッド呼び出し回数が同値の 1000 だった時だけは unextendable が一番スコアが良かった。というまぁなんというか SimpleDelegator 以外の3すくみみたいな状態である。

でも多分 Rails アプリとかに乗せると uninclude の成績はがくっと落ちるんじゃないかなぁ……試してないからわからないけど、そんな予感がしてる。

Ruby のメソッドキャッシュが extend で全部飛ばないようになるとかそういう改良が必要な気がしています。 C のコード全然書けないから自分で改良するのは大変そうだけど、ちまちま読んでみるのもいいかもしれないなぁ……