Skip to content

perf: QuickJS constant-factor plan — S0–S2 fast paths + S3 architecture and build baseline (E) - #27

Open
Eric-Song-Nop wants to merge 37 commits into
mainfrom
session/dd42dc95
Open

Eric-Song-Nop wants to merge 37 commits into
mainfrom
session/dd42dc95

Conversation

@Eric-Song-Nop

@Eric-Song-Nop Eric-Song-Nop commented Sep 18, 2026

Copy link
Copy Markdown
Contributor

概要

分阶段缩小与 QuickJS 的常数因子差距,保持 plain RC + 循环回收模型(无 JIT)。本 PR 已完成 S0–S2 可信快路S3 方案 E(构建基线);S3 的结构改造(A 8B 值表示 / B quickening / D 数据导向堆)按设计文档另起。

关键文档

文档 内容
docs/reports/performance-plan.md S0–S2 计划、实现与度量,以及各引擎的横向设计比较
docs/reports/performance-architecture.md S3 架构设计:8B 值表示 + quickening + 数据导向堆(无 JIT、默认无 unsafe)

我们做了什么

S0:测量基线

新增 scripts/benchmark/property_read_probe.py;S0 证据(Result/Option 管道、copy_value、IC 归属)保留在 performance-plan.md §2。结论:主导成本是 Result/Option 管道 + 堆 binding 借用。

S1:可信快路

  • Node.strongCell<u32> + retain_raw_fast(快路饱和,通用路径保持报错)。
  • 可信访问器 live_node_fast(_mut) / var_ref_fast(_mut) / object_fast,无失败根转换 take_owned_raw_value_fast
  • 共享借用快路 read_owned_cell_fast / read_run_cell(captured)与 property_ic_read_fast(数据属性命中)。
  • 精简 try_replace_immediate_var_ref_value
case pre-S1 ns/op S1 ns/op 变化
prop_read_int 222.87 185.38 −16.8%
prop_read_obj 279.78 234.58 −16.2%
prop_read_string 306.63 284.84 −7.1%

S2:可信路径收尾

已落地 S2.2(可信 IC 读)PropertyReadCache::read_location 改用 object_fast/shape_fast;新增 slot_object_release_readiness_fast

case pre-S1 S1 S2 pre-S1→S2
prop_read_int 222.73 185.97 184.35 −17.2%
prop_read_obj 281.70 236.71 220.22 −21.8%
prop_read_string 300.87 280.10 280.86 −6.7%

S2.1(快速释放)与 S2.3(非失败 push)已撤销:与既有契约冲突(blocked_borrow_and_deferred_release_do_not_commit_or_drain 固定「任意借用都 defer 释放」;operand_push_index 的容量失败是被事务测试覆盖的可恢复路径),需单独立项。

S3:架构设计 + 方案 E(构建基线)

  • 设计定稿于 docs/reports/performance-architecture.md
  • Cargo.toml[profile.release] lto = "fat", codegen-units = 1
  • 新增 scripts/benchmark/pgo.py:instrumented 构建 → 训练 → llvm-profdata merge → profile-use 重建,带 build receipt;逐进程唯一 LLVM_PROFILE_FILE,训练用 scaling.py(22 case×{64,128})+ v8-v7 全部 suite。

性能总结(pre-E HEAD 06386457 → E,同机串行)

套件 指标 LTO+CGU=1 +PGO
V8-v7(8 suite) Score geomean +9.1% +60.0%(×1.60)
scaling.py(44 cell) 整进程 wall geomean ×0.898 ×0.696(−30.2%)
属性读探针(3 case) ns/op −5.5% ~ −8.8% −26.9% ~ −37.0%

相对 pinned QuickJS 仍有结构差距:V8-v7 Score 慢约 11.9×(E 前 19.0×),整进程 wall 慢约 5.7×(E 前 8.2×),属性读慢 6.5–10.7×。E 只抬高比较基线,收敛留给 A/B/D。

验证

  • cargo test --locked --workspace --all-targets:全绿(lib 2278、oracle 907、CLI 32 等)。
  • cargo fmtcheck-source-layout.py、benchmark 单测(22):通过;未新增 clippy lint。
  • Test262 全量零回归--fulltotal=102037 pass=79982 runnable=80032,门禁 complete Test262 vector matches;仅产出 current-source receipt,未改 current.conf(方案 E 的 Cargo.toml 变更使 --check 如预期报源码过期)。

后续

  • A/B/D:8B 值表示、quickening、数据导向堆,按 performance-architecture.md 分阶段推进。
  • S2.1b/S2.3b:延迟释放与 operand push 的契约改造需独立设计。

Add a diagnostic probe that generates monomorphic a.b property-read
workloads, measures median wall time/ns-per-op per engine, and records a
perf symbol report. Commit the S0 findings: Result/Option plumbing and the
var-ref binding path dominate, not reference counting itself.
Record the S1 scope and algorithm: Cell-backed refcounts, trusted
non-fallible heap accessors, infallible root conversion, and shared-borrow
fast paths for the property-read and captured-cell hot paths.
Make heap reference counts Cell-backed and add non-fallible trusted
accessors, then route the hot captured-cell and data-property reads
through shared-borrow fast paths that skip Result plumbing and the
generation check. Symbols and non-data cases fall back to the ordinary
fallible paths, so behavior is unchanged.

