我日常不写 Python,但对脚本语言 Runtime 的性能做过些调研。这里抛砖引玉下简单的结论:关键根本不在于用什么语言实现,而在于是否用上 JIT。
我们不妨将题主提到的 CPython 作为基准,把它运行计算密集型任务的性能水平设为 1。那么在此基础上大概可以得到:在 1 这个量级,有经典的 Lua 和 Ruby 解释器,以及最近的 QuickJS 和 Hermes JS 引擎。
在约 20 到 50 这个量级,有 LuaJIT 和 V8 这种带 JIT 的引擎。
在约 200 以上的量级,有 Linux 和 Windows 这种操作系统(原生语言的 Runtime)。我不太确定 WASM 和 Java 这种加了层 VM 的「原生」性能水平如何?粗略猜测在 100 左右,望不吝赐教。
然后让我们看看,这几个性能量级的 Runtime 是怎么实现的:最慢、最简单的解释器是什么语言写的?既有 C 也有 C++
一言不合快个几十倍的 JIT 引擎是什么语言写的?既有 C 也有 C++
最硬核的操作系统是什么语言写的?还是那句话……既有 C 也有 C++
所以,用 C 还是 C++ 写 Implementation 重要吗?不重要。比挑选编程语言更重要的,是基本的工程设计,以及实际场景下的取舍。你可能会有疑问,这性能都差了几十倍,还有什么取舍的必要吗?这里个人的理解是这样的:最经典的解释器,原理不外乎逐行解释执行代码。它自身速度虽慢,但非常轻便灵活。这种解释器通常在几万行代码内就可以实现,编译到二进制的体积多在 1M 以下,非常容易嵌入到大型原生项目(如游戏引擎)中,易于在低配硬件上运行,也非常容易与原生模块互调。
JIT 虽然能比解释器快几十倍,但代码量通常庞大且晦涩难懂,难以裁剪定制,资源占用也比较高。QuickJS 只要一个神人就能做到基本完整支持 ES2019,而 V8 则要一支 Google 的精英团队才维护得动。另外,JIT 机制需要一个 warm up 的过程,这也是不能忽视的性能开销。
可以说,在是否使用 JIT 之间的取舍,也比用 C++ 还是 C 来实现 Runtime 更重要。让我们看看工业界的实例:Chrome 发布当年一骑绝尘地快,原因之一就在于 V8 这个工业级的 JIT 引擎,基本吊打当时 IE 的传统 JS 解释器。
Facebook 最近宣布在 RN 上取得的性能进步,反而在于它用不带 JIT 的 Hermes,替代了安卓默认的 V8——嵌入式的 JS 引擎降低了 JS 与 Native 通信的成本,并可以编译到自身的字节码,提高 App 的加载速度(这里 Facebook 玩了个花招,宣称 Hermes 支持了 AOT 编译。但这种编译到字节码的 AOT,运行时是肯定不如 V8 编译到机器码的 JIT 来得快的)。
V8 最近提供了一个 JIT-less 模式,可以关闭 JIT 来降低内存占用。在 JIT-less 模式下面,运行计算密集型任务的效率慢了 10 倍以上,但真实世界里 Web 应用场景的 Benchmark 水平则只慢了 40% 左右。
虽然脚本语言对 CPU 密集型的任务战力不高,但打不过我可以叫大哥呀!在我熟悉的 Web 上,我个人看到了这样的一种趋势:嵌入式的脚本引擎和嵌入式的 WASM 原生代码相辅相成,进一步增强了脚本世界和 Native 世界的联系。你可能发现我写的是 JS 或 Python,但其中的每行代码背后都是强力的原生代码。脚本就像《攻壳机动队》里那样被「武装」起来:
最后再次做个简单的总结吧——对于脚本语言的在真实世界中的表现,最重要的不是你有没有用上 C++20 的哪些新特性来实现 Python,而是 JIT 与基础架构设计上的取舍。
参考: