Когда использовать memoization в Ruby on Rails

в середине июля 2008 года в Rails core была добавлена Memoization. Демонстрация использования здесь.

Я не смог найти хороших примеров того, когда методы должны быть записаны, и последствия для производительности каждого из них. этот блог, например, предполагает, что часто memoization вообще не следует использовать.

для чего-то, что потенциально может иметь огромные последствия для производительности, кажется, мало ресурсов это выходит за рамки простого учебника.

кто-нибудь видел memoization, используемые в своих собственных проектах? Какие факторы заставили бы вас рассмотреть возможность запоминания метода?


после проведения еще нескольких исследований самостоятельно я обнаружил, что memoization используется замечательное количество раз внутри Rails core.

вот пример: http://github.com/rails/rails/blob/1182658e767d2db4a46faed35f0b1075c5dd9a88/actionpack/lib/action_view/template.rb.

это использование, похоже, идет вразрез с выводами блога выше, что найденная memoization может повредить производительности.

3 ответов


Я думаю, что многие разработчики Rails не полностью понимают, что делает memoization и как она работает. Я видел, как он применялся к методам, которые возвращают ленивые загруженные коллекции (например, набор данных Sequel), или применялся к методам, которые не принимают аргументов, но вычисляют что-то на основе переменных экземпляра. В первом случае мемуаризация-это накладные расходы, а во втором-источник неприятных и трудных ошибок.

Я бы не применить memoization, если

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

есть и другие ситуации, когда мемуаризация не подходит, например, в вопросе и ответах выше, но это три, которые я думаю, не так очевидны.

последний элемент, вероятно, самый важный: memoization кэширует результат на основе аргументов метода, если метод выглядит так не может быть мемоизированную:

def unmemoizable1(name)
  "%s was here %s" % name, Time.now.strftime('%Y-%m-%d')
end

def unmemoizable2
  find_by_shoe_size(@size)
end

как, однако, может быть переписан, чтобы воспользоваться мемоизация (хотя в этих двух случаях, очевидно, должно быть сделано по другим причинам):

def unmemoizable1(name)
  memoizable1(name, Time.now.strftime('%Y-%m-%d'))
end

def memoizable1(name, time)
  "#{name} was here #{time}"
end
memoize :memoizable1

def unmemoizable2
  memoizable2(@size)
end

def memoizable2(size)
  find_by_shoe_size(size)
end
memoize :memoizable2

(предполагая, что find_by_shoe_size не имел или полагался на какие-либо побочные эффекты)

фокус в том, чтобы извлечь чистые функции из метода и применить мемоизация к тому, что вместо.


когда метод извлекает данные из нескольких таблиц и выполняет некоторые вычисления перед возвращением результирующего объекта, и этот метод несколько раз в запросах, memoization может иметь смысл.

помните, что кэширование запросов также активно, поэтому только методы memoize, которые выполняют вычисления в Ruby, а не чистые выборки базы данных.


возможно, мой опыт является хорошим примером того, когда не использовать memoize. В моей модели заказа я запоминал как простые результаты расчета, т. е. заказ#subtotal, заказ#tax; а также объекты модели, т. е. заказ#most_recent_credit_card_used. В последнем случае при запоминании метода, возвращающего объект CreditCard, я получаю ошибки "замороженного хэша" при попытке обновить атрибуты на memoized объекте. Приказом № most_recent_credit_card_used.замерзла? возвращает true, когда метод был записан, что, конечно, не то, что хотел.

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