Measured with the S0 property-read probe (N=5,000,000, median ns/op):
prop_read_int -14.6%, prop_read_obj -10.1%, prop_read_string -8.6%.
Route PropertyReadCache::read_location through the trusted object/shape
accessors and add a validation-free slot release-readiness check for the
shared-borrow property-read fast path. Removes the generation checks and
Result plumbing that remained on an IC hit.

Measured with the property-read probe (N=5,000,000, repeat 7): object
property reads improve 7.0% over S1 (21.8% over pre-S1); int/string are
unchanged. S2.1 fast release and S2.3 infallible operand pushes were
dropped because they conflict with the existing deferred-release and
transaction-failure contracts.
Rename docs/reports/s1-plan.md to performance-plan.md since it now holds the
S0-S3 plan, decisions, results, and an overview index.
Drop the S3 value-slimming plan and add a horizontal comparison of QuickJS,
Lua 5.4, LuaJIT, V8, JSC, SpiderMonkey, CPython, Boa, and the current
quickjs-oxide across value representation, property keys, GC, allocator,
dispatch, inline caching/specialization, and JIT, plus the requirements for a
2x no-JIT target.
pgo.py used LLVM's default profile filename, so every training process
overwrote the same .profraw and only the last workload's counters
survived. Set a unique LLVM_PROFILE_FILE per process before training and
merge the resulting raws. Drop the unused --training-dir/features path
and default training sizes to 64/128 so every case (including
regexp-groups) is valid. Document the PGO flow in the benchmark README
and record the S3 plan E measurements (LTO+CGU=1 and PGO vs pre-S3
HEAD) in the design.
@Eric-Song-Nop Eric-Song-Nop changed the title perf: heap fast-path toward QuickJS (S0: property-read cost probe) perf: QuickJS constant-factor plan — S0–S2 fast paths + S3 design/build baseline (E) Sep 18, 2026
@Eric-Song-Nop
Eric-Song-Nop marked this pull request as ready for review September 18, 2026 14:06
Stage-numbered document names are hard to track. Rename the S3 design
to a content-descriptive name and update the cross-references in the
performance plan. The document title drops the stage prefix as well.
@Eric-Song-Nop Eric-Song-Nop changed the title perf: QuickJS constant-factor plan — S0–S2 fast paths + S3 design/build baseline (E) perf: QuickJS constant-factor plan — S0–S2 fast paths + S3 architecture and build baseline (E) Sep 18, 2026
The report was stage-numbered and its key evidence (Result/Option
pipeline cost, copy_value, IC attribution) is already recorded in the
performance plan's hotspot section, with the probe script as the
reproduction source. Remove the duplicate and point the plan at section
2 instead.
Update the probe example output and plain-build paths to descriptive
target names, and record in the architecture doc that release LTO/CGU
apply to every `cargo build --release` while PGO requires the explicit
two-stage pgo.py flow.
@Eric-Song-Nop

Copy link
Copy Markdown
Contributor Author

评测口径的诚实说明(PGO / 构建对照)

补充一处需要打星号的地方,避免把构建层收益当成结构优势。

1. V8-v7 的 ×1.60 是 in-distribution(背题)

PGO 的训练负载是 scaling.py(全 22 case×{64,128})+ v8-v7 全部 8 个 suite,而基准又用同一套 v8-v7 评测。这是 train-on-test / benchmark overfitting

  • V8-v7 ×1.60 偏乐观,不能当作泛化收益引用
  • scaling.py 的 case 也全部进了训练(只是 size 不同,同代码路径),严格说也是弱 in-distribution;
  • 相对干净的是 property_read_probe(负载本身未参与训练)——−27% ~ −37%,但属性读代码路径同样被 training 覆盖,只能算弱 held-out,不是无偏泛化证明。

正确做法是评测集留白:要么用一个完全不参与训练的 suite,要么只在真实世界 corpus 上训练。PGO 技术本身没问题,有问题的是这次的具体口径。

2. 与 QuickJS / Boa 的构建对照不对等

项目 release 构建 来源
QuickJS(本仓库 pinned oracle) -O2无 LTO、无 PGO make qjs;上游 MakefileCONFIG_LTO 注释掉,CONFIG_PROFILE 是 gprof
quickjs-ng CMake Release,默认无 PGO 上游
Boa lto = "fat" + codegen-units = 1,无 PGO [profile.release]
V8 / Chrome / SpiderMonkey LTO + PGO 工业发布标配

因此:

  • 相对 QuickJS 的「E 后仍慢 5.7–12×」是偏乐观的我方口径——我们拿最优构建(LTO+PGO)比上游默认构建(-O2)。若给 QuickJS 也开 CONFIG_LTO=y + PGO,它的分数会上升,相对差距不会缩小
  • 相对 Boa,LTO/CGU 半截不构成优势(Boa 本就有),只有 PGO 是增量。

3. 结论

  • E 作为构建层收益是真实的:LTO/CGU 对任何 cargo build --release 自动生效;PGO 需显式跑 pgo.py,且 profile 与源码/rustc 版本绑定。默认 release 不含 PGO。
  • 不应把 V8-v7 ×1.60 当作对 QuickJS 的泛化优势;对外报数必须声明构建口径(「生产最优构建 vs 上游默认构建」)。
  • 要得到结构差距的无偏结论,需要:评测 suite 留白,或不参与训练的 corpus 训练 PGO,或给 QuickJS 同等构建待遇后再比。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant