Оптимизирует ли Python вызовы функций из циклов? - PullRequest
17 голосов
/ 30 августа 2011

Скажем, у меня есть код, который несколько раз вызывает функцию из цикла, и я хочу, чтобы код работал быстро:

def outer_function(file):
    for line in file:
        inner_function(line)

def inner_function(line):
    # do something
    pass

Это не обязательно обработка файла, это может быть, например, точка рисования функции, вызываемая из линии рисования функции. Идея состоит в том, что по логике эти два должны быть разделены, но с точки зрения производительности они должны действовать как можно быстрее.

Python обнаруживает и оптимизирует такие вещи автоматически? Если нет - есть ли способ дать ему ключ к этому? Может, использовать какой-нибудь дополнительный внешний оптимизатор? ...

Ответы [ 5 ]

15 голосов
/ 30 августа 2011

Python не выполняет встроенных вызовов функций из-за своей динамической природы. Теоретически, inner_function может сделать что-то, что связывает имя inner_function с чем-то другим - у Python нет способа узнать, что это может произойти во время компиляции. Например:

def func1():
    global inner_func
    inner_func = func2
    print 1

def func2():
    print 2

inner_func = func1

for i in range(5):
    inner_func()

Печать:

1
2
2
2
2

Вы можете подумать, что это ужасно. Затем подумайте еще раз: функциональность и динамичность Python - одна из его наиболее привлекательных особенностей. Многое из того, что позволяет Python, достигается за счет производительности, и в большинстве случаев это приемлемо.

Тем не менее, вы, вероятно, можете взломать что-то вместе, используя инструмент, подобный byteplay или подобный - разберите внутреннюю функцию на байт-код и вставьте ее во внешнюю функцию, затем соберите заново. Если подумать, если ваш код достаточно критичен для производительности, чтобы оправдать подобные взломы, просто перепишите его на языке C. В Python есть отличные возможности для FFI.


Это все относится к официальной реализации CPython. Интерпретатор JITting во время выполнения (например, PyPy или печально несуществующая Unladen Swallow) теоретически может обнаружить нормальный случай и выполнить встраивание. Увы, я не достаточно знаком с PyPy, чтобы знать, делает ли он это, но определенно может.

13 голосов
/ 30 августа 2011

Какой Python? JIT-компилятор PyPy будет - после нескольких сотен или десятков (в зависимости от того, сколько кодов операций выполняется на каждой итерации) и т. Д. - начнет отслеживать выполнение, забывать о вызовах функций Python по пути и компилировать собранную информацию в кусок оптимизированный машинный код, который, вероятно, не имеет какого-либо остатка логики, которая заставила сам вызов функции произойти. Трассировки являются линейными, серверная часть JIT даже не знает, что был вызов функции, она просто видит инструкции обеих функций, смешанные вместе, как они были выполнены. (Это идеальный случай, когда, например, в цикле есть ветвление или все итерации принимают одну и ту же ветвь. Некоторый код не подходит для такого рода JIT-компиляции и быстро делает недействительными трассы, прежде чем они приведут к значительному ускорению, хотя это довольно редко.)

Теперь, CPython, что многие люди имеют в виду, когда говорят о "Python" или интерпретаторе Python, не настолько умен. Это простая виртуальная машина с байт-кодом, которая будет должным образом выполнять логику, связанную с вызовом функции снова и снова в каждой итерации. Но опять же, почему вы все равно используете переводчика, если производительность так важна ? Попробуйте написать этот горячий цикл в нативном коде (например, как расширение C или в Cython ), если так важно поддерживать такие накладные расходы на минимально возможном уровне.

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

5 голосов
/ 30 августа 2011

Если под «Python» вы подразумеваете CPython , обычно используемую реализацию, нет.

Если под «Python» вы имели в виду любую реализацию языка Python, да. PyPy может многое оптимизировать, и я считаю, что его метод JIT должен позаботиться о подобных случаях.

3 голосов
/ 30 августа 2011

CPython («стандартная» реализация python) не выполняет такого рода оптимизацию.

Однако учтите, что если вы подсчитываете циклы ЦП при вызове функции, то, вероятно, для вашей проблемы CPython не являетсяправильный инструмент.Если вы на 100% уверены, что алгоритм, который вы собираетесь использовать, уже является лучшим (это самая важная вещь), и что ваши вычисления действительно связаны с ЦП, то варианты, например:

  • Использование PyPy вместо CPython
  • с использованием Cython
  • Написание модуля C ++ и сопряжение его с sip
  • Если возможно, реализуйте свой алгоритм с помощью numpy simd подход
  • Если возможно, перенесите вычисления на аппаратном обеспечении графического процессора, используя, например, PyCuda
1 голос
/ 30 августа 2011

Вызов функции для вызова оператора pass, очевидно, несет довольно большие (∞) накладные расходы.Относится ли ваша настоящая программа к чрезмерным накладным расходам, зависит от размера внутренней функции.Если на самом деле это просто установка пикселя, то я бы предложил другой подход, использующий примитивы рисования, написанные на родном языке, например C или C ++.

Существуют (несколько экспериментальные) JIT-компиляторы для Python, которые оптимизируютвызовы функций, но основной Python не сделает этого.

...