Когда использовать 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. если вы собираетесь обновить эти объекты в памяти.