可扩展的依赖性缓存

时间:2011-12-06 00:47:39

标签: ruby r design-patterns dependency-injection machine-learning

我正在开发一个用于计算和缓存概率模型的系统,我正在寻找能够实现这一目标的软件(最好用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>

Model Dependencies

实际功能如下(再次,这是一个玩具示例,实际上这些功能要复杂得多,计算时间可能需要几分钟到几个小时。)

Functions

值应如下所示。

Values

没有缓存,这在任何框架中都可以。我可以为模型1创建一个类,并使模型2扩展该类,并使A,B和C成为该类的函数。或者我可以使用依赖注入框架,将模型1的A和C替换为模型2。类似于模型3。

但是我遇到了缓存问题。我想在所有模型上计算C,以便比较结果。

所以我在模型1上计算C,并缓存结果A,B和C.然后我在模型2上计算C,并且它使用之前的缓存版本B,因为它是从模型2扩展的。 / p>

然而,当我计算模型3时,我不需要使用B的缓存版本,因为即使函数是相同的,它依赖的函数A也是不同的。

有没有一种很好的方法来处理这种带依赖性问题的缓存?

3 个答案:

答案 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来支持这个问题。