Эффективная память в Python
У меня есть некоторая задача для решения, и самая важная часть на данный момент состоит в том, чтобы сделать script максимально эффективным по времени. Одним из элементов, которые я пытаюсь оптимизировать, является memoization внутри одной из функций.
Итак, мой вопрос: Какой из следующих 3-4 методов является наиболее эффективным/быстрым методом реализации memoization в Python?
Я предоставил код только в качестве примера - если один из методов более эффективен, но не в том случае, о котором я упоминал, пожалуйста, поделитесь тем, что вы знаете.
Решение 1 - использование изменяемой переменной из внешней области
Это решение часто отображается как пример memoization, но я не уверен, насколько он эффективен. Я слышал, что использование глобальных переменных (в данном случае это переменная из внешней, а не глобальной области) менее эффективно.
def main():
memo = {}
def power_div(n):
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
Решение 2 - использование аргумента по умолчанию, изменяемого аргумента
Я где-то нашел, что использование измененных по умолчанию аргументов использовалось в прошлом для передачи переменных из внешней области, когда Python сначала искал переменную в локальной области, а затем в глобальной области пропускал нелокальную область (в этом случае область действия внутри функции main()
). Поскольку аргумент по умолчанию инициализируется только в момент, когда функция определена и доступна только внутри внутренней функции, возможно, она более эффективна?
def main():
def power_div(n, memo={}):
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
Или, может быть, более эффективна следующая версия (на самом деле комбинация решений 1 и 2)?
def main():
memo = {}
def power_div(n, memo=memo):
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
Решение 3 - атрибут функции
Это еще один довольно распространенный пример memoization в Python - объект memoization сохраняется как атрибут самой функции.
def main():
def power_div(n):
memo = power_div.memo
try:
return memo[n]
except (KeyError):
memo[n] = (n ** 2) % 4 # example expression, should not matter
return memo[n]
# extensive usage of power_div() here
Резюме
Мне очень интересно ваше мнение о четырех вышеупомянутых решениях для memoization. Важно также, что функция, использующая memoization, находится в пределах другой функции.
Я знаю, что есть и другие решения для memoization (например, Memoize
decorator), но мне трудно поверить, что это более эффективное решение чем перечисленные выше. Исправьте меня, если я ошибаюсь.
Спасибо заранее.
Ответы
Ответ 1
Различные стили доступа к переменной уже были синхронизированы и сравниваются по адресу: http://code.activestate.com/recipes/577834-compare-speeds-of-different-kinds-of-access-to-var
Здесь краткое резюме: локальный доступ превосходит нелокальные (вложенные области), которые обрушивают глобальный доступ (область модуля), которая превосходит доступ к встроенным.
Ваше решение №2 (с локальным доступом) должно победить. Решение № 3 имеет медленный поиск (для которого требуется поиск в словаре). В решении № 1 используется нелокальный (вложенной области) доступ, который использует переменные ячеек (быстрее, чем поиск по типу, но медленнее, чем локальные).
Также обратите внимание: класс исключения KeyError является глобальным поиском и может быть ускорен локализацией. Вы можете полностью заменить try/except и вместо этого использовать memo.get(n, sentinel)
. И даже это можно было бы ускорить, используя связанный метод. Конечно, ваш самый легкий ускорение скорости может исходить только от опроса pypy: -)
Короче говоря, есть много способов настроить этот код. Просто убедитесь, что это того стоит.
Ответ 2
В интересах людей, которые спотыкаются на этот вопрос, ища способ сделать memoization в python, я рекомендую fastcache.
Он работает на python 2 и 3, быстрее, чем любой из описанных выше методов, и дает возможность ограничить размер кеша, чтобы он не стал слишком большим:
from fastcache import clru_cache
@clru_cache(maxsize=128, typed=False)
def foo(cat_1, cat_2, cat_3):
return cat_1 + cat_2 + cat_3
Установка fastcache проста, используя pip
:
pip install fastcache
или conda
:
conda install fastcache