Кэширование результатов функций в Python

Кэширование функций в Python, модуль cachepy

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

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

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

Осуществлялось тестирование новых подходов по обработке плоских контуров, получаемых в результате оцифровки изображений. Выделение контуров из набора изображений и получение их цифровых моделей — весьма емкая операция; поэтому работая с алгоритмами обработки контуров, было бы очень удобно избегать процедуру их извлечения из исходных изображений, выполняемую многократно. Иными словами, процедуру извлечения контуров из изображений необходимо кэшировать. Конечно, это можно сделать по-разному: выполнив такую оцифровку однажды и сохранив результаты в файл, и каждый последующий раз просто загружать необходимые данные из файла (по сути, это тоже будет кэшированием); 2) воспользоваться какими-либо специально созданными функциями (декораторами), позволяющими, если некоторая функция вызывается многократно с теме же параметрами, не вычислять ее заново, а отдавать сохраненный в памяти результат на предыдущем шаге.

Для языка программирования Python существует много решений кэширования. Это и хорошо известный пример memoize декоратора, а также другие модули, решающие задачу кэширования: 1) percache; 2) hermes-cache; 3) caches. И хотя отдельные решения позволяют назначать время жизни для кэша как путем указания реального времени, так и максимального количества вызовов функции, по достижении которого ее значение необходимо вычислить снова, ни одна из встретившихся не позволяла наряду с этими свойствами осуществлять шифрование кэша (что могло бы быть полезным при его сохранении, например, в каком-либо "общедоступном" месте).

Было решено написать утилиту для кэширования в Python со следующим функционалом:

  • возможность шифрования кэша;
  • автоматическое назначение уникального ключа;
  • указание максимального числа вызовов функции;
  • указание времени жизни кэша
  • возможность сохранения кэша в файл

В итоге получился модуль cachepy.

Пример кэширования в Python

Приведем пример кэширования функции, осуществляющий вычисление последовательности Фибоначчи до его n-го члена. Создадим файл cache.py со следующим содержимым:

from cachepy import Cache


mycache = Cache('cache.dat', key='some')


@mycache
def fib(n):
    print 'Fib is evaluated for n=', n
    if n == 0 or n == 1:
        return 1
    return fib(n-2) + fib(n-1)


print fib(45)

Вычисляемые значения fib(0), fib(1), ... fib(10) кэшируются и сохраняются в файл. Таким образом последовательный запуск скрипта приводит к следующему результату:
 

~/workspace> python -m cache
Fib is evaluated for n= 10
Fib is evaluated for n= 8
Fib is evaluated for n= 6
Fib is evaluated for n= 4
Fib is evaluated for n= 2
Fib is evaluated for n= 0
Fib is evaluated for n= 1
Fib is evaluated for n= 3
Fib is evaluated for n= 5
Fib is evaluated for n= 7
Fib is evaluated for n= 9
89
~/workspace> python -m cache
89

Этот пример интересен и тем, что в нем мы кэшируем рекуррентно определенную функцию, и если теперь, например, если попробуем вызвать fib(12), то все значения fib(0), ... fib(10) будут извлекаться из кэша (о чем можно судить по выводу на экран служебной строки "Fib is evaluated... "). Поскольку кэш сохраняется в файле и не несет никаких ограничений (количество вызовов, время жизни), им можно пользоваться многократно. В результате вызова fib(12) будут вычислено всего лишь два недостающих значения:
 

~/workspace> python -m cache
Fib is evaluated for n= 12
Fib is evaluated for n= 11
233

Установить cachepy можно из репозитария PyPi: pip install cachepy.
 

blog comments powered by Disqus