Расширяемое кеширование зависимостей - PullRequest
3 голосов
/ 06 декабря 2011

Я работаю над разработкой системы для вычисления и кеширования вероятностных моделей и ищу либо программное обеспечение, которое делает это (предпочтительно на R или Ruby), либо шаблон проектирования, который будет использоваться при реализации моего собственного.

У меня общий шаблон вида функции C зависит от выхода функции B, который зависит от выхода функции A. У меня есть три модели, называем их 1, 2 и 3. Модель 1 реализует A, B и C. Модель 2 реализует только C, а Модель 3 реализует A и C.

Я хотел бы иметь возможность получить значение 'C' из всех моделей с минимальным перерасчетом промежуточных шагов.

Чтобы сделать вещи менее абстрактными, простой пример:

У меня есть график зависимости, который выглядит так: A 1 является реализацией модели 1 A, а A 3 является реализацией модели 3 A. C зависит от B, а B зависит от A во всех моделях.

Model Dependencies

Действительные функции заключаются в следующем (опять же, это игрушечный пример, в действительности эти функции гораздо более сложны, и их вычисление может занять минуты или часы).

Functions

Значения должны быть следующими.

Values

Без кеширования это нормально в любых рамках. Я могу создать класс для модели 1 и сделать так, чтобы модель 2 расширяла этот класс, и чтобы A, B и C были функциями этого класса. Или я могу использовать структуру внедрения зависимостей, заменив модели 1 и А на модели 2. И аналогично для модели 3.

Однако у меня проблемы с кэшированием. Я хочу вычислить C на всех моделях, чтобы сравнить результаты.

Таким образом, я вычисляю C на модели 1 и кеширую результаты, A, B и C. Затем я вычисляю C на модели 2, и она использует ранее сохраненную в кэше версию B, поскольку она расширена с модели 2.

Однако, когда я вычисляю модель 3, мне не нужно использовать кэшированную версию B, поскольку, хотя функция одна и та же, функция, от которой она зависит, A отличается.

Есть ли хороший способ справиться с такого рода кэшированием с проблемой зависимости?

Ответы [ 3 ]

2 голосов
/ 06 декабря 2011

Ключом кеширования вызовов метода является знание того, где реализован метод.Вы можете сделать это, используя 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 голосов
/ 06 декабря 2011

В любом случае ... с этим, мой первый шаг - убедиться, что все функции A, B и C являются чистыми функциями, то есть прозрачными по ссылкам. Это должно помочь, потому что тогда вы будете знать, нужно ли пересчитывать кэшированное значение в зависимости от того, изменился ли ввод.

Говоря об этом, когда я вычисляю C1, ничего не вычисляется, так что вычисляйте все.

При вычислении C2 проверьте, нуждается ли B1 в обновлении. Таким образом, вы спрашиваете B1, нуждается ли оно в обновлении. B1 проверяет, изменился ли его вход, A2 изменился с A1. Это не так, и, поскольку все функционалы прозрачны по ссылкам, вы гарантированно, что если вход не изменился, то вывод будет таким же. Поэтому, поэтому использовал кешированную версию B1 для вычисления C2

При вычислении C3 проверьте, нуждается ли B1 в обновлении. Поэтому мы спрашиваем B1, нуждается ли оно в обновлении. B1 проверяет, изменился ли его вход A3 с A2, когда он в последний раз что-то вычислял. Он имеет, поэтому мы пересчитываем B1, а затем впоследствии пересчитываем C3.

Что касается внедрения зависимостей, в настоящее время я не вижу причин организовывать его по классам A, B и C. Я предполагаю, что вы хотите использовать шаблон стратегии, чтобы вы могли использовать перегрузку операций для того, чтобы оставьте алгоритм таким же, но варьируйте реализации.

Если вы, ребята, используете язык, который может передавать функции, я просто связал бы функции вместе с небольшим количеством склеивающего кода, который проверяет, должен ли он вызывать функцию или использовать кэшированное значение. И каждый раз, когда вам нужно другое вычисление, соберите все реализации алгоритма, который вам нужен.

0 голосов
/ 22 апреля 2012

В итоге мы создали собственный DSL на Ruby для поддержки этой проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...