Module: When::Parts::MethodCash

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

Overview

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

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

* fn 

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

* a_to_b 

特記事項

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

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 で、キャッシュ機能を登録する


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

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


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 のグローバルな設定を行う


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 するか否か


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