This follow-up investigation identifies the next performance priorities after the optimizations tracked in #6, #7, #8, #9 and #10, building on the profiling work in #4.
本轮建议优先处理 Map 查找、真实数组的键处理、已有属性槽更新。证据主要指向多余的扫描、复制、字符串转换和布局发布;这些应先于更大范围的 VM/调用协议重构逐项验证。
被测版本与方法 / Measurement scope
- 被测快照:Eric-Song-Nop/quickjs-oxide@cdc35ec,包含静态属性 Atom、非数组提前返回、原始数值转换、引用清理和内建批量初始化等已有优化。发 issue 时该快照比上游 main
56faa471a5f923ef0fe9540e0b12f64b1e7ec6c5 多 38 个提交;本文数字属于这个优化后的快照,不是未经这些改动的上游 main。
- 机器:
eric-83am,AMD Ryzen 7 7840HS,Linux 6.18.44-1-lts;Rust 1.94.1 / LLVM 21.1.8。主要 timing/stat/CPU 采样固定 CPU 2,SMT sibling 为 CPU 3;未锁频或隔离整台机器。构建与正式测量分开串行执行。
- 普通 timing binary:release、debug=1、strip=none,profiling feature 关闭。另建 profiling binary,以及强制 frame pointer 的采样 binary;CPU 比例来自采样构建,墙钟 benchmark 来自普通构建。
- 参考:QuickJS 2026-06-04,GCC 16.2.1、默认 make 的
-O2。测量后独立重建的 .text/.rodata/.data/.data.rel.ro 与被测参考 ELF 逐字节相同。V8-v7 外部源码固定在 ahaoboy/js-engine-benchmark@2034d98fc8c5f8044e186267593f5d5ea5232caf。
- 覆盖 50 项固定 microbench、全部 8 项固定 V8 子套件、8 个 Map 规模配置、10 个对象宽度/构建对照配置;另保留原始 V8 单轮筛查和默认五项 microbench 三轮。
- microbench 抽取原始函数体及必要 helper;单独 pilot 后固定相同 N,两引擎每轮交错运行,各三轮。四项首次过短的转换负载另以 N=500000、五轮重测并替换汇总。固定 V8 保留 base/suite 原文和原断言,执行一次 Setup/run/TearDown,各三轮。原始自校准 harness 另行运行。
- 固定工作负载全部正式样本成功,完整可观测 stdout 在两引擎间一致;这只是 workload smoke check,不替代 Test262。
Benchmark:当前还差多少
以下均为 整进程墙钟中位数,单位 ms,包含启动、编译、输出和收尾;比例为 Oxide/QuickJS,越大表示 Oxide 越慢。它不是上游 ns/op、不是纯执行阶段时间,也不是预计能取得的加速倍数。* 表示 QuickJS 进程短于 20 ms,启动与调度影响不可忽略。
| Case |
固定 N |
Oxide ms |
QuickJS ms |
慢倍数 |
| empty_loop |
6084891 |
941.337 |
28.004 |
33.6× |
| prop_read |
846083 |
962.523 |
23.904 |
40.3× |
| prop_write |
314880 |
988.454 |
10.025 |
98.6× * |
| array_read |
258984 |
974.331 |
15.199 |
64.1× * |
| array_write |
62914 |
1018.251 |
6.412 |
158.8× * |
| array_slice |
872 |
934.589 |
14.383 |
65.0× * |
| typed_array_read |
136651 |
964.113 |
13.486 |
71.5× * |
| arguments_read |
19199 |
1005.923 |
10.039 |
100.2× * |
| func_call |
400000 |
814.395 |
29.778 |
27.3× |
| bigint64_arith |
1600 |
629.182 |
47.314 |
13.3× |
| map_set_string |
10 |
1022.977 |
4.225 |
242.1× * |
| map_set_int |
22 |
946.043 |
4.220 |
224.2× * |
| regexp_ascii |
113 |
975.183 |
16.314 |
59.8× * |
| string_build_large1 |
80 |
460.992 |
67.594 |
6.8× |
CPU profile:时间花在哪里
共 86 份采用的 profile、70,835 个样本、0 个 lost-event records。使用 cycles:u、499 Hz、frame pointers,最终以匹配 ELF/libc 符号和 perf script --no-inline --max-stack 512 解码。下表 12 项关键 microbench 均为三次 profile 的 period 加权结果。包含子调用的比例互相重叠,不能相加。
| Case |
样本数 |
热点 |
包含子调用 % |
| empty_loop |
1552 |
read_frame_binding |
20.50% |
| empty_loop |
1552 |
execute_hot_instruction |
52.19% |
| prop_write |
1538 |
prepare_set_property_with_receiver_in_realm |
66.77% |
| array_write |
1668 |
intern_property_key |
47.87% |
| array_slice |
1614 |
intern_property_key |
66.87% |
| typed_array_read |
1526 |
typed_array_canonical_numeric_index |
40.40% |
| arguments_read |
1562 |
create_arguments |
74.12% |
| arguments_read |
1562 |
replace_layout |
51.81% |
| map_set_int |
1495 |
find_map_record |
94.79% |
| map_set_string |
1840 |
find_map_record |
96.83% |
| regexp_ascii |
1523 |
replace_layout |
26.45% |
| func_call |
1282 |
snapshot_function_bytecode |
4.04% |
普通构建的硬件计数也表明存在大量额外工作:同一固定工作量下,Oxide 的 retired instructions 分别约为 QuickJS 的 20.6×(空循环)、102.8×(属性写入)、261.3×(数组写入)、414.5×(字符串键 Map)。数组写入 IPC 为 3.07 / 2.92,不能把其差距主要归结为 IPC 不足。
两个规模实验:避免只盯单个固定尺寸
Map 每个配置累计 32768 次 set + 32768 次 has,仅改变每个 Map 的容量及 Map 数量。三轮中位数,单位 ms;校验和均为 32768。
| 容量 |
整数键 Oxide / QuickJS |
字符串键 Oxide / QuickJS |
| 32 |
115.64 / 5.39 |
188.15 / 7.21 |
| 128 |
243.33 / 5.87 |
433.04 / 7.40 |
| 512 |
795.05 / 5.42 |
1612.88 / 6.72 |
| 2048 |
2825.61 / 4.82 |
5083.62 / 7.56 |
同样写已有属性 p0 十万次,改变对象的总属性数。另做相同对象创建和输出、但没有写循环的构建对照。以下仍是直接测得的全进程时间,没有扣除启动时间后冒充独立阶段测量。
| 属性数 |
含写循环 Oxide / QuickJS ms |
仅构建 Oxide / QuickJS ms |
| 8 |
96.68 / 3.34 |
4.21 / 1.67 |
| 32 |
112.43 / 3.35 |
4.42 / 1.73 |
| 128 |
177.33 / 3.35 |
5.42 / 1.75 |
| 512 |
411.22 / 3.25 |
6.04 / 1.77 |
| 2048 |
1352.78 / 2.88 |
11.47 / 2.23 |
建议拆开的九个优化点 / Proposed follow-ups
-
P0 · Map 索引与快照(find_map_record):每次先收集并复制全部存活记录,再逐项 root 和 SameValueZero 比较。先删除整表临时 Vec,再引入指向保序记录的哈希索引;只去掉 Vec 仍不能解决线性查找。保留 NaN/±0、字符串/BigInt 相等性、对象身份、删除后重加顺序、活迭代器与 GC 语义。
-
P0 · Array 数字索引与固定键(array_own_key):之前的提前返回解决的是非数组。真数组仍先 intern length 再识别 index,slice 等内建也存在整数→字符串→intern。让立即整数 Atom 先返回,并复用运行时固定键;保留洞、稀疏数组、原型 setter、只读 length 和索引上界。
-
P0 · 已有槽更新延迟复制(store_property_slot):在确认 existing slot 的 flags 未变之前,就复制 entries 和 slots。将同槽同 flags 的替换放在复制之前,只有布局变化才复制;进一步考虑 receiver==object 的 own writable data 路径。保留 Proxy/accessor、异 receiver、VarRef/AutoInit、冻结属性及所有权错误契约。宽度实验与该机制一致,但尚未通过优化后的 A/B 证明它是唯一原因。
-
P1 · TypedArray canonical-index 快路径(typed_array_canonical_numeric_index):立即整数索引仍经过 spelling→ToNumber→NumberToString→spelling comparison。可先处理已确定的非负整数 Atom,再合并无回调区间的 buffer 状态读取。注意 ta[-0] 与 ta["-0"]、非规范字符串、越界和 detached/resizable buffer;值转换可执行用户代码,之后仍需重验。
-
P1 · 平坦字符串路径(Utf16Units):平坦短字符串也建立含 61 个 Option 槽的通用遍历状态。为 Latin1/UTF-16 提供直接 slice 比较、哈希与范围复制,rope 保留通用路径。V8 RegExp 中 Utf16Units::next 自身约 24.8%,replacement 的重复 skip/range 扫描也应检查。维持 surrogate、跨表示相等性与 hash/Eq 一致性。
-
P1 · Arguments / RegExp 结果批量发布(Arguments、RegExp exec):高频新对象仍逐项安装属性、反复替换布局。复用已有“先准备、一次发布”的思想;保留 mapped arguments 别名/解除映射、strict callee、实际参数数,以及 RegExp lastIndex 转换顺序、compile 重入、groups/indices 和属性顺序。
-
P1 · VM local/值栈/dispatch(execute_inner):原始数值转换优化后,空循环热点转向直接 binding 读取、dispatch 和 numeric update。先证明 publication/verification 已保证哪些条件,再减少重复检查和中间 Value 搬运,评估安全的直接槽/栈窗口。保留 TDZ、eval/capture、回溯 PC、suspension 与外部字节码契约。
-
P2 · 函数帧与参数存储(take_call_arguments、callee frame):调用方 split_off 参数 Vec,callee 再 clone 参数并建立 locals、flags 和值栈。先在同步非逃逸调用中评估帧/参数存储复用;generator/async、arguments/eval、闭包逃逸和递归需要独立边界。
-
P2 · 小规模引用事务(retain_edges_transactionally):0/1/2 条边也建立 HashMap。短输入可采用小数组预检,复杂情况保留通用路径,不能削弱重复边计数、溢出预检和发布前失败保证。固定 RayTrace/Splay 的 replace_layout 分别约占 38%/21%,表明布局成本也存在于真实程序,不仅是 Context 初始化。
三个 critical 的架构/测量流程问题
- 局部操作被放大成全局工作。 单键查找复制全表、单槽写入复制整份布局、确定的整数索引往返字符串,这是可定位的算法与内部流程成本。应区分“不会调用用户代码的内部借用区间”和“可能重入的语义边界”,在必要的边界建立根引用,而不是到处套用完整公共 API 流程。保持
unsafe_code = "forbid",不靠删除所有权/异常检查换性能。
- 低分辨率 microbench 不能承担几个百分点改动的验收。 当前原始 microbench 的 Oxide 五项读数连续三次都是 200/500/400/500/400 ns,QuickJS 为 10/10/10/25/10 ns。Date.now fallback 等待下一 tick,并在约 2 ms 窗口取最小值,存在明显台阶。原始成绩保留;细粒度验收应使用足够长的固定工作量、交错重复、分布与指令数。还要注意
-T 的 coverage:本轮 Map 和 RegExp 大负载各只有 9 个 arena backing-storage 事件,不能据此认为临时 Vec 或字符串分配很少。
- 只从能完成的 harness 取得反馈,会遗漏最慢的一半套件。 旧报告诚实记录了 timeout,但这些路径仍缺少同等深度的热点反馈。本轮四个原始 V8 harness 在 60 秒内未完成,拆成单次工作却全部完成。上游 harness 有 warmup 和至少 32 次计时调用要求;单次耗时数秒会自然放大总时长。应同时保留原始 harness 状态与所有子套件的固定工作/限时采样,不能以可完成子集推导全引擎成绩。
全部 V8 子套件:单次工作与原始筛查
固定版各三轮中位数;原始版每引擎单轮、60 秒 timeout。固定版不输出上游 score,包含 setup/compile/teardown,不能冒充 steady-state score。
| Case |
固定 Oxide ms |
固定 QuickJS ms |
慢倍数 |
原始 Oxide 状态 |
| richards |
134.82 |
4.94 |
27.3× |
ok |
| deltablue |
208.04 |
8.74 |
23.8× |
ok |
| crypto |
4117.14 |
91.52 |
45.0× |
timeout |
| raytrace |
1643.19 |
30.51 |
53.9× |
ok |
| earley-boyer |
3819.20 |
94.23 |
40.5× |
timeout |
| regexp |
6992.77 |
308.23 |
22.7× |
timeout |
| splay |
3935.75 |
398.19 |
9.9× |
ok |
| navier-stokes |
3724.90 |
59.04 |
63.1× |
timeout |
采样质量、profiler 开销与验收限制
- 深栈必须单独解释:固定 Earley-Boyer 约 75.92% 的周期权重触及 127 帧上限;原始 12 秒前缀中 Earley-Boyer 为 92.80%、Crypto 为 48.30%。这些数据适合叶函数/近端热点,不能当完整调用树。固定 Richards/DeltaBlue 只有 82/121 个样本,相邻小热点不宜精确排名。
- 初次符号路径与 inline 展开设置有问题,最终全部从保留的原始 perf data 用匹配 ELF/libc、关闭 inline 展开重新解码。旧解码不用于本文结论。一次解析验证与广泛筛查短暂重叠,关键候选另做独立重复;正式固定墙钟 benchmark 在此前已完成。
- 11 轮模式轮换的小分配负载,普通/compiled-off/dump/trace 的全进程中位数为 11.902 / 12.102 / 13.358 / 12.227 ms,相对普通为 1.000 / 1.017 / 1.122 / 1.027×。包含报告 I/O,且该独立模式实验未固定核心;不能宣称通用 VM instrumentation overhead。
- 100 次独立生命周期样本中 Context 创建中位约 1.712 ms;未用 QuickJS CPU lifecycle time 除以 Oxide wall time。
- 本轮没有修改引擎,也没有重跑全量 Test262。新 fixed-work / CPU 采集工具与既有 Python benchmark 测试共 11 项通过。实现候选仍须独立 A/B、相关语义验证与完整回归;热点比例不等于未来收益。
建议的第一批验收
完整 50 项固定 microbench(ms,中位数 [min–max])
* 标记参考进程短于 20 ms。通常每引擎三轮;四项转换采用 N=500000 的五轮追加结果。
| Case |
N |
Oxide |
QuickJS |
慢倍数 |
| empty_loop |
6084891 |
941.337 [936.818–943.084] |
28.004 [25.242–30.716] |
33.6× |
| empty_down_loop |
4922868 |
727.989 [719.465–757.899] |
22.280 [22.173–22.572] |
32.7× |
| prop_read |
846083 |
962.523 [956.778–976.543] |
23.904 [23.167–24.287] |
40.3× |
| prop_write |
314880 |
988.454 [986.772–1014.869] |
10.025 [9.998–10.279] |
98.6× * |
| prop_update |
244870 |
984.076 [974.529–1001.705] |
9.354 [9.170–10.638] |
105.2× * |
| prop_create |
16000 |
473.764 [464.743–475.411] |
13.661 [13.342–14.824] |
34.7× * |
| prop_clone |
16000 |
354.274 [353.050–356.886] |
15.422 [15.218–15.574] |
23.0× * |
| prop_delete |
13775 |
933.515 [931.566–947.708] |
19.476 [19.289–20.910] |
47.9× * |
| array_read |
258984 |
974.331 [939.281–990.257] |
15.199 [14.947–15.566] |
64.1× * |
| array_write |
62914 |
1018.251 [993.866–1018.615] |
6.412 [5.475–6.886] |
158.8× * |
| array_update |
50862 |
991.071 [986.400–1016.193] |
6.412 [5.367–6.542] |
154.6× * |
| array_prop_create |
429 |
955.775 [948.984–962.737] |
5.871 [5.711–6.808] |
162.8× * |
| array_slice |
872 |
934.589 [929.606–942.152] |
14.383 [13.640–14.607] |
65.0× * |
| array_length_read |
763569 |
988.487 [971.999–1012.538] |
21.206 [20.980–21.212] |
46.6× |
| array_length_decr |
276 |
900.721 [889.720–910.088] |
12.216 [12.195–12.344] |
73.7× * |
| array_push |
536 |
954.363 [948.038–964.242] |
9.988 [9.929–11.055] |
95.6× * |
| array_pop |
357 |
956.865 [953.330–970.513] |
12.634 [12.605–13.189] |
75.7× * |
| typed_array_read |
136651 |
964.113 [960.264–966.960] |
13.486 [11.362–13.642] |
71.5× * |
| typed_array_write |
130706 |
942.938 [931.311–948.904] |
15.066 [14.250–15.294] |
62.6× * |
| arguments_read |
19199 |
1005.923 [996.929–1012.721] |
10.039 [9.968–10.179] |
100.2× * |
| arguments_strict_read |
21299 |
989.891 [984.626–1002.527] |
9.093 [8.195–9.700] |
108.9× * |
| global_read |
800000 |
475.010 [474.074–491.454] |
14.707 [14.498–14.808] |
32.3× * |
| global_write |
800000 |
513.182 [502.537–524.331] |
21.557 [21.346–21.838] |
23.8× |
| local_destruct |
16000 |
194.053 [193.707–196.012] |
6.304 [5.985–6.655] |
30.8× * |
| global_func_call |
400000 |
799.553 [793.922–864.780] |
31.861 [31.830–33.503] |
25.1× |
| func_call |
400000 |
814.395 [777.952–838.658] |
29.778 [29.338–30.055] |
27.3× |
| func_closure_call |
387594 |
971.655 [915.930–991.790] |
31.075 [31.040–31.168] |
31.3× |
| int_arith |
2936 |
895.295 [880.068–903.705] |
32.691 [30.275–32.980] |
27.4× |
| float_arith |
2318 |
956.662 [945.460–962.188] |
29.256 [28.546–29.576] |
32.7× |
| bigint64_arith |
1600 |
629.182 [628.469–654.626] |
47.314 [46.079–47.422] |
13.3× |
| map_set_string |
10 |
1022.977 [997.324–1046.565] |
4.225 [4.085–4.377] |
242.1× * |
| map_set_int |
22 |
946.043 [943.617–1013.494] |
4.220 [3.348–4.227] |
224.2× * |
| map_delete |
17 |
1003.507 [962.275–1019.598] |
4.575 [4.382–4.849] |
219.4× * |
| weak_map_set |
320 |
472.549 [464.130–476.554] |
20.806 [19.766–20.833] |
22.7× |
| array_for |
16000 |
872.099 [865.024–875.632] |
17.652 [16.930–17.819] |
49.4× * |
| array_for_in |
13525 |
923.327 [905.315–929.677] |
54.248 [53.667–54.789] |
17.0× |
| array_for_of |
8710 |
934.913 [925.805–944.980] |
16.330 [15.777–16.743] |
57.3× * |
| math_min |
1105 |
910.831 [909.563–911.811] |
32.099 [31.006–32.112] |
28.4× |
| regexp_ascii |
113 |
975.183 [961.477–1001.322] |
16.314 [16.209–16.830] |
59.8× * |
| regexp_utf16 |
114 |
978.061 [973.794–1006.473] |
16.397 [16.154–16.575] |
59.6× * |
| regexp_replace |
115 |
958.446 [949.552–958.699] |
72.638 [71.001–73.048] |
13.2× |
| string_length |
486553 |
927.943 [913.993–944.636] |
15.154 [14.997–15.207] |
61.2× * |
| string_build1 |
1600 |
413.676 [411.749–436.005] |
28.669 [27.232–29.176] |
14.4× |
| string_build3 |
1600 |
412.570 [410.640–420.591] |
48.273 [48.176–51.337] |
8.5× |
| string_build_large1 |
80 |
460.992 [457.152–561.467] |
67.594 [67.079–71.105] |
6.8× |
| string_build_large2 |
80 |
459.190 [449.246–464.798] |
65.614 [65.536–66.535] |
7.0× |
| int_to_string |
500000 |
337.349 [336.992–340.163] |
41.085 [39.540–43.002] |
8.2× |
| float_to_string |
500000 |
1637.664 [1625.477–1644.945] |
248.151 [246.025–254.227] |
6.6× |
| string_to_int |
500000 |
201.339 [196.078–212.734] |
28.860 [28.259–29.571] |
7.0× |
| string_to_float |
500000 |
181.219 [175.300–224.374] |
44.904 [43.703–45.081] |
4.0× |
原始证据状态
上述 benchmark/profile 数字已直接列在 issue 中。原始 perf data/stat、ELF、stdout/stderr、完整样本、构建 receipt 和复现脚本已保留在本地与 eric-83am;原始包尚未上传 GitHub,不把本地路径当作公开下载链接。
- 文件:
quickjs-oxide-broad-profile-eric-83am-2026-09-10.tar.gz(65,293,018 bytes,2751 个成员逐项验证)。
- SHA-256:
84b3f5d43b74ccd57aef352a6d05f3e916019895bfda52e720e0e679400b339f。
- 不包含外部 benchmark 源码或生成的第三方 workload。新建的 fixed-work/CPU 工具与完整报告目前仍是本地工作区改动,尚未有公开提交链接。
This follow-up investigation identifies the next performance priorities after the optimizations tracked in #6, #7, #8, #9 and #10, building on the profiling work in #4.
本轮建议优先处理 Map 查找、真实数组的键处理、已有属性槽更新。证据主要指向多余的扫描、复制、字符串转换和布局发布;这些应先于更大范围的 VM/调用协议重构逐项验证。
被测版本与方法 / Measurement scope
56faa471a5f923ef0fe9540e0b12f64b1e7ec6c5多 38 个提交;本文数字属于这个优化后的快照,不是未经这些改动的上游 main。eric-83am,AMD Ryzen 7 7840HS,Linux 6.18.44-1-lts;Rust 1.94.1 / LLVM 21.1.8。主要 timing/stat/CPU 采样固定 CPU 2,SMT sibling 为 CPU 3;未锁频或隔离整台机器。构建与正式测量分开串行执行。-O2。测量后独立重建的.text/.rodata/.data/.data.rel.ro与被测参考 ELF 逐字节相同。V8-v7 外部源码固定在ahaoboy/js-engine-benchmark@2034d98fc8c5f8044e186267593f5d5ea5232caf。Benchmark:当前还差多少
以下均为 整进程墙钟中位数,单位 ms,包含启动、编译、输出和收尾;比例为 Oxide/QuickJS,越大表示 Oxide 越慢。它不是上游 ns/op、不是纯执行阶段时间,也不是预计能取得的加速倍数。
*表示 QuickJS 进程短于 20 ms,启动与调度影响不可忽略。CPU profile:时间花在哪里
共 86 份采用的 profile、70,835 个样本、0 个 lost-event records。使用
cycles:u、499 Hz、frame pointers,最终以匹配 ELF/libc 符号和perf script --no-inline --max-stack 512解码。下表 12 项关键 microbench 均为三次 profile 的 period 加权结果。包含子调用的比例互相重叠,不能相加。read_frame_bindingexecute_hot_instructionprepare_set_property_with_receiver_in_realmintern_property_keyintern_property_keytyped_array_canonical_numeric_indexcreate_argumentsreplace_layoutfind_map_recordfind_map_recordreplace_layoutsnapshot_function_bytecode普通构建的硬件计数也表明存在大量额外工作:同一固定工作量下,Oxide 的 retired instructions 分别约为 QuickJS 的 20.6×(空循环)、102.8×(属性写入)、261.3×(数组写入)、414.5×(字符串键 Map)。数组写入 IPC 为 3.07 / 2.92,不能把其差距主要归结为 IPC 不足。
两个规模实验:避免只盯单个固定尺寸
Map 每个配置累计 32768 次 set + 32768 次 has,仅改变每个 Map 的容量及 Map 数量。三轮中位数,单位 ms;校验和均为 32768。
同样写已有属性
p0十万次,改变对象的总属性数。另做相同对象创建和输出、但没有写循环的构建对照。以下仍是直接测得的全进程时间,没有扣除启动时间后冒充独立阶段测量。建议拆开的九个优化点 / Proposed follow-ups
P0 · Map 索引与快照(find_map_record):每次先收集并复制全部存活记录,再逐项 root 和 SameValueZero 比较。先删除整表临时 Vec,再引入指向保序记录的哈希索引;只去掉 Vec 仍不能解决线性查找。保留 NaN/±0、字符串/BigInt 相等性、对象身份、删除后重加顺序、活迭代器与 GC 语义。
P0 · Array 数字索引与固定键(array_own_key):之前的提前返回解决的是非数组。真数组仍先 intern
length再识别 index,slice 等内建也存在整数→字符串→intern。让立即整数 Atom 先返回,并复用运行时固定键;保留洞、稀疏数组、原型 setter、只读 length 和索引上界。P0 · 已有槽更新延迟复制(store_property_slot):在确认 existing slot 的 flags 未变之前,就复制 entries 和 slots。将同槽同 flags 的替换放在复制之前,只有布局变化才复制;进一步考虑 receiver==object 的 own writable data 路径。保留 Proxy/accessor、异 receiver、VarRef/AutoInit、冻结属性及所有权错误契约。宽度实验与该机制一致,但尚未通过优化后的 A/B 证明它是唯一原因。
P1 · TypedArray canonical-index 快路径(typed_array_canonical_numeric_index):立即整数索引仍经过 spelling→ToNumber→NumberToString→spelling comparison。可先处理已确定的非负整数 Atom,再合并无回调区间的 buffer 状态读取。注意
ta[-0]与ta["-0"]、非规范字符串、越界和 detached/resizable buffer;值转换可执行用户代码,之后仍需重验。P1 · 平坦字符串路径(Utf16Units):平坦短字符串也建立含 61 个 Option 槽的通用遍历状态。为 Latin1/UTF-16 提供直接 slice 比较、哈希与范围复制,rope 保留通用路径。V8 RegExp 中 Utf16Units::next 自身约 24.8%,replacement 的重复 skip/range 扫描也应检查。维持 surrogate、跨表示相等性与 hash/Eq 一致性。
P1 · Arguments / RegExp 结果批量发布(Arguments、RegExp exec):高频新对象仍逐项安装属性、反复替换布局。复用已有“先准备、一次发布”的思想;保留 mapped arguments 别名/解除映射、strict callee、实际参数数,以及 RegExp lastIndex 转换顺序、compile 重入、groups/indices 和属性顺序。
P1 · VM local/值栈/dispatch(execute_inner):原始数值转换优化后,空循环热点转向直接 binding 读取、dispatch 和 numeric update。先证明 publication/verification 已保证哪些条件,再减少重复检查和中间 Value 搬运,评估安全的直接槽/栈窗口。保留 TDZ、eval/capture、回溯 PC、suspension 与外部字节码契约。
P2 · 函数帧与参数存储(take_call_arguments、callee frame):调用方 split_off 参数 Vec,callee 再 clone 参数并建立 locals、flags 和值栈。先在同步非逃逸调用中评估帧/参数存储复用;generator/async、arguments/eval、闭包逃逸和递归需要独立边界。
P2 · 小规模引用事务(retain_edges_transactionally):0/1/2 条边也建立 HashMap。短输入可采用小数组预检,复杂情况保留通用路径,不能削弱重复边计数、溢出预检和发布前失败保证。固定 RayTrace/Splay 的 replace_layout 分别约占 38%/21%,表明布局成本也存在于真实程序,不仅是 Context 初始化。
三个 critical 的架构/测量流程问题
unsafe_code = "forbid",不靠删除所有权/异常检查换性能。-T的 coverage:本轮 Map 和 RegExp 大负载各只有 9 个 arena backing-storage 事件,不能据此认为临时 Vec 或字符串分配很少。全部 V8 子套件:单次工作与原始筛查
固定版各三轮中位数;原始版每引擎单轮、60 秒 timeout。固定版不输出上游 score,包含 setup/compile/teardown,不能冒充 steady-state score。
采样质量、profiler 开销与验收限制
建议的第一批验收
完整 50 项固定 microbench(ms,中位数 [min–max])
*标记参考进程短于 20 ms。通常每引擎三轮;四项转换采用 N=500000 的五轮追加结果。原始证据状态
上述 benchmark/profile 数字已直接列在 issue 中。原始 perf data/stat、ELF、stdout/stderr、完整样本、构建 receipt 和复现脚本已保留在本地与 eric-83am;原始包尚未上传 GitHub,不把本地路径当作公开下载链接。
quickjs-oxide-broad-profile-eric-83am-2026-09-10.tar.gz(65,293,018 bytes,2751 个成员逐项验证)。84b3f5d43b74ccd57aef352a6d05f3e916019895bfda52e720e0e679400b339f。