Декоратор lru_cache модуля functools

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Смотреть материал на YouTube | RuTube

На этом занятии мы с вами рассмотрим еще один декоратор lru_cache из модуля functools. Как можно понять из его названия, он позволяет кэшировать результаты вызываемых объектов, в частности, функций.

Вообще аббревиатура LRU означает «Least Recently Used», то есть это кэш, который сохраняет результаты наиболее часто используемых вычислений, уменьшая нагрузку на процессор для ускорения работы программы.

Принцип работы декоратора lru_cache, применительно к какой-либо функции, заключается в сохранении в памяти (кэше) результатов ее работы. Когда функция вызывается с одними и теми же аргументами повторно, её результат берётся непосредственно из кэша, минуя повторное исполнение самого тела функции. При этом кэш постоянно обновляется, чтобы в нем присутствовали только наиболее употребительные элементы.

Давайте посмотрим на конкретном примере использование этого декоратора. Чтобы лучше была видна работа декоратора lru_cache, мы возьмем рекурсивную функцию, которая вычисляет n-е число Фибоначчи:

def fib(n):
    if n <= 1:
        return 1
 
    return fib(n-1) + fib(n-2)

Функция fib() специально не оптимизирована. Замерим время ее выполнения без каких-либо декораторов:

print(timeit.timeit(lambda: fib(30), number=1))  # 0.0881322999484837

И с декоратором lru_cache:

@functools.lru_cache()
def fib(n):
    if n <= 1:
        return 1
 
    return fib(n-1) + fib(n-2)
 
 
print(timeit.timeit(lambda: fib(30), number=1)) # 2.340017817914486e-05

Как видите, время выполнения значительно сократилось из-за кэширования предыдущих вычислений функции fib().

По умолчанию декоратор lru_cache() ограничивается 128 элементами. Но мы можем установить свое ограничение любым числом степени 2, например, 2 элемента:

@functools.lru_cache(maxsize=2)
def fib(n):
    ...

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

print(timeit.timeit(lambda: fib(30), number=1)) # 0.004572800127789378

Или же установить размер кэша в 256 элементов:

@functools.lru_cache(maxsize=256)
def fib(n):
    ...

Тогда получим гораздо более быстрое выполнение рекурсивной функции.

В случаях, когда требуется неограниченный размер кэша, параметр maxsize следует задать значением None:

@functools.lru_cache(maxsize=None)  # неограниченный эш
def fib(n):
    ...

Итак, декоратор lru_cache работает по следующим правилам:

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

Мало того функция, декорированная с помощью lru_cache,  приобретает два встроенных метода:

print(fib.cache_info()) # возвращает информацию о текущем состоянии кэша
fib.cache_clear() # очищает кэш

Таким образом, декоратор lru_cache помогает оптимизировать производительность приложений, сокращая затраты на постоянные обращения к медленным операциям (таким как сетевые запросы, операции ввода-вывода или длительные вычисления). Это простое и эффективное решение для повышения скорости обработки запросов и снижения нагрузки на систему.

Курс по стандартной библиотеке: https://stepik.org/a/259466?utm_source=proproprogs

Видео по теме