我正在开发一个用于计算和缓存概率模型的系统,我正在寻找能够实现这一目标的软件(最好用R或Ruby)或者在我实现自己的时候使用的设计模式。
我有一个形式函数C的一般模式取决于函数B的输出,它取决于函数A的输出。我有三个模型,称它们为1,2和3.模型1实现A,B和C.模型2只实现C,模型3实现A和C.
我希望能够从所有模型中获得值'C',而对中间步骤的重新计算最少。
为了减少抽象,一个简单的例子:
我有一个依赖图,看起来像这样: 1 是模型1的A实现,A 3 是模型3的实现A. C依赖于B,B在所有模型中依赖于A. / p>
实际功能如下(再次,这是一个玩具示例,实际上这些功能要复杂得多,计算时间可能需要几分钟到几个小时。)
值应如下所示。
没有缓存,这在任何框架中都可以。我可以为模型1创建一个类,并使模型2扩展该类,并使A,B和C成为该类的函数。或者我可以使用依赖注入框架,将模型1的A和C替换为模型2。类似于模型3。
但是我遇到了缓存问题。我想在所有模型上计算C,以便比较结果。
所以我在模型1上计算C,并缓存结果A,B和C.然后我在模型2上计算C,并且它使用之前的缓存版本B,因为它是从模型2扩展的。 / p>
然而,当我计算模型3时,我不需要使用B的缓存版本,因为即使函数是相同的,它依赖的函数A也是不同的。
有没有一种很好的方法来处理这种带依赖性问题的缓存?
答案 0 :(得分:2)
无论如何......有了这个,我的第一个传递就是确保函数A,B和C都是纯函数,也就是引用透明。这应该有所帮助,因为那时你会知道是否重新计算缓存值取决于输入是否已经改变。
所以说说通过,当我计算C1时,没有任何计算,所以计算一切。
计算C2时,检查B1是否需要更新。所以你问B1是否需要更新。 B1检查其输入A2是否已从A1更改。它没有,并且因为所有的功能都是引用透明的,所以你保证如果输入没有改变,那么输出是相同的。因此,使用B1的缓存版本来计算C2
计算C3时,检查B1是否需要更新。所以我们问B1是否需要更新。 B1检查其输入,A3是否已从A2更改,上次计算的是什么。它有,所以我们重新计算B1,然后重新计算C3。
至于依赖注入,我目前没有理由在A,B和C类下组织它。我猜你想要使用策略模式,这样你就可以使用操作重载了保持算法相同,但改变实现。
如果你们使用的语言可以传递函数,我会简单地将函数与一些粘合代码链接在一起,以检查它是应该调用函数还是使用缓存值。每次需要不同的计算时,请重新组合所需的算法的所有实现。
答案 1 :(得分:2)
缓存方法调用的关键是知道方法的实现位置。您可以使用UnboundMethod#owner
执行此操作(您可以使用Module#instance_method
并传入符号来获取未绑定的方法)。使用这些将导致这样的事情:
class Model
def self.cache(id, input, &block)
id = get_cache_id(id, input)
@@cache ||= {}
if !@@cache.has_key?(id)
@@cache[id] = block.call(input)
puts "Cache Miss: #{id}; Storing: #{@@cache[id]}"
else
puts "Cache Hit: #{id}; Value: #{@@cache[id]}"
end
@@cache[id]
end
def self.get_cache_id(sym, input)
"#{instance_method(sym).owner}##{sym}(#{input})"
end
end
class Model1 < Model
def a
self.class.cache(__method__, nil) { |input|
1
}
end
def b(_a = :a)
self.class.cache(__method__, send(_a)) { |input|
input + 3
}
end
def c(_b = :b)
self.class.cache(__method__, send(_b)) { |input|
input ** 2
}
end
end
class Model2 < Model1
def c(_b = :b)
self.class.cache(__method__, send(_b)) { |input|
input ** 3
}
end
end
class Model3 < Model2
def a
self.class.cache(__method__, nil) { |input|
2
}
end
def c(_b = :b)
self.class.cache(__method__, send(_b)) { |input|
input ** 4
}
end
end
puts "#{Model1.new.c}"
puts "Cache after model 1: #{Model.send(:class_variable_get, :@@cache).inspect}"
puts "#{Model2.new.c}"
puts "Cache after model 2: #{Model.send(:class_variable_get, :@@cache).inspect}"
puts "#{Model3.new.c}"
puts "Cache after model 3: #{Model.send(:class_variable_get, :@@cache).inspect}"
答案 2 :(得分:0)
我们最终用Ruby编写了自己的DSL来支持这个问题。