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

Constant Summary collapse

Escape =
{:to_str  => true,
:to_ary  => true,
:to_hash => true}

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)

    ブロック(省略可)


125
126
127
128
129
130
131
132
133
134
135
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
# File 'lib/when_exe/parts/method_cash.rb', line 125

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 =~ /^(_*)(.+?)_to_(.+)$/) && 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
          end
        }
      end
      key = _key_simplefy(args)
      inv = send("#{prefix}#{from}_to_#{to}_", *args)
      @_m_cash_ ||= {}
      @_m_cash_["#{prefix}#{to}_to_#{from}"]    ||= {}
      @_m_cash_["#{prefix}#{from}_to_#{to}"]    ||= {}
      @_m_cash_["#{prefix}#{to}_to_#{from}"][_key_simplefy(inv)] = args
      @_m_cash_["#{prefix}#{from}_to_#{to}"][key] = inv
      return inv
    ensure
      @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
    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
        end
      }
      key = _key_simplefy(args)
      @_m_cash_ ||= {}
      @_m_cash_["#{name}"] ||= {}
      if (respond)
        return send("#{name}_setup", key, *args)
      else
        return(@_m_cash_["#{name}"][key] ||= send("#{name}_", *args))
      end
    ensure
      @_m_cash_lock_ && @_m_cash_lock_.locked? && @_m_cash_lock_.unlock
    end
  end
end

Class Attribute Details

.directBoolean

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

Returns:

  • (Boolean)

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


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

def direct
  @direct
end

Class Method Details

._setup_(direct = false, escape = false) ⇒ void

Note:

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

This method returns an undefined value.

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

Parameters:

  • direct (Boolean) (defaults to: false)

    '_' で終わるメソッドをキャッシュせずに毎回計算する場合 true, キャッシュする場合 false/nil

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

    毎回 method_missing を発生させるメソッドを true にして指定

  • escape (false, nil) (defaults to: false)

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

  • escape (true) (defaults to: false)

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


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

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

.escape(method) ⇒ boolean

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

Parameters:

  • method (Symbol)

    メソッドシンボル

Returns:

  • (boolean)

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


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

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

Instance Method Details

#method_missing_Object


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

alias :method_missing_ :method_missing