v8引擎 - 为什么从JS调用本机代码如此昂贵?

Tho*_*ein 2 v8 node.js

基于对其他问题的多个答案,从Javascript调用本机C++是昂贵的.

我用节点模块"基准"检查了自己并得出了相同的结论.

一个简单的JS函数可以直接获得~90 000 000个调用,当调用C++函数时,我最多可以得到大约25 000 000个调用.这本身并没有那么糟糕.

但是当添加对象的创建时,JS仍然是大约7万次调用/秒,但是本机版本遭受了巨大的损失并且下降到大约2 000 000.

我假设这与v8引擎如何工作的动态特性有关,并且它将JS代码编译为字节代码.

但是是什么阻止他们对C++代码实现相同的优化呢?(或至少打电话/洞察什么会有帮助)

小智 5

在不深入研究特定 V8 版本及其内在原因的情况下,我可以说开销不是 C++ 后端与 Javascript 的工作方式相比,而是语言之间的路径 - 即实现调用的二进制接口来自 Javascript 领域的本机方法,反之亦然。

根据我的理解,交叉调用涉及的操作是:

  1. 准备论据。
  2. 保存 JS 上下文。
  3. 调用门代码[实现桥]
  4. 桥将参数转换为 C++ 风格的参数
  5. 该桥还转换了调用约定以匹配 C++
  6. 在 V8 中调用 C++ 运行时 API 包装器。
  7. 该包装器调用实际方法来执行操作。
  8. 当C++函数返回时,反之亦然。

这里可能涉及额外的步骤,但我想这本身就足以解释为什么头顶表面。

现在,我们来谈谈 JS 优化:V8 引擎附带的 JIT 编译器有 2 个部分:第一个部分只是将脚本转换为机器代码,第二个部分根据收集的配置文件信息优化代码。这是一个纯粹的动态事件,也是一个在静态编译空间中工作的 C++ 编译器无法比拟的绝佳机会。例如,在 JS 代码块中创建和销毁对象而不将其范围逃逸到块外的信息将导致 JIT 版本优化对象创建,例如堆栈分配(OSR),而这将始终在JS 堆,当调用原生版本时。

感谢您提出这个问题,这是一次有趣的对话!


jmr*_*mrk 5

(V8开发人员在这里.)如果没有看到您运行的代码,很难完全确定您观察到的效果,并且根据您的描述我无法重现它.特别是微量标记往往很棘手,而且它们似乎正在测量的相对加速或减速通常会产生误导,除非你已经证实在引擎盖下发生的事情正是你期望发生的事情.例如,可能是优化编译器能够消除整个工作负载的情况,因为它可以静态地证明结果不会在任何地方使用.或者可能是根本没有调用发生的情况,因为编译器选择内联被调用者.

一般来说,跨越JS/C++边界是有一定成本的,因为不同的调用约定和一些其他需要完成的检查和准备工作,比如检查可能抛出的异常.一个JavaScript函数调用另一个,而一个C++函数调用另一个,将比调用C++或其他方式的JavaScript更快.

这种边界跨越成本与任何一方的编译器优化级别无关.它也与字节码无关.("Hot",即频繁执行,JavaScript函数无论如何都被编译为机器代码.)

最后,V8不是C++编译器.它根本不是为了对C++代码进行任何优化而构建的.即使它尝试过,也没有理由认为它可以比现有的C++编译器做得更好-O3.(V8甚至没有看到你的C++模块的源代码,所以在你试验重新编译之前,你必须弄清楚如何提供这个源.)