«Предварительная обработка» функции Python, чтобы избежать избыточной оценки условной логики

Я пишу код 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 можно создавать настольные приложения с использованием библиотеки Tkinter.


70
1

Ответ:

Решено

Отвечать:

Да, он делает то, что вы думаете. Самый простой способ узнать это — просто добавить несколько операторов печати.

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 не для вас.


Интересные вопросы для изучения