Подтвердить что ты не робот

Python оптимизирует вызовы функций из циклов?

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

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

def inner_function(line):
    # do something
    pass

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

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

4b9b3361

Ответ 1

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) может теоретически обнаруживать нормальный случай и выполнять inlining. Увы, я не достаточно знаком с PyPy, чтобы узнать, делает ли это это, но это определенно может.

Ответ 2

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

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

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

Ответ 3

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

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

Ответ 4

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

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

  • Использование PyPy вместо CPython
  • используя Cython
  • Написание модуля С++ и его взаимодействие с sip
  • Если возможно реализовать свой алгоритм с numpy simd approach
  • Если возможно, переместите вычисления на аппаратное обеспечение GPU, например, PyCuda

Ответ 5

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

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