Ответ, вероятно, в операторе "%". PyPy имеет JIT, который просматривает один l oop за раз (включая все вызовы, сделанные этим l oop, если таковые имеются). В первой версии кода l oop компилируется с использованием «x% = mod», где значение «mod», как известно, не является константой - это просто значение, полученное ранее в функции , Он может выглядеть постоянным, но не полностью: если вы запускаете программы, используя, скажем, некоторые отладочные хуки, то вы могли бы изменить его значение, даже не введя l oop ---, т.е. до того, как JIT активирует По этой причине JIT не оптимизируется для постоянных локальных переменных; кроме того, это в некоторой степени редкий случай: локальные переменные обычно не являются константами.
С другой стороны, во втором случае «x% = mod» использует глобальную переменную «mod». Глобальные переменные чаще всего являются константами (например, большинство глобальных переменных на самом деле являются функциями или классами или числовыми c константами). Таким образом, JIT в PyPy содержит специальный код для поддержки этого. Глобальные переменные внутренне более сложны, чем локальные переменные: они будут помнить, если они были изменены, и до тех пор, пока они не изменялись, тогда они будут записывать, какие части ассемблера были сгенерированы, предполагая, что они постоянны. Таким образом, до тех пор, пока вы не измените «mod», JIT скомпилирует «x% = mod», предполагая, что «mod» - это точно постоянная 1000000007.
Почему это имеет значение ? Потому что деление и по модулю на константу заменяются умным кодом, используя вместо этого умножение, используя известный трюк. G CC или любой хороший C компилятор тоже использует подобный прием. Если вас интересуют подробности, код, заменяющий деление на константу чем-то, основанным на умножении, находится здесь (UINT_MUL_HIGH делает «(x * y) >> 64» с 64-битными числами без знака x и y, которые являются одним ассемблером инструкция): https://foss.heptapod.net/pypy/pypy/blob/branch/default/rpython/jit/metainterp/optimizeopt/intdiv.py