Я пишу код Python, который включает функции с двумя наборами параметров. Первый набор параметров будет меняться каждый раз при вызове функции. Второй набор параметров будет одинаковым для больших групп вызовов функций (например, они меняются каждые 10 000 вычислений функции). Я надеюсь написать эту функцию таким образом, чтобы условная логика выполнялась только один раз при изменении значений второго набора параметров. Я включил некоторый код, который, по моему мнению, может работать, но после некоторого тестирования с модулем времени улучшение кажется очень незначительным. Действительно ли этот код делает то, что я описываю? А если нет, есть ли способ сделать это на Python?
def preprocessor(parameter_array):
# example parameters
condition = parameter_array[0]
index = parameter_array[1]
def f(x_array):
result = 0
if (condition):
result += x_array[index] ** 2
else:
result += x_array[index] - 5
# ...
return result
return f
# define some test parameters
test_parameters = [True, 1]
# run the function 100000 times with these parameters
function = preprocessor(test_parameters)
sum = 0
for i in range(100000):
sum += function([i, i])
print(sum)
Я надеюсь, что записанный таким образом оператор if будет оцениваться только всякий раз, когда я вызываю функцию preprocessor
. Таким образом, это ускоряет вычисление цикла for, в котором изменяются только значения x. Для этого потребовалось около 0,024 секунды, а для запуска контрольного элемента потребовалось около 0,028 секунды. Я также включил код, который использовал для элемента управления ниже (для удобства чтения все без модуля времени).
def f(x_array, parameter_array):
# example parameters
condition = parameter_array[0]
index = parameter_array[1]
result = 0
if (condition):
result += x_array[index] ** 2
else:
result += x_array[index] - 5
# ...
return result
# define some test parameters
test_parameters = [True, 1]
# run the function 100000 times without using the preprocessed function
sum = 0
for i in range(100000):
sum += f([i, i], test_parameters)
print(sum)
Вы читали о декораторах Python?
@jasonharper, понятно. Вот чего я боялся. Проблема возврата одной из двух реализаций заключается в том, что приведенный мной код — всего лишь игрушечный пример, объясняющий проблему. В моем реальном случае использования у меня есть сотни параметров, поэтому возможные перестановки параметров становятся слишком большими, чтобы это было практично. Спасибо за помощь.
Отвечать:
Да, он делает то, что вы думаете. Самый простой способ узнать это — просто добавить несколько операторов печати.
def initialize(initialState):
state = initialState
print(f"I am executed on initialization: {state}")
def f(increment):
nonlocal state
print(f"I am incrementing {state} by {increment}")
state = state + increment
return f
fun = initialize(10)
fun(5)
fun(20)
выход
I am executed on initialization: 10
I am incrementing 10 by 5
I am incrementing 15 by 20
В стороне:
Возможно, ваш пример слишком упрощен, но логика, которую вы извлекаете, чрезвычайно проста. Такого ускорения вы не получите. Я не уверен, есть ли дополнительный код между условием и возвратом, но объявлять возвращаемое значение и затем увеличивать его на основе результата условия нет необходимости.
Здесь я сравнил ваш подход с двумя разными упрощениями, и оба обеспечивают ускорение. Python — действительно странный язык. Каждый известный мне компилируемый язык способен исключить инструкции ветвления при использовании тернарного кода. Я ожидаю, что троичный подход будет самым быстрым.
import timeit
def approach1():
def preprocessor (parameter_array):
condition = parameter_array[0]
index = parameter_array[1]
return lambda x_array: x_array[index] ** 2 if condition else x_array[index] - 5
test_parameters = [True, 1]
function = preprocessor(test_parameters)
sum = 0
for i in range(100000):
sum += function([i, i])
def approach2():
def preprocessor (parameter_array):
condition = parameter_array[0]
index = parameter_array[1]
def f (x_array):
result = 0
if (condition):
result += x_array[index] ** 2
else:
result += x_array[index] - 5
return result
return f
test_parameters = [True, 1]
function = preprocessor(test_parameters)
sum = 0
for i in range(100000):
sum += function([i, i])
def approach3():
def preprocessor (parameter_array):
condition = parameter_array[0]
index = parameter_array[1]
def f (x_array):
if (condition):
return x_array[index] ** 2
return x_array[index] - 5
return f
test_parameters = [True, 1]
function = preprocessor(test_parameters)
sum = 0
for i in range(100000):
sum += function([i, i])
n = 100
print(timeit.timeit(approach1, number=n))
print(timeit.timeit(approach2, number=n))
print(timeit.timeit(approach3, number=n))
выход
0.8117037120027817
0.9467067930017947
0.8014805819984758
P.S.
Хотя эти типы оптимизации могут быть интересными и помочь вам узнать о характеристиках производительности языка, гораздо лучше потратить время на оптимизацию таких вещей, как используемые вами алгоритмы. Если вам действительно нужна производительность, Python не для вас.
Спасибо за ваш подробный ответ. Думаю, я недостаточно ясно выразил свой первоначальный вопрос. Но да, как вы сказали, у меня гораздо больше кода между условием и возвратом. Пример, который я привел, был просто иллюстрацией моей проблемы.
Проблемы с производительностью весьма и весьма специфичны. Трудно давать какие-либо рекомендации, когда я не знаю, что он делает. Обычно я следую двум подходам: 1) кеширование/запоминание - то, что вы пытаетесь сделать 2) сокращение №2 - это сокращение не только инструкций для ЦП, но и выделения памяти. Посмотрите, не выделяете ли вы память в циклах без необходимости.
Кроме того, вызов функции требует некоторых накладных расходов. Если вы находитесь в «горячем» цикле, как показано в конце примера, возможно, лучше всего просто выполнить это суммирование внутри функции, которую вы вызываете.
Это честно. Я могу дать еще немного контекста. Я пытаюсь выполнить подбор кривых некоторых данных для системы ОДУ, которые зависят от некоторого набора параметров. Это предполагает использование решателя ОДУ для решения системы для каждого набора параметров, который пытается использовать алгоритм аппроксимации кривой. Именно это создает необходимость вызывать функцию (чтобы получить производные) тысячи раз для разных значений x, но с теми же параметрами. К сожалению, зависимость уравнения от некоторых параметров не так проста, как установка переменной, и включает в себя операторы if и т. д.
Я не знаком. Это что-то итеративное? Это означает, что вычисление текущего состояния зависит от предыдущего состояния? Или это что-то сделано на матрице?
Он является итеративным в том смысле, что входной массив x_array будет зависеть от значений всех предыдущих вызовов функций.
Я собирался порекомендовать что-нибудь вроде Pandas/Numpy или Polars, но не думаю, что они в этом случае помогут. Вы можете попробовать numba.pydata.org и посмотреть, поможет ли это вообще, но им действительно нужны массивы Numpy. Единственное улучшение, в котором вы можете увидеть улучшение (если ваш список параметров однороден), — это замена списка массивом: docs.python.org/3/library/array.html. Списки необходимо просматривать, тогда как массивы можно напрямую индексировать.
if (condition):
находится внутри функцииf
, возвращаемойpreprocessor()
, поэтому он оценивается при каждом вызове функции — я не понимаю, как вы думали, что это что-то ускорит. Вы могли бы избавиться от условного условия, вернувpreprocessor()
одну из двух реализацийf
, каждая из которых имеет разные вычисленияresult
, но потенциальный выигрыш в скорости будет довольно небольшим.