#python #processing-efficiency #memory-efficient
#python #эффективность обработки #экономия памяти
Вопрос:
У меня есть вопрос об эффективности, в котором меня интересуют как скорость обработки, так и эффективность памяти.
Я пишу код, который вызывает функцию много раз. Некоторые из ее аргументов должны оставаться переменными, потому что функция вызывается с разными значениями для этих аргументов каждый раз. Другие, однако, являются постоянными и одинаковыми не только при каждом вызове функции, но и не меняются ни при каждом запуске кода, ни даже между запусками. Тем не менее, я все равно хотел бы записать их как переменные, а не жестко кодировать их, чтобы я мог ссылаться на них по описательным именам.
В следующем примере я ожидаю, что первые два одинаково эффективны, а последний не так эффективен. Однако у меня нет четкого представления о том, почему. Кто-нибудь может помочь объяснить это?
РЕДАКТИРОВАТЬ: в приведенном ниже примере я использую только 100 итераций. На самом деле я ожидал бы, что функции будут вызываться таким образом тысячи или миллионы раз.
РЕДАКТИРОВАТЬ 2: для тех, кто просто говорит мне профилировать его. обратите внимание, что я спросил, почему, а не только если, некоторые способы более эффективны, чем другие. Профилирование ответит, если, но не почему.
import numpy as np
def myfunc(a):
(a**4) np.sqrt(3)*a - a/3
for a in arange(100):
y[a] = myfunc(a)
по сравнению
import numpy as np
myb = 3
def myfunc(a):
(a**4) np.sqrt(myb)*a - a/myb
for a in arange(100):
y[a] = myfunc(a)
по сравнению
import numpy as np
myb = 3
def myfunc(a, b):
(a**4) np.sqrt(b)*a - a/b
for a in arange(100):
y[a] = myfunc(a, myb)
Комментарии:
1. О чем мы говорим с точки зрения количества итераций, которые вам нужно выполнить? сотни? тысячи? миллионы? — Я вижу цикл со 100 итерациями. Будет ли это выполняться много раз другим кодом, который также выполняет циклы? — Я хочу сказать, что, если вы не говорите о миллионах итераций, беспокоиться об эффективности передачи нескольких переменных не стоит trouble…it это не приведет ни к чему существенному с точки зрения времени.
2. Вы пробовали профилировать свой код? Все они по существу эквивалентны.
3. Преждевременная оптимизация — корень всего зла . Делайте все, что считаете наиболее естественным и понятным, и оптимизируйте это, если это становится узким местом в производительности.
4. @Steve Этот вопрос — проблема, о которой я думаю для нескольких кодов. В некоторых случаях функция like
myfunc
вызывается несколько миллионов раз. В других случаях код A вызываетmyfun
~ 100 раз, а код B вызывает код A ~ 100 раз, поэтому меня интересуют оба случая.5. Так почему бы не профилировать его самостоятельно, используя, например
timeit
Ответ №1:
Эффективность на уровне, о котором вы говорите, не имеет значения. Вам следует беспокоиться о удобочитаемости вашего кода, а не об эффективности перемещения нескольких значений. Если код более удобочитаем для передачи значений функции каждый раз через цикл, даже если они не меняются, затем передайте их функции. Например, помещение вещей в глобальные переменные обычно гораздо менее читабельно с точки зрения понимания того, что делает код.
Вот пример, который я собрал:
import random
def foo(iter, a, b, c, d, e, f, g, h, i ,j):
x = a b c d e f g h i j
if iter % 100000 == 0:
print(x)
for i in range(1000000):
foo(i, random.random() * 100,
random.random() * 100,
random.random() * 100,
random.random() * 100,
random.random() * 100,
random.random() * 100,
random.random() * 100,
random.random() * 100,
random.random() * 100,
random.random() * 100)
Результат:
658.9874644541911
643.4372986147371
636.6218502753122
475.3660640474451
648.4789890659888
466.2721794578193
595.3755252194462
583.45879143973
498.04278700281304
283.2047039562956
Этот код выполняет миллион итераций создания 10 случайных значений, умножения каждого на 100, передачи их по отдельности в функцию и суммирования их в функции. Каждые 100 000 итераций я печатаю значение суммы просто для проверки работоспособности.
Это выполняется за 2-3 секунды на моем Macbook Pro. Наши компьютеры в наши дни действительно очень быстрые и мощные. Настолько, что почти никогда не стоит беспокоиться о видах оптимизации, о которых вы говорите.
ОБНОВЛЕНИЕ: чтобы продвинуть точку дальше, и потому, что мне было любопытно, я попытался извлечь поколения случайных чисел и запустить это:
for i in range(1000000):
foo(i, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
Это выполняется практически мгновенно, печатая 55 десять раз в мгновение ока. Итак, большая часть первого примера — это генерация 10 миллионов случайных чисел. Далее я отмечу, что при использовании этих констант компилятор и процессор, вероятно, оптимизируют wazoo, поскольку в этом случае ничего не меняется. Но это только подталкивает к дальнейшему. Нет причин беспокоиться о передаче постоянных значений, отчасти потому, что компиляторы и процессоры в наши дни распознают и оптимизируют такие шаблоны для вас. Именно поэтому я использовал random() для первого примера, чтобы избежать этих оптимизаций.
Память — это другая проблема, но обычно это решается самой проблемой, а не тем, как именно вы это делаете. Безусловно, бывают случаи, когда память становится особой проблемой, когда вам нужно быть умным (выполнять что-то в пакетах, обрабатывать с потоками и т. Д.). Вопрос памяти заключается в том, где было бы неплохо знать, о каких числах мы говорим, и как данные выглядят в целом.
Комментарии:
1. Это полезно для понимания того, что мне не нужно беспокоиться об этом вопросе, но мне все еще интересно, что делается по-разному между различными методами на более низком уровне. Я полагаю, что это потребовало бы отдельного отдельного вопроса, поэтому я отмечу это как принятое. Спасибо!
2. Вы можете использовать дизассемблер, чтобы увидеть байтовый код для двух версий.
3. @Barmar — Да, пн. Отличная идея. Я думаю, что PyCharm может сделать это за вас, хотя я забыл, как именно. Хотя я мог бы подумать о Java и IntelliJ.