Написание кодов в локальном пространстве имен медленнее, чем в глобальном в PyPy3 - PullRequest
2 голосов
/ 10 апреля 2020

Я тестировал приведенные ниже коды с функцией времени bash.

# test_local.py
def main():
    n = 10 ** 7
    mod = 10 ** 9 + 7
    x = 0
    for i in range(n):
        x += i
        x %= mod


if __name__ == "__main__":
    main()
# test_global.py
n = 10 ** 7
mod = 10 ** 9 + 7
x = 0
for i in range(n):
    x += i
    x %= mod
Results:
python3 test_local.py  1.03s user 0.02s system 91% cpu 1.139 total
python3 test_global.py  1.92s user 0.01s system 98% cpu 1.956 total

pypy3 test_local.py  0.26s user 0.12s system 36% cpu 1.034 total
pypy3 test_global.py  0.13s user 0.03s system 97% cpu 0.161 total

Env:
CPython3 (3.8.2), PyPy3 (7.3.0)

Почему test_local.py медленнее, чем test_global.py в PyPy3, хотя результат противоположен в CPython?


Обновление
После ответа Армина Ри go я попробовал другой код ниже. Работает быстрее, сохраняя большинство частей в main ().

#  test_global_constant.py
MOD = 10 ** 9 + 7


def main():
    n = 10 ** 7
    x = 0
    for i in range(n):
        x += i
        x %= MOD


if __name__ == "__main__":
    main()
Results:
python3 test_global_constant.py  1.08s user 0.01s system 99% cpu 1.099 total

pypy3 test_global_constant.py  0.12s user 0.03s system 95% cpu 0.164 total

1 Ответ

2 голосов
/ 10 апреля 2020

Ответ, вероятно, в операторе "%". 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

...