Python: увеличение вызова функции рекурсии при профилировании памяти - PullRequest
0 голосов
/ 21 мая 2019

Я пытаюсь изучить рекурсивные функции.Я использую Colab Laboratory для запуска своего кода.

Вот мой вопрос: почему увеличивается количество вызовов функций при использовании профилировщика памяти?Я использовал глобальную переменную, чтобы подсчитать, сколько раз была вызвана функция.

Это мой оригинальный код без профилировщика:

# imports and other things...

accm = 0
def sum_num(n):
    global accm
    accm += 1
    if n == 1:
        return 1
    return n + sum_num(n - 1)

call_list = []
for i in range(0, 6000, 100):
    accm = 0
    if i == 0:
        i = 1
    sum_num(i)
    call_list.append(accm)

# visualization things...

И установить предел рекурсии на 10000.

График, визуализирующий accm, является линейным от 1 до 6000:

И я ожидал, что это не изменится, когда я использую профилировщик, но это произошло.

Вот профилированная версия:

# imports and other things...
from memory_profiler import memory_usage

accm = 0
def sum_num(n):
    global accm
    accm += 1
    if n == 1:
        return 1
    return n + sum_num(n - 1)


call_list = []
for i in range(0, 6000, 100):
    accm = 0
    if i == 0:
        i = 1
    _ = max(memory_usage((sum_num, (i,))))
    call_list.append(accm)

# visualization things...

И я получил нелинейный граф от 1 до 16000;его максимальная точка составляет около 20000, и она линейно увеличивается с шумом до x = 25 и y = 16000, уменьшается, затем снова линейно увеличивается с шумом:

Я хочу знатьпочему это происходит и как это исправить.

1 Ответ

0 голосов
/ 21 мая 2019

Профилировщик, который вы используете, будет запускать функцию, которую вы передаете memory_usage несколько раз, так как он пытается определить интервал выборки памяти, что позволит ему получить достаточно выборок для точного считывания. Если ваш код выполняется слишком быстро, ему нужно будет уменьшить размер выборки, поэтому функция будет вызываться чаще.

Вы можете увидеть, как это работает, в исходном коде memory_profiler. Вот важный цикл (с вырезанными ненужными вещами):

while True:
    child_conn, parent_conn = Pipe() # this will store MemTimer's results
    p = MemTimer(os.getpid(), interval, child_conn, backend,
                 timestamps=timestamps,
                 max_usage=max_usage,
                 include_children=include_children)
    #...
    returned = f(*args, **kw)
    #...
    n_measurements = parent_conn.recv()
    #...
    if n_measurements > 4 or interval < 1e-6:
        break
    interval /= 10.

Моя интерпретация вашего выходного графа заключается в том, что когда ваша функция вызывается с небольшим входом, interval необходимо сжать три раза, прежде чем профилировщик получит достаточное разрешение для точного измерения использования памяти, поэтому количество вызовов для рекурсивная функция в четыре раза больше нормального уровня. После итерации 22 или около того интервал должен быть сокращен только дважды, поэтому значения графика изменяются в три раза по сравнению с нормальным уровнем. Есть также некоторый шум, хотя, где необычно быстрый или медленный запуск кода приводит к необычному количеству интервальных изменений, которые необходимо выполнить. Вот почему вы видите несколько долин, где требовалось меньше изменений, и одну вершину, где произошла дополнительная.

Вы можете избежать изменения размеров, если передадите соответствующий аргумент interval в memory_usage. По умолчанию используется значение 0.1, поэтому вы, вероятно, можете избежать всех изменений, передав начальное значение 0.0001 (но при рассмотрении эксперимента вы можете сделать 0.0005 или более): memory_usage((sum_num, (i,)), 1e-4)

...