Module: When::Parts::MethodCash

Included in:
Coordinates::Temporal, Ephemeris::Formula
Defined in:
lib/when_exe/parts/method_cash.rb

Overview

メソッドの実行結果をキャッシュし処理の高速化を行う

fn というメソッドをキャッシュ化

* fn ではなく fn_ を定義しておく
* fn メソッドが呼ばれると fn_ を実行し、{引数=>戻り値} を Hash に記憶する
* 同じ引数で再度 fn メソッドが呼ばれると Hash から戻り値を取り出して返す

a_to_b と b_to_a という互いに逆関数のメソッドをキャッシュ化

* a_to_b ではなく a_to_b_ , b_to_a ではなく b_to_a_ を定義しておく
* a_to_b メソッドが呼ばれると a_to_b_ を実行し、{引数=>戻り値}, {戻り値=>引数}を Hash に記憶する
* 同じ引数で再度 a_to_b メソッドが呼ばれると Hash から戻り値を取り出して返す
* b_to_a メソッドが呼ばれ Hash に戻り値があれば Hash から戻り値を取り出して返す

特記事項

Argument identification

The eql? method of When::TM::(Temporal)Position is not overridden.
It seems that the cost of identification of the argument exceeds the merit of the method cash.
I do not recommend applying the methodcash to the method which takes
When::TM::(Temporal)Position as an argument.

Multi-thread critical situation

There is a problem in consistency of hash when this function is used in multi-thread environment.
If the initialize method sets Mutex in instance variable @_m_cash_lock_,
this function gives up use of hash in the critical situation.

class Foo
  include MethodCash

  def initialize
    ...
    @_m_cash_lock_ = Mutex.new
    ...
   end
end

参考 http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-list/47663

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(name, *args, &block) ⇒ void

This method returns an undefined value.

最初に発生する method_missing で、キャッシュ機能を登録する

Parameters:

  • name (Symbol)

    メソッド名

  • args (Array)

    メソッド引数

  • block (block)

    ブロック(省略可)



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
# File 'lib/when_exe/parts/method_cash.rb', line 136

def method_missing(name, *args, &block)

  return method_missing_(name, *args, &block) unless respond_to?("#{name}_", true)
  return send("#{name}_",  *args, &block) if MethodCash.direct

  if ((name.to_s =~ /\A(_*)(.+?)_to_(.+)\z/) && respond_to?("#{$1}#{$3}_to_#{$2}_", true))
    prefix, from, to = $~[1..3]
    begin
      if @_m_cash_lock_
        return send("#{prefix}#{from}_to_#{to}_", *args, &block) unless @_m_cash_lock_.try_lock
        unlock = "ensure ; @_m_cash_lock_.locked? && @_m_cash_lock_.unlock"
      end
      [[from, to],[to, from]].each do |pair|
        a, b = pair
        lock = @_m_cash_lock_ ? "  return #{prefix}#{a}_to_#{b}_(*args) unless @_m_cash_lock_.try_lock" : ''
        instance_eval %Q{
          def #{prefix}#{a}_to_#{b}(*args)
            key = _key_simplefy(args)
            inv = @_m_cash_["#{prefix}#{a}_to_#{b}"][key]
            return inv if inv
            begin
            #{lock}
              inv = #{prefix}#{a}_to_#{b}_(*args)
              @_m_cash_["#{prefix}#{b}_to_#{a}"][_key_simplefy(inv)] = args
              @_m_cash_["#{prefix}#{a}_to_#{b}"][key] = inv
              return inv
            #{unlock}
            end
          rescue ThreadError
          end
        }
      end
      key = _key_simplefy(args)
      inv = send("#{prefix}#{from}_to_#{to}_", *args)
      @_m_cash_ ||= Hash.new {|hash,key| hash[key]={}}
      @_m_cash_["#{prefix}#{to}_to_#{from}"][_key_simplefy(inv)] = args
      @_m_cash_["#{prefix}#{from}_to_#{to}"][key] = inv
      return inv
    ensure
      begin
        @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
      rescue ThreadError
      end
    end

  else
    begin
      respond = respond_to?("#{name}_setup", true)
      setup   = respond ? "#{name}_setup(key, *args)" :
                          "(@_m_cash_[\"#{name}\"][key] = #{name}_(*args))"
      if @_m_cash_lock_
        return send("#{name}_", *args, &block) unless @_m_cash_lock_.try_lock
        lock   = "  return #{name}_(*args) unless @_m_cash_lock_.try_lock"
        unlock = "ensure ; @_m_cash_lock_.locked? && @_m_cash_lock_.unlock"
      end
      instance_eval %Q{
        def #{name}(*args)
          key = _key_simplefy(args)
          ret = @_m_cash_["#{name}"][key]
          return ret if ret
          begin
          #{lock}
            return #{setup}
          #{unlock}
          end
        rescue ThreadError
        end
      }
      key = _key_simplefy(args)
      @_m_cash_ ||= Hash.new {|hash,key| hash[key]={}}
      if respond
        return send("#{name}_setup", key, *args)
      else
        return(@_m_cash_["#{name}"][key] ||= send("#{name}_", *args))
      end
    ensure
      begin
        @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
      rescue ThreadError
      end
    end
  end
end

Class Attribute Details

.directBoolean

'_' で終わるメソッドをキャッシュせずに毎回計算するか否か

Returns:

  • (Boolean)

    キャッシュしない true, キャッシュする false/nil



66
67
68
# File 'lib/when_exe/parts/method_cash.rb', line 66

def direct
  @direct
end

Class Method Details

._setup_(options = {}) ⇒ void

Note:

When::TM::Calendar クラスの一部はキャッシュ使用を前提としているため :direct=>true では動作しません

This method returns an undefined value.

When::Parts::MethodCash のグローバルな設定を行う

Parameters:

  • options (Hash) (defaults to: {})

    以下の通り

Options Hash (options):

  • :direct (Boolean)

    '_' で終わるメソッドをキャッシュせずに毎回計算するか否か

  • :escape (Hash{Symbol=>boolean})

    毎回 method_missing を発生させるメソッドを true にする

  • :escape (false, nil)

    to_str, to_ary, to_hash のみ毎回 method_missing を発生させる

  • :escape (true)

    すべて毎回 method_missing を発生させる



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/when_exe/parts/method_cash.rb', line 80

def _setup_(options={})
  @_setup_info = options
  @direct      = options[:direct]
  case options[:escape]
  when true
    instance_eval %Q{
      def escape(method)
        true
      end
    }
  when Hash
    @escape = Escape.merge(options[:escape])
    instance_eval %Q{
      def escape(method)
        @escape[method]
      end
    }
  else
    instance_eval %Q{
      def escape(method)
        Escape.key?(method)
      end
    }
  end
end

._setup_infoHash

設定情報を取得する

Returns:

  • (Hash)

    設定情報



110
111
112
# File 'lib/when_exe/parts/method_cash.rb', line 110

def _setup_info
  @_setup_info ||= {}
end

.escape(method) ⇒ boolean

method_missing メソッドを forward するか否か

Parameters:

  • method (Symbol)

    メソッドシンボル

Returns:

  • (boolean)

    true - しない, false/nil - する



120
121
122
# File 'lib/when_exe/parts/method_cash.rb', line 120

def escape(method)
  Escape.key?(method)
end

Instance Method Details

#method_missing_Object



125
# File 'lib/when_exe/parts/method_cash.rb', line 125

alias :method_missing_ :method_missing