python - 在 PyPy3 中,在本地命名空间中编写代码比在全局命名空间中编写代码慢

标签 python pypy

我用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”的值并不知道是一个常量——它只是来自函数早期的一个值。它可能看起来恒定,但并不完全:如果您使用某些调试 Hook 运行程序,那么您可以想象在进入循环之前就已经更改了它的值——即因此,JIT 不会针对常量局部变量进行优化;此外,这在某种程度上是罕见的情况:局部变量通常不是常量。

另一方面,在第二种情况下,“x %= mod”使用全局变量“mod”。全局变量更常见的是常量(例如,大多数全局变量实际上是函数或类或数值常量)。因此 PyPy 中的 JIT 包含特殊代码来支持这一点。全局变量在内部比局部变量更复杂:它们会记住它们是否已更改,并且只要它们没有更改,那么它们就会记录已生成的汇编程序片段(假设它们是常量)。因此,只要您不更改“mod”,那么“x %= mod”就会由 JIT 编译,假设“mod”恰好是常量 1000000007。

为什么它会有所不同?因为用常量除法和取模被使用乘法的聪明代码所取代,使用了众所周知的技巧。 GCC 或任何优秀的 C 编译器也使用类似的技巧。如果您对细节感兴趣,可以使用基于乘法的代码替换常量除法的代码(UINT_MUL_HIGH 使用无符号 64 位数字 x 和 y 执行“(x * y) >> 64”,这是一个汇编器操作说明): https://foss.heptapod.net/pypy/pypy/blob/branch/default/rpython/jit/metainterp/optimizeopt/intdiv.py

关于python - 在 PyPy3 中,在本地命名空间中编写代码比在全局命名空间中编写代码慢,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/61144206/

相关文章:

python - 在Python中覆盖大文件的一部分

parsing - 我可以使用什么前端与 RPython 来实现语言?

python - 如何使用 pip 和从启动板安装的 pypy?

python - 安装 Python 时使用 pypy 轻松安装

Python 输入永远不会等于整数

python - PyTorch 中张量的最小-最大归一化

python - 添加列表中的数字但保留其他元素

c - Pypy CFFI内存管理问题

python - PyPy 何时/何处生成机器代码?

python - 如何通过查询向mysql表中添加新列