Python 是将函数参数加载到寄存器中还是将它们保留在堆栈中?

2024-01-02

所以我正在编写一个函数,它接受一个元组作为参数并对其执行一系列操作。看起来是这样的:

   def swap(self, location):
    if (location[0] < 0 or location[1] < 0 or
        location[0] >= self.r or location[1] >= self.c):
        return False

    self.board[0][0] = self.board[location[0]][location[1]]
    self.board[location[0]][location[1]] = 0
    self.empty = (location[0],location[1])

我试图使我的代码尽可能高效,所以因为我没有修改location,将变量加载到寄存器中是否有意义(loc0 = location[0]; loc1 = location[1])用于更快的计算(零周期读取)或者是location当它作为函数参数传入时,已经被Python编译器加载到寄存器中了吗?

Edit:我硬着头皮进行了一些测试。以下是此函数使用重复输入运行 1000 万次的结果(以秒为单位):"up", "down", "left", "right"(分别)

 Code as is:
   run#1: 19.39
   run#2: 17.18
   run#3: 16.85
   run#4: 16.90
   run#5: 16.74
   run#6: 16.76
   run#7: 16.94

 Code after defining location[0] and location[1] in the beginning of the function:
   run#1: 14.83
   run#2: 14.79
   run#3: 14.88
   run#4: 15.033
   run#5: 14.77
   run#6: 14.94
   run#7: 14.67

这意味着性能平均提高了 16%。对于我的情况来说绝对不是微不足道的。当然,这并不科学,因为我需要在更多环境中使用更多输入进行更多测试,但对于我的简单用例来说已经足够了!

使用 Python 2.7 在Macbook Pro(2015 年初) https://everymac.com/systems/apple/macbook_pro/specs/macbook-pro-core-i5-2.7-13-early-2015-retina-display-specs.html,其中有一个Broadwell i5-5257U CPU https://ark.intel.com/products/84985/Intel-Core-i5-5257U-Processor-3M-Cache-up-to-3_10-GHz(2c4t 最大睿频 3.1GHz,持续 2.7GHz,3MB 三级缓存)。

IDE 是:PyCharm Edu 3.5.1 JRE:1.8.0_112-release-408-b6 x86_64 JVM:OpenJDK 64 位服务器 VM 。

不幸的是,这是一个根据代码速度评分的课程。


如果您使用解释器,任何 Python 变量不太可能存在于不同表达式之间的寄存器中。您可以查看 Python 源代码如何编译为字节码。

Python 字节码(存储在解释器外部文件中的类型)是基于堆栈的(http://security.coverity.com/blog/2014/Nov/understanding-python-bytecode.html http://security.coverity.com/blog/2014/Nov/understanding-python-bytecode.html)。然后,该字节码被解释或 JIT 编译为本机机器代码。常规 python 仅进行解释,因此将 python 变量跨多个语句保存在机器寄存器中是不合理的.

用 C 编写的解释器可能会将字节码堆栈的顶部保留在解释循环内的局部变量中,而 C 编译器可能会将该 C 变量保留在寄存器中。因此,重复使用相同的 Python 变量可能最终不会有太多的存储/重新加载往返。

请注意,Broadwell CPU 上的存储转发延迟约为 4 或 5 个时钟周期,远低于 DRAM 往返的数百个周期。存储/重新加载甚至不必等待存储退出并提交到 L1D 缓存;它直接从存储缓冲区转发。有关的:http://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/ http://blog.stuffedcow.net/2014/01/x86-memory-disambiguation/ and http://agner.org/optimize/ http://agner.org/optimize/,以及其他链接x86 /questions/tagged/x86标签维基)。对于 L1D 缓存命中,加载使用延迟也只有 5 个时钟周期(从地址准备好到数据准备好的延迟。您可以通过链表(在 asm 中)进行指针追踪来测量它。)有足够的解释器开销(总计它运行以确定下一步要做什么的指令数量),这甚至可能不是瓶颈。


对于解释器来说,将特定的 python 变量保存在寄存器中根本不合理。即使你用 asm 写了一个解释器,根本问题是寄存器不可寻址。 x86add r14d, eax指令必须将两个寄存器硬编码到指令的机器代码中。 (所有其他 ISA 的工作方式都相同:寄存器号是指令机器代码的一部分,没有基于任何数据的间接寻址)。即使解释器做了工作来弄清楚它需要“将 reg-var #3 添加到 reg-var #2”(即将字节码堆栈操作解码回寄存器变量以获得它解释的内部表示),它也会必须使用与任何其他寄存器组合不同的功能。

给定一个整数,获取第 N 个寄存器的值的唯一方法是分支到使用该寄存器的指令,或者将所有寄存器存储到内存并索引结果数组。 (或者可能是某种无分支比较和掩码的东西)。

无论如何,尝试对此做任何具体的事情都是无利可图的,这就是为什么人们只用 C 编写解释器并让 C 编译器(希望)很好地优化实际运行的机器代码。

或者您编写一个 JIT 编译器,就像 Sun 为 Java 所做的那样(HotSpot VM)。 IDK 如果有 Python 的话。看Python 3 解释器有 JIT 功能吗? https://stackoverflow.com/questions/13034991/does-the-python-3-interpreter-have-a-jit-feature.

JIT 编译器实际上会将 Python 代码转换为机器代码,其中寄存器状态主要保存 Python 变量而不是解释器数据。同样,如果没有 JIT 编译器(或提前编译器),“将变量保存在寄存器中”就不是一回事。


它可能更快,因为它避免了 [] 运算符和其他开销(参见布伦的回答,您接受了)


脚注:一些 ISA 具有内存映射寄存器。例如AVR(8 位 RISC 微控制器),该芯片还具有内置 SRAM,其中包含低范围内存地址(包括寄存器)。因此,您可以执行索引加载并获取寄存器内容,但您也可能在未保存架构寄存器内容的内存上执行此操作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Python 是将函数参数加载到寄存器中还是将它们保留在堆栈中? 的相关文章

随机推荐