ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

V8 ImportDefer 性能基准测试:用 `--js-defer-import-eval` 量化 import defer 提案的命名空间访问开销

V8 ImportDefer 性能基准测试:用 `--js-defer-import-eval` 量化 import defer 提案的命名空间访问开销 V8 ImportDefer 性能基准测试用--js-defer-import-eval量化 import defer 提案的命名空间访问开销【免费下载链接】v8The official mirror of the V8 Git repository项目地址: https://gitcode.com/gh_mirrors/v81/v8本指南围绕 V8 仓库中的 test/js-perf-test/ImportDefer/README.md 展开完整讲解该合成吞吐量基准套件的设计动机、文件组成、运行方式与结果解读。该套件用于衡量 ES 模块import defer提案import defer * as ns from ...在模块求值完成后反复访问其命名空间导出时的每次访问开销。读者将掌握如何手动运行d8驱动该基准、如何通过tools/run_perf.py接入 V8 官方性能测试体系以及如何正确解读 eager 与 deferred 两组对照数据。背景为什么要为 import defer 单独建一个基准import defer是 TC39 正在推进的 ES 模块提案允许开发者把模块求值推迟到首次访问其导出时才执行语法形如import defer * as ns from ...。该特性在 V8 中由实验性标志--js-defer-import-eval开启对应实现位于 src/flags/feature-flags.h 中的JS_FEATURE(js_defer_import_eval, defer import eval)。延迟求值能改善模块加载的启动性能但它引入了一个天然的性能疑点一个尚未求值的 deferred 命名空间其属性访问必须触发模块求值必然比普通命名空间更慢。因此社区真正关心的问题是——一旦模块已经求值完毕deferred 命名空间的读取是否还能回到普通命名空间的水平。这正是 ImportDefer 基准套件要回答的问题在模块完成求值后对 deferred 命名空间的每次访问开销应与对普通命名空间的访问开销相当。HotAccess基准臂arm在一个紧循环中密集读取模块命名空间的导出。每个场景都配备一个eager 控制组import * as和一个deferred 实验组import defer * as两者唯一的差异就是 import 关键字本身从而把命名空间每次访问的成本从噪声中隔离出来。套件文件组成与各自职责该套件位于 test/js-perf-test/ImportDefer 目录共包含 5 个文件文件角色run.jsBenchmarkSuite 吞吐量驱动注册HotAccessEager/HotAccessDefer两个基准value.js被两个基准臂共同读取的可变导出模块hotaccess-eager.jseager 控制组import * as m from value.jshotaccess-defer.jsdeferred 实验组import defer * as m from value.jsImportDefer.json独立的性能测试注册配置供tools/run_perf.py使用这种一对孪生模块 一个公共被读取模块的布局是刻意设计的两个基准臂除了import关键字外逐字相同比较 hotaccess-eager.js 与 hotaccess-defer.js 即可验证因此任何测量到的差异都可以归因于 deferred 命名空间的访问路径而不是代码形状差异。测量逻辑HotAccess 基准臂的逐行剖析数据源模块 value.jsvalue.js 导出一个可变状态let value初始为 0set(x)负责改写它另外导出 20 个常量a0到a19。这些常量取值 019是基准自检的关键素材。核心循环20 次导出读取 1 次状态写入两个基准臂的bench()函数逻辑完全一致hotaccess-eager.js 与 hotaccess-defer.jsexport function bench() { for (let i 0; i iterations; i) { // iterations 10000 let accumulator m.value; // 读取可变导出 accumulator m.a0; // 依次累加 20 个导出 // ... m.a1 .. m.a19 依次累加 m.set(accumulator); // 把结果写回导出状态 } if (m.value ! 190 * iterations) throw new Error; // 自检 m.set(0); // 复位 }每一轮迭代对命名空间执行 20 次属性读取和 1 次函数调用m.set。iterations在 run.js 中定义为10000。值得注意的是自检逻辑20 个常量之和恰好为0 1 ... 19 190因此 10000 次迭代后的累计值必为190 * iterations。这个等式由 value.js 的导出设计直接保证——fixtures 自检失败会抛出异常从而把deferred 访问语义出错但性能数字正常这类隐蔽 bug 拦截在基准运行期而不只是依赖 perf harness 的结果正则去发现问题。README 中fixtures self-checkm.value ! 190 * iterations并在失败时抛出的描述正是对这段代码的总结。驱动层 run.js异步导入 微任务强制执行run.js 的关键在于bench()依赖的模块必须先完成求值基准测量才有意义测的是已求值 deferred 命名空间的访问成本。由于这里使用动态import()Promise 语义模块在微任务中加载run.js 借助 V8 内部函数%PerformMicrotaskCheckpoint()在同步代码中强制执行微任务队列function HotAccessEager() { let success false; import(hotaccess-eager.js).then(m { m.bench(); success true; }); %PerformMicrotaskCheckpoint(); // 强制跑完模块加载微任务 if (!success) throw new Error(Error evaluating hotaccess-eager.js); }这正是--allow-natives-syntax标志的用武之地——没有它%PerformMicrotaskCheckpoint()无法解析。若模块求值失败例如hotaccess-eager.js内部throwsuccess保持false驱动层会抛出Error保证错误被显式上报而非静默吞掉。本地运行手动用 d8 驱动按 README 的说明本地运行必须从套件所在目录内执行——相对 specifier如hotaccess-eager.js是相对进程 cwd 解析的而 perf harnesstools/run_perf.py恰好也以套件路径作为 cwd 来运行 d8cd test/js-perf-test/ImportDefer ../../../out/arm64.release/d8 --allow-natives-syntax --js-defer-import-eval run.js说明请把out/arm64.release/d8换成你本机实际的构建输出路径如out/x64.release/d8要求 d8 二进制已启用 release 构建--allow-natives-syntax用于启用 run.js 中的%PerformMicrotaskCheckpoint()内部调用--js-defer-import-eval启用 import defer 特性deferred 基准臂依赖它由于套件内同时注册了 eager 与 deferred 两个 BenchmarkSuite见 run.js一次运行会同时打印两组分数。运行成功的标志是打印出预期的分数行*-ImportDefer(Score): ...且没有异常抛出——fixtures 自检会在数值不符时主动throw。性能测试注册为什么是独立配置与大多数 js-perf-test 套件不同ImportDefer刻意没有并入批量的JSTests1..5.json分组而是仿照ClassFields.json、RegExp.json等先例拥有自己独立的配置 test/js-perf-test/ImportDefer.json。这意味着它只在 runner 被显式指向该配置时才运行不会混入常规批量基准轮次——原因显而易见该基准测量的是仍处于实验期的--js-defer-import-eval特性需要专用标志且测量目标deferred 命名空间访问成本与常规套件关注的启动/吞吐场景不同。配置要点test/js-perf-test/ImportDefer.jsonflags: [--allow-natives-syntax, --js-defer-import-eval]perf harness 注入的运行标志与手动运行命令保持一致units: score结果以 ops/sec 计越高越好run_count/run_count_arm64均为 1每个基准只跑一次timeout/timeout_arm64分别为 120 / 240 秒为 64 位 ARM 平台预留了更长超时results_regexp: ^%s\\-ImportDefer\\(Score\\): (.)$与 run.js 中PrintResult的输出格式name -ImportDefer(Score): result精确对应harness 据此从 d8 输出中抓取分数resources列出value.js、hotaccess-eager.js、hotaccess-defer.js确保这些被动态导入的文件随套件一并部署。通过tools/run_perf.py显式运行该套件tools/run_perf.py --arch arm64 --binary-override-path out/arm64.release/d8 \ test/js-perf-test/ImportDefer.json--binary-override-path是 tools/run_perf.py 提供的参数用于显式指定 d8 二进制路径绕过对构建目录的自动探测自动探测在多构建目录共存时可能产生歧义参见其Found ambiguous build directories校验逻辑。结果解读关注组内对照而非绝对值run.js 通过PrintResult输出HotAccessEager-ImportDefer(Score)与HotAccessDefer-ImportDefer(Score)两个分数单位是 ops/secunits: score越高越好测量的是稳态下每次访问的成本。解读时有两条铁律README 已明确强调绝对数值不具备跨机器可比性——绝对数字取决于机器性能与运行环境CPU 频率、是否被调度、d8 构建配置等同一组分数在不同机器上不可横向比较真正的信号是单次运行内部 eager 与 deferred 的对比——由于两个基准臂除 import 关键字外逐字相同HotAccessEager分数与HotAccessDefer分数的比值才精确反映了模块已求值后deferred 命名空间每次访问的相对开销。按设计假设第一次访问触发模块求值之后deferred 命名空间的反复读取应与普通命名空间读取一样快即两组分数应趋于接近。若某次提交导致HotAccessDefer相对HotAccessEager出现明显回退就说明 deferred 命名空间的稳态访问路径引入了额外成本这通常是命名空间访问在延迟求值场景下的惰性解析、缓存或 deopt 处理退化所致——这正是该基准要在性能回归测试中提前暴露的问题。与其他相关资源的衔接基准运行依赖的实验性标志定义在 src/flags/feature-flags.h属于 JS feature 类标志可用--js-defer-import-eval/--no-js-defer-import-eval开关性能测试的整体执行入口与参数体系可参考 tools/run_perf.py 与 docs/test.md若要为 import defer 补充功能与回归测试可参照 test/mjsunit 下模块类测试的组织方式结合--js-defer-import-eval编写验证语义正确性的用例如延迟求值时机、ModuleNamespace访问行为等与本基准形成功能正确性 性能回归的完整覆盖。【免费下载链接】v8The official mirror of the V8 Git repository项目地址: https://gitcode.com/gh_mirrors/v81/v8创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表