【问题标题】:v8 Engine - Why is calling native code from JS so expensive?v8 引擎 - 为什么从 JS 调用本机代码如此昂贵?
【发布时间】:2017-03-18 12:29:42
【问题描述】:

基于对其他问题的多个答案,从 Javascript 调用本机 C++ 成本很高。

我用节点模块“基准”检查了自己,得出了同样的结论。

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

但是当添加一个对象的创建时,JS 仍然是大约 70 000 000 次调用/秒,但原生版本受到极大影响,下降到大约 2 000 000 次。

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

但是是什么让他们无法对 C++ 代码实现相同的优化呢? (或者至少打电话/了解什么会有所帮助)

【问题讨论】:

    标签: node.js v8


    【解决方案1】:

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

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

    这种跨界成本与任何一方的编译器优化级别无关。它也与字节码无关。 (“热”,即频繁执行,JavaScript 函数无论如何都会编译成机器码。)

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

    【讨论】:

    • 谢谢!也许它真的消除了那个代码部分,我将尝试一个更广泛的示例来弄清楚到底发生了什么,是否有可能为 JS 启用代码跟踪以检查它是否真的被执行? (或者这会影响优化水平吗?)我很清楚 v8 无法优化 C++ 部分。
    • 任何可观察到的副作用都会强制执行代码。例如,您可以在每次(否则不可观察的)循环迭代中增加一个全局计数器,然后检查该计数是否符合您的期望。一种正交方法(回答不同的问题)是--print-opt-code,用于检查生成了哪些优化代码。或者你可以在这里发布你的测试链接,让人们更容易理解你做了什么;-)
    【解决方案2】:

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

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

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

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

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

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

    【讨论】:

    • 我很确定有很多步骤,你能指出这些具体步骤发生在 v8 源代码中的什么地方吗?想检查一下! (我正在使用 0.12 atm 。例如 3.28,但最新版本很好)
    • 我猜这在 v8/src/code-stubs.cc 中有介绍,但不是 100% 肯定。解决此问题的一种调试方法是生成跟踪并将生成的跟踪与源相关联。让我看看我是否能够提供更具体的信息。
    • 在使用 node.js 时,您可以尝试使用 --trace 生成 JS 函数的跟踪信息,使用 --print_code 生成生成的机器码。两者都是重量级的,并且会产生很多痕迹,因此不能与性能检查一起运行。希望这会有所帮助!
    • 我想通过 v8/src/builtins/builtins.cc 也是值得的,因为来自 JS 代码的 API 调用会通过通过这些文件生成的代码。
    猜你喜欢
    • 1970-01-01
    • 2017-06-04
    • 1970-01-01
    • 2015-07-12
    • 2011-05-03
    • 1970-01-01
    • 2011-06-18
    • 2019-11-06
    • 1970-01-01
    相关资源
    最近更新 更多