yihong0618 和朋友们的频道 头像

消息来源频道

yihong0618 和朋友们的频道

@hyi0618

频道8,612 位成员公开可见0 人在线

yihong0618 和朋友们的频道

成员规模8,612 位成员
在线情况0 人在线
消息总数10,129 条消息
浏览量总数3,286,989 次浏览

在这个频道里搜索消息……

t.me/hyi0618

https://www.phoronix.com/news/Python-3.14-New-Interpreter
刚看到标题的时候以为 CPython 终于 done a good job 了,结果
This interpreter currently only works with Clang 19 and newer on x86-64 and AArch64 architectures. However, we expect that a future release of GCC will support this as well.
和之前的 copy-paste JIT 一样,又是创架构不得入内,只不过这次多了个 GCC。
好吧无所谓,回归实现的思路方面。实现一个 interpreter 最 straightforward 的思路就是一个大 switch-case,对每一个 bytecode 去 dispatch 到不同的路径上。
然而这么做的问题是 interpreter 的控制流过于复杂,冷热路径混杂在一起。使得编译器很难做合适的优化。举个例子,比如 PUC Lua(纯血 C89 无 JIT 的 Lua)的解释器体(luaV_execute)里[1],哪怕开 -O2 Clang 19 也会吐出有很多 spill 和 restore 的汇编。
movq %rax, 64(%rsp) # 8-byte Spill
movq 24(%rax), %rax
movq 56(%rax), %rax
movq %rax, 40(%rsp) # 8-byte Spill
xorl %ebp, %ebp
testl %r9d, %r9d
jne .LBB18_697
因此,比较高效的 interpreter,比如 LuaJIT 几千行的汇编地狱[2],会选择直接用汇编手写整个解释器,保证对上下文和 slowpath 的可控性。
很显然手写汇编费时费力,而且完全丧失了可移植性;所以就有了 Python 这个新方案,把每个 bytecode 拆在一个个小函数里实现好,用 musttail[3] 属性强制编译器把到下一个 bytecode 的实现函数作为尾调用(tailcall)处理,来让编译器生成代码的同时尽可能减少复杂控制流对优化的影响;用 preserve_none 强制编译器不把任何寄存器视为 callee saved,减少 tailcall 时的 spill/restore 损耗(解释器大循环里解释器状态这种寄存器是一直不会动的)。
这个其实让我有点想到 iSH[4] 了,里面的解释器把 x86 指令实现用汇编写成一个个 tailcall 的小 subroutine,以在不能随意 mprotect(PROT_EXEC) 的果子设备上尽可能快地解释 x86 代码。都是 tailcall,感觉有些异曲同工。
tailcall 的原始 idea: https://github.com/faster-cpython/ideas/issues/642
cpython issue: https://github.com/python/cpython/issues/128563
Issue 中提及的
- protobuf 解析器使用 tailcall 优化的参考 https://blog.reverberate.org/2021/04/21/musttail-efficient-interpreters.html
- Lua 解释器使用 tailcall 优化的参考 https://sillycross.github.io/2022/11/22/2022-11-22/
[1]: https://gist.github.com/ziyao233/842a55c0f8bbc756f2ae82eac3be7d12 # 我传了一份 emit 出来的汇编在这里
[2]: https://github.com/LuaJIT/LuaJIT/blob/v2.1/src/vm_x64.dasc
[3]: https://reviews.llvm.org/D99517
[4]: https://github.com/ish-app/ish