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

时间:2017-03-18 12:29:42

标签: node.js v8

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

我用节点模块检查了自己"基准测试"得出了同样的结论。

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

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

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

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

2 个答案:

答案 0 :(得分:5)

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

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

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

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

答案 1 :(得分:4)

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

在我的理解中,交叉调用涉及的操作是:

  1. 准备参数。
  2. 保存JS上下文。
  3. 调用门代码[实现桥梁]
  4. 桥将参数转换为C ++样式参数
  5. 网桥还会将调用约定转换为匹配C ++
  6. 在V8中调用C ++运行时API包装器。
  7. 此包装器调用实际方法来执行操作。
  8. 当C ++函数返回时,反向执行相同的操作。
  9. 这里可能还有其他步骤,但我想这本身就足以解释为什么是高架表面。

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

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