以下の記事を読むと、DCIはRubyでは遅いという指摘があったので、移譲で
実装すればいいと思ったので、下記記事のサンプルコードをもとに、
send/method_missing, forwardable, delegateを使ったサンプルを書いて
みました。

※ 上記記事では移譲を使ったアプローチにも言及しているし、改善するには
頑張らないといけないという程度の話と認識しました。

■ ベンチマークの結果(ruby 1.9.3p194)

         without dci  3024223.4 (±1.7%) i/s -   15139474 in   5.007655s
            with dci   768491.0 (±6.6%) i/s -    3833440 in   5.011732s
      with dci(send)  1586461.1 (±2.1%) i/s -    7977320 in   5.030680s
with dci(forwardable)
                      1712079.6 (±1.8%) i/s -    8580546 in   5.013597s
  with dci(delegate)  1134862.9 (±1.3%) i/s -    5701904 in   5.025205s


見にくくて済みません。コードは下の方にあります。

■ 思ったこと

元記事のベンチマークは、オブジェクトの生成コストくらいしかないような
処理にextendを突っ込んでいるため、そりゃ比較にならないだろうという
問題があります。

そもそも、処理が無いならDCIを入れる意味なんてほとんどないことを考えれば、
ここのコストは無視してもいいものなのですが、無視してもいいコストをいかに
削るかという、無駄に挑戦することも一興かなと思い、記事にしました。

fooメソッドの処理内容によっては(※)、extendを使った元記事のアプローチの
方が優秀な場合もあると思います。柔軟性などを考えれば、移譲の方が使い勝手が
いいだろうとも思いますが。

※ 単純な移譲にならずに、Double Dispatchになるとオーバーヘッドで遅くなる
可能性もあると思います。

さて、ベンチマーク結果に移ると、forwardableが意外と速い。先に書いた通り、
オブジェクト生成コストしかないような処理に対して、半分程度の速度低下で
DCIが実現できるということです。これは、RubyにおけるDCIの実用性を示す
ものではないでしょうか。

■ 余談

これ、DCIじゃなくてRoleObjectじゃんって突っ込みが入りそうですが、
DCIで設計したらRoleObjectで実装するのが定番かなと思ってます。あとは、
Decoratorかな。やったことないけど。

しかし、皆DCIに突っ込んでいくとか、勇敢すぎると思う。Coplienの名前が
出たら全力で逃げるところだろう。マルチパラダイムデザインの恐怖を
忘れたんだろうか。。。(ネタですよ。念のため。)

■ ソースコード

require 'rubygems'
require 'benchmark/ips'
require 'forwardable'
require 'delegate'

class ExampleClass
  def foo; 42; end
end

module ExampleMixin
  def foo; 43; end
end

class ExampleClassSend
  def initialize(role)
    @role = role
  end
  
  def method_missing(action, *args)
    @role.__send__(action, *args)
  end
end

class ExampleClassForwardable 
  extend Forwardable
  
  def initialize(role)
    @role = role
  end
  
  def_delegator :@role, :foo, :foo
end

class ExampleClassDelegate < SimpleDelegator
end

class ExampleRole
  def foo
    43; 
  end
end

@role = ExampleRole.new

Benchmark.ips do |bm|
  bm.report("without dci") { ExampleClass.new.foo }
  
  bm.report("with dci") do
    obj = ExampleClass.new
    obj.extend(ExampleMixin)
    obj.foo
  end
  
  bm.report("with dci(send)") do
    ExampleClassSend.new(@role).foo
  end
  
  bm.report("with dci(forwardable)") do
    ExampleClassForwardable.new(@role).foo
  end
  
  bm.report("with dci(delegate)") do
    ExampleClassDelegate.new(@role).foo
  end
end


Posted by あかさた
最近のエントリ
最近の読書メモ