Эффективная память в 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