Почему существует огромная разница в производительности между этими двумя кодами в Python и Cython?

Я столкнулся с проблемами производительности в Python, один из моих друзей предложил мне использовать Cython. После поиска больше я нашел этот код здесь

Python:

def test(value): for i in xrange(value): z = i**2 if(i==1000000): print i if z < i: print "yes" test(10000001) 

Cython:

 def test(long long value): cdef long long i cdef long long z for i in xrange(value): z = i**2 if(i==1000000): print i if z < i: print "yes" test(10000001) 

После того, как я выполнил оба кода, неожиданно я достиг 100-кратного ускорения Cython

Почему просто добавив объявления переменных, это ускорение достигнуто ?? Также я должен упомянуть, что приведенная ниже производительность кода такая же, как у Python в Cython.

Cython:

 def test(long long value): for i in xrange(value): z = i**2 if(i==1000000): print i if z < i: print "yes" test(10000001) 

Python – это язык. CPython является компилятором байт-кода и интерпретатором для Python.

Это займет некоторый код:

 for i in xrange(value): z = i**2 if(i==1000000): print i if z < i: print "yes" 

и дать вам «байт-код»:

  • загрузите итератор в цикл for и зациклируйте его содержимое на i
  • загрузить i , загрузить 2 , запустить двоичную мощность, хранить z
  • загрузить i , загрузить 1000000 , сравнить
  • загрузить i , распечатать
  • загрузить z , загрузить i , сравнить
  • загрузить 'yes' , распечатать
  • Конец

В полном объеме:

  1 0 SETUP_LOOP 70 (to 73) 3 LOAD_NAME 0 (xrange) 6 LOAD_NAME 1 (value) 9 CALL_FUNCTION 1 12 GET_ITER >> 13 FOR_ITER 56 (to 72) 16 STORE_NAME 2 (i) 2 19 LOAD_NAME 2 (i) 22 LOAD_CONST 0 (2) 25 BINARY_POWER 26 STORE_NAME 3 (z) 3 29 LOAD_NAME 2 (i) 32 LOAD_CONST 1 (1000000) 35 COMPARE_OP 2 (==) 38 POP_JUMP_IF_FALSE 49 4 41 LOAD_NAME 2 (i) 44 PRINT_ITEM 45 PRINT_NEWLINE 46 JUMP_FORWARD 0 (to 49) 5 >> 49 LOAD_NAME 3 (z) 52 LOAD_NAME 2 (i) 55 COMPARE_OP 0 (<) 58 POP_JUMP_IF_FALSE 13 6 61 LOAD_CONST 2 ('yes') 64 PRINT_ITEM 65 PRINT_NEWLINE 66 JUMP_ABSOLUTE 13 69 JUMP_ABSOLUTE 13 >> 72 POP_BLOCK >> 73 LOAD_CONST 3 (None) 76 RETURN_VALUE 

Стоит отметить, что в Python целое число является экземпляром класса int или long . Это означает, что есть не только число, но и указатель и другая часть информации, в которой говорится, какой класс по крайней мере . Это делает много накладных расходов.

Но также стоит отметить, как работает xrange .

xrange создает экземпляр класса ( LOAD_NAME (xrange) , CALL_FUNCTION ), который может быть CALL_FUNCTION в for . Команда for (в основном) делегирует вызов функции на __iter__ итератора. В каждом цикле есть вызов функции.

Кроме того, каждый раз, когда вы хотите получить или установить переменную z или i , она должна искать в словаре locals. Это очень медленно.


Запуск чистого кода Python в Cython:

Когда вы запускаете его в Cython (третий пример в своем вопросе), он компилируется на C. Но все это C действительно сообщает виртуальной машине CPython, что делать.

CPython один: парень, читающий книгу, и тщательно выполняющий свои функции.
CPython с Cython: парень кричит инструкции парню, который тщательно выполняет свои функции.

Это может быть немного быстрее, но медленная часть по-прежнему заключается в том, что CPython медленно выполняет работу.


Использование cythonized code:

Что произойдет, когда вы cdef long long будете cdef long long ?

  • Китон знает, что xrange действует на long long :

    • Он знает, что цикл действителен (поэтому ему не нужно проверять, что вы дали ему list или что-то подобное)

    • Он знает, что цикл не будет переполняться (потому что он не определен, если он это делает!)

    • Поэтому он может превратить его в цикл C ( for (int index=0; index<copy_of_value; index++) { i = index; ... } )

  • Это позволяет избежать int и long классов, которые имеют много косвенных накладных расходов и проверки типов

  • Это позволяет избежать поиска в словарях. Вещи всегда там, где вы кладете их в стек

  • Например, i ** 2 намного проще, поскольку подпрограмма может быть встроена (это всегда число, чувак) и работать непосредственно с целым числом и игнорировать переполнение

Таким образом, результат заканчивается тем, что выполняется в основном с помощью C, и идет только на CPython для некоторых элементов очистки и вызовов print .


Имеют смысл?

Как я уже упоминал в своем комментарии: ваше третье решение медленнее / как-медленно-как-python-версия, потому что ему не хватает статических функций ввода, которые позволяют Cython ускорить ваш код. Когда вы объявляете переменную long fe, Cython не нужно создавать «дорогой» Python-Object, но может полностью полагаться на C-Code. Я не Cython и не специалист по Python, но я думаю, что конструкция объекта Python является основным узким местом.

Потому что, когда вы пишете код на Python, вы не должны кодироваться, как если бы вы были на C ++. Python2 медленный. Python for loop – это slooow, и в справочнике говорится об этом. Вы должны использовать списки для небольшого или многотипного списка. Для списка чисел используйте массив:

 import array def create_doubles(value): doubles_iterator = i**2 for i in range(value) result = array('L', doubles_generator) #here it will calculate # I skip other two things, they can be done out of loop. 

Я думаю, Cython просто заменяет список массивом для вас неявно.