我使用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)
为什么在PyPy3中test_local.py比test_global.py慢,尽管在CPython中结果却相反?
更新按照Armin Rigo的回答,我在下面尝试了另一个代码。在将大部分保留在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
答案可能在“%”运算符中。 PyPy的JIT一次查看一个循环(包括该循环完成的所有调用,如果有的话)。在代码的第一个版本中,使用“ x%= mod”编译循环,其中“ mod”的值不知道是常量,它只是来自函数早期的值。它看起来可能是常数,但并不完全是:如果您使用一些调试钩子来运行程序,那么可以想象甚至在进入循环之前也已经改变了它的值-即因此,在局部变量不变的情况下,JIT不会进行优化。另外,这在某种程度上是罕见的情况:局部变量通常不是常数。