сохранять значения функции для предотвращения повторного запуска - PullRequest
0 голосов
/ 12 июля 2020

Допустим, у меня есть сложная функция f(fvar1, ..., fvarN), например:

def f(fvar1,..., fvarN):
    return (complicated function of fvar1, ..., fvarN).

Теперь функция g(gvar1, ..., gvarM) имеет выражение в терминах f(fvar1, ..., fvarN), скажем:

def g(gvar1, ..., gvarM):
    return stuff * f(gvar1 * gvar2, ..., gvar5 * gvarM) - stuff * f(gvar3, gvar2, ..., gvarM)

где аргументы f внутри g могут быть различными линейными комбинациями gvar1, ..., gvarM.

Поскольку f - сложная функция, вызов f дорогостоящий, но также трудно сохранить значение локально в g, потому что g имеет много экземпляров f с различными комбинациями аргументов.

Есть ли способ сохранить значения f, например что f одних и тех же значений не вызывается снова и снова без необходимости определять каждый другой экземпляр f локально в g?

1 Ответ

1 голос
/ 12 июля 2020

Да, это называется воспоминанием. Основная идея c состоит в том, чтобы f() поддерживал какое-то хранилище данных на основе переданных параметров. Затем, если он вызывается с теми же параметрами , он просто возвращает сохраненное значение, а не пересчитывает это.

Хранилище данных, вероятно, необходимо ограничить по размеру и оптимизировать для ожидаемого шаблона вызовов, удалив наборы параметров на основе некоторых правил. Например, если число использования набора параметров указывает на вероятность его использования в будущем, вы, вероятно, захотите удалить шаблоны, которые используются нечасто, и оставить те, которые используются чаще.

Рассмотрим, например, следующий код Python для сложения двух чисел (давайте представим, что это очень затратная по времени операция):

import random

def addTwo(a, b):
    return a + b

for _ in range(100):
    x = random.randint(1, 5)
    y = random.randint(1, 5)
    z = addTwo(x, y)
    print(f"{x} + {y} = {z}")

Это работает, но, конечно, неэффективно, если вы используете те же числа, что и раньше. Вы можете добавить мемоизацию следующим образом.

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

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

import random, time

# Cache, and the stats for it.

(pairToSumMap, cached, calculated) = ({}, 0, 0)

def addTwo(a, b):
    global pairToSumMap, cached, calculated

    # Attempt two different cache lookups first (a:b, b:a).

    sum = None
    try:
        sum = pairToSumMap[f"{a}:{b}"]
    except:
        try:
            sum = pairToSumMap[f"{b}:{a}"]
        except:
            pass

    # Found in cache, return.

    if sum is not None:
        print("Using cached value: ", end ="")
        cached += 1
        return sum

    # Not found, calculate and add to cache (with limited cache size).

    print("Calculating value: ", end="")
    calculated += 1

    time.sleep(1) ; sum = a + b # Make expensive.

    if len(pairToSumMap) > 10:
        del pairToSumMap[list(pairToSumMap.keys())[0]]
    pairToSumMap[f"{a}:{b}"] = sum
    return sum

for _ in range(100):
    x = random.randint(1, 5)
    y = random.randint(1, 5)
    z = addTwo(x, y)
    print(f"{x} + {y} = {z}")

print(f"Calculated {calculated}, cached {cached}")

Вы увидите, что я также добавил кэшированную / рассчитанную информацию, включая окончательную строку статистики, которая показывает кеширование в действии, например:

Calculated 29, cached 71

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

...