test: 新增内存占用基准用例与留存内存采集

bench 每轮采集在 workload 后 promote 根持有/会话引用的 epoch 对象并强制
一次 full GC,新增 peak_bytes / retained_bytes / retained_objects 三口径
(原 session_* 字段为执行期累计分配,不反映 workload 后留存);dispatch
循环增加 session 堆高水位采样,VM 暴露 session_bytes_peak 与
collect_session_gc。新增 8 个 mem_* 压力用例(对象图/字符串/闭包链/数组
增删/跨调用保留/Map/Set 保留/字符串拼接),基线扩至 76 条目。
This commit is contained in:
root
2026-09-10 08:18:52 +00:00
parent 31d3f59d2b
commit 1b87e212f9
20 changed files with 1359 additions and 697 deletions
+92 -68
View File
@@ -1,68 +1,92 @@
Test Name Wall(ms) Comp(ms) Exec(ms) Instrs GC Ct IC Rate Sess Objs Sess B Test Name Wall(ms) Comp(ms) Exec(ms) Instrs GC Ct IC Rate Sess Objs Sess B Peak B Ret B
------------------------------------------------------------------------------------------------------------------------ ------------------------------------------------------------------------------------------------------------------------------------------------------
arith_ops 23.65 0.000 23.652 3000019 0 0.0% 0 0 arith_ops 47.27 0.000 47.271 3000026 0 0.0% 0 0 0 0
array_filter 232.56 0.000 232.558 41231 0 0.0% 2001 380120 array_filter 405.68 0.001 405.674 41244 0 51.2% 6001 1228144 1228144 2176
array_for_each 248.79 0.000 248.791 25231 0 0.0% 1 140120 array_for_each 374.59 0.001 374.588 25244 0 62.7% 4001 140144 140144 1088
array_index_get 8.60 0.000 8.602 1012031 0 0.0% 1 120 array_index_get 9.84 0.000 9.835 1012042 0 70.6% 1 144 8288 8288
array_index_set 12.24 0.000 12.243 1012029 0 0.0% 1 120 array_index_set 9.38 0.000 9.376 1012038 0 74.3% 1 144 8288 8288
array_join 165.75 0.000 165.750 3040019 0 0.0% 20000 4180000 array_join 113.52 0.001 113.520 3040032 0 98.4% 20000 4660000 4660000 419
array_literal 51.09 0.000 51.094 1150019 0 0.0% 50000 6000000 array_literal 84.01 0.000 84.007 1150028 0 87.4% 50000 8400000 8400000 336
array_map 224.96 0.000 224.960 41231 0 0.0% 2001 380120 array_map 386.85 0.001 386.846 41244 0 81.4% 6001 2028144 2028144 2976
array_push_pop 321.18 0.000 321.182 7450019 0 0.0% 50000 6000000 array_push_pop 215.52 0.001 215.517 7450032 0 91.3% 50000 7200000 7200000 360
array_reduce 254.39 0.000 254.386 43231 0 0.0% 1 140120 array_reduce 417.76 0.001 417.759 43244 0 93.7% 4001 140144 140144 1584
array_slice 73.67 0.000 73.674 58231 0 0.0% 3001 20928120 array_slice 85.12 0.001 85.122 58244 0 93.8% 3001 30672144 30672144 7824
array_sort 71.47 0.000 71.468 13931 0 0.0% 501 3504120 array_sort 108.11 0.000 108.110 13944 0 93.8% 1501 5128144 5128144 7824
branch_if 10.45 0.000 10.445 1866686 0 0.0% 0 0 branch_if 22.25 0.000 22.249 1866693 0 93.8% 0 0 0 0
branch_switch 10.60 0.000 10.604 1875019 0 0.0% 0 0 branch_switch 21.67 0.000 21.674 1875026 0 93.8% 0 0 0 0
call_args 36.06 0.000 36.064 760023 0 0.0% 2 275 call_args 51.47 0.001 51.465 720030 0 93.8% 2 35 499 499
call_closure 24.28 0.000 24.282 340035 0 0.0% 4 549 call_closure 24.62 0.000 24.622 340046 0 93.8% 4 69 997 997
call_method 29.61 0.000 29.605 420027 0 100.0% 3 395 call_method 32.01 0.000 32.014 420036 0 92.5% 3 195 819 819
call_new_proto 71.14 0.000 71.140 660029 0 37.2% 100002 12000310 call_new_proto 83.91 0.000 83.910 660038 0 89.4% 20004 3040070 3040070 1334
call_recursion 348.35 0.000 348.354 5525823 0 25.0% 2 275 call_recursion 522.22 0.001 522.221 5525830 0 88.0% 2 35 499 499
call_this 46.73 0.000 46.729 620029 0 37.9% 5 680 call_this 63.87 0.001 63.866 620038 0 86.1% 5 248 1344 1344
cmp_ops 29.79 0.000 29.792 3600019 0 42.9% 0 0 cmp_ops 70.09 0.000 70.095 3600028 0 85.0% 0 0 0 0
code_forge 0.01 0.000 0.005 3600019 0 42.9% 0 329 code_forge 804.26 0.000 804.263 85020 0 85.0% 10000 291670 291670 0
coercion_boolean 15.92 0.000 15.923 3050019 0 42.9% 0 0 coercion_boolean 36.78 0.000 36.776 3050026 0 85.0% 0 0 0 0
coercion_concat 41.84 0.000 41.837 1600019 0 42.9% 0 3888890 coercion_concat 55.45 0.000 55.452 1600028 0 85.0% 0 3888890 3888890 39
coercion_nullish 66.91 0.000 66.911 3650025 0 59.9% 1 120 coercion_nullish 109.39 0.000 109.392 3650036 0 81.3% 1 152 304 304
coercion_tonumber 14.94 0.000 14.935 2100019 0 66.7% 0 0 coercion_tonumber 29.99 0.000 29.989 1800026 0 79.3% 0 0 0 0
coercion_toprimitive 88.44 0.000 88.442 360019 0 66.7% 100000 13580000 coercion_toprimitive 111.99 0.001 111.990 360028 0 79.3% 100000 4780000 4780000 1327
coercion_tostring 123.01 0.000 123.014 4200019 0 66.7% 0 34200000 coercion_tostring 166.51 0.000 166.509 3900028 0 79.3% 0 22200000 22200000 186
gc_array 137.04 0.000 137.036 3110019 0 66.7% 5000 600000 gc_array 77.05 0.000 77.048 3110030 0 81.4% 5000 720000 720000 688
gc_object 25.42 0.000 25.418 1200019 0 73.4% 50000 6000000 gc_object 61.56 0.000 61.563 1200028 0 83.2% 50000 8000000 8000000 160
gc_string 6.89 0.000 6.885 320019 0 76.5% 0 788890 gc_string 10.66 0.000 10.660 320028 0 83.6% 0 788890 788890 40
ic_cache 14.22 0.000 14.216 412035 0 75.3% 1 120 gc_string_long 595.93 0.000 595.930 16000028 1 83.6% 0 7334531 33554441 41
ic_polymorphic 74.38 0.000 74.383 3433378 0 60.9% 100003 12000360 ic_cache 12.00 0.000 12.002 412044 0 83.6% 1 144 1642960 1642960
loop_do_while 6.60 0.000 6.604 1400015 0 55.7% 0 0 ic_polymorphic 93.71 0.000 93.711 3333393 0 85.3% 100003 16800504 16800504 1176
loop_for 37.24 0.000 37.240 8000019 0 55.7% 0 0 loop_do_while 12.68 0.000 12.678 1400022 0 86.2% 0 0 0 0
loop_for_in 146.11 0.000 146.112 4561231 0 55.7% 1 3610 loop_for 69.38 0.000 69.379 8000026 0 86.2% 0 0 0 0
loop_for_of 345.58 0.000 345.577 2734231 0 55.7% 1 108120 loop_for_in 183.54 0.000 183.538 4561244 0 86.2% 1 3610 3610 1064
loop_for_of_string 222.06 0.000 222.054 2266931 0 55.7% 0 58150 loop_for_of 412.69 0.000 412.684 2734244 0 86.2% 1 108144 108144 1088
loop_index 231.48 0.000 231.478 3649231 0 55.7% 1 120 loop_for_of_string 256.13 0.000 256.129 2266944 0 86.2% 0 58150 58150 132
loop_while 33.83 0.000 33.831 8000019 0 55.7% 0 0 loop_index 314.94 0.000 314.940 3649244 0 78.4% 1 144 1088 1088
loop_while_index 217.07 0.000 217.073 3649231 0 55.7% 1 120 loop_while 69.48 0.000 69.484 8000026 0 74.3% 0 0 0 0
map_ops 298.46 0.000 298.455 9850019 0 55.7% 0 0 loop_while_index 314.96 0.000 314.961 3649244 0 68.5% 1 144 1088 1088
math_ops 101.06 0.000 101.063 4800019 0 55.7% 0 0 map_ops 232.57 0.001 232.570 9850030 0 69.4% 50000 9600000 9600000 544
object_keys 204.96 0.000 204.959 2680019 0 55.7% 10000 15000000 math_ops 130.99 0.000 130.987 4800026 0 71.6% 0 0 0 0
prop_access 11.71 0.000 11.715 2200031 0 67.6% 1 120 mem_array_grow 200.91 0.000 200.915 2800026 0 71.6% 1 144 8288 8288
prop_bracket 12.05 0.000 12.047 1400029 0 74.0% 1 120 mem_closure_chain 86.90 0.000 86.901 480040 0 71.6% 40000 1278890 8799386 8799386
prop_delete 22.46 0.000 22.462 320029 0 74.4% 1 120 mem_cross_call_retain 159.32 0.001 159.322 1380034 0 70.4% 4 2670485 7951253 7951253
prop_dynamic 3.22 0.000 3.221 700023 0 75.3% 1 120 mem_map_retain 42.20 0.001 42.194 500023 0 69.9% 1 1977972 4609319 4609319
prop_nested 45.30 0.000 45.296 1200019 0 77.7% 150000 18000000 mem_object_graph 111.83 0.001 111.828 884726 0 70.0% 32769 5504888 11010200 11010200
prop_set 28.20 0.000 28.204 2400027 0 78.0% 1 120 mem_set_retain 32.60 0.001 32.603 360023 0 70.1% 1 1089082 4449274 4449274
property 87.66 0.000 87.663 72031 0 77.8% 1 231900 mem_string_accum 69.16 0.000 69.162 760022 0 70.2% 1 2744617 3064761 3064761
set_ops 283.31 0.000 283.314 7150019 0 77.8% 0 0 mem_string_rope 0.55 0.000 0.547 10028 1 70.2% 0 62108000 62108000 62108000
string_build 168.54 0.000 168.541 8440019 0 77.8% 0 73900000 member_compound 488.16 0.000 488.157 3200082 0 71.8% 100006 16000968 16000968 2104
string_case 44.80 0.000 44.799 1200023 0 77.8% 0 7600000 object_keys 313.98 0.001 313.981 2680030 0 72.6% 10000 15000000 15000000 424
string_char 75.01 0.000 75.014 2800019 0 77.8% 0 0 prop_access 21.34 0.000 21.341 2000040 0 74.3% 1 176 352 352
string_concat 36.82 0.000 36.817 1800019 0 77.8% 0 9000000 prop_bracket 29.50 0.000 29.496 1400040 0 75.7% 1 168 336 336
string_indexof 18.26 0.000 18.258 700023 0 77.8% 0 0 prop_delete 22.46 0.000 22.465 320038 0 75.8% 1 168 336 336
string_replace 13.25 0.000 13.248 460023 0 77.8% 0 1320000 prop_dynamic 6.19 0.000 6.190 700032 0 76.0% 1 120 272 272
string_replace_fn 122.85 0.000 122.849 50631 0 77.8% 0 4051150 prop_nested 261.03 0.001 261.030 1200028 0 76.8% 150000 22800000 22800000 912
string_split 74.43 0.000 74.426 400023 0 77.8% 20000 19560000 prop_set 31.92 0.000 31.922 2400036 0 78.3% 1 160 320 320
string_substring 38.86 0.000 38.860 1150023 0 77.8% 0 2150000 property 154.55 0.001 154.547 72040 0 79.0% 1 231900 231900 24264
string_trim 12.50 0.000 12.502 300023 0 77.8% 0 1560000 set_ops 372.45 0.001 372.447 7150030 0 79.8% 50000 9600000 9600000 464
try_catch_ok 2.12 0.000 2.116 550019 0 77.8% 0 0 string_build 458.30 0.000 458.295 8440030 2 80.3% 0 6791158 33554485 122
try_catch_throw 21.15 0.000 21.148 400019 0 77.9% 0 720000 string_case 69.83 0.000 69.830 1200032 0 80.3% 0 7600000 7600000 0
typeof_ops 88.37 0.000 88.370 2600019 0 78.0% 0 15200000 string_char 99.59 0.000 99.591 2800028 0 80.3% 0 0 0 0
unary_ops 11.52 0.000 11.517 2000019 0 78.0% 0 0 string_concat 70.02 0.000 70.020 1600034 0 80.3% 0 1900000 1900000 38
string_indexof 24.26 0.000 24.255 700032 0 80.3% 0 0 0 0
string_replace 19.15 0.000 19.149 460034 0 80.3% 0 1320000 1320000 66
string_replace_fn 238.27 0.001 238.267 50592 0 80.3% 4000 4049360 4049360 7177
string_split 133.50 0.000 133.495 400034 0 80.1% 20000 24200000 24200000 1562
string_substring 43.37 0.000 43.367 1150034 0 80.0% 0 2150000 2150000 43
string_trim 11.91 0.000 11.906 300032 0 80.0% 0 1560000 1560000 0
try_catch_ok 4.35 0.000 4.348 550026 0 80.0% 0 0 0 0
try_catch_throw 26.10 0.000 26.101 400026 0 80.0% 0 720000 720000 0
typeof_ops 51.55 0.000 51.544 2600026 0 80.0% 0 0 0 0
unary_ops 25.55 0.000 25.555 2000026 0 80.0% 0 0 0 0
内存基准用例(mem_*):
mem_object_graph 大型对象图留存:15 层满二叉树 32767 节点整体驻留 session 堆
mem_string_accum 字符串累积留存:40000 个互不相同的字符串驻留数组(interner 压力)
mem_closure_chain 闭包/upvalue 长链:10000 层闭包各自捕获前层闭包与 payload 驻留
mem_array_grow 数组增缩容量:扩到 200000 元素后截回 1000,观测元素向量容量是否回落(账目按 Vec capacity 计)
mem_cross_call_retain 跨调用长存活数据:全局 store 经 30000 次调用累积 30000 条记录
mem_map_retain 原生盒边持有:Map 持 20000 组字符串键 + 对象值
mem_set_retain 原生盒边持有:Set 持 20000 个对象(各带 payload 字符串)
mem_string_rope 拼接 rope 链留存:重复二元拼接形成深 Cons 链(账目按逻辑长度计,数值高于实际足迹)
口径:Peak B/Ret B 为 session 堆账目(对象头 + 堆数据 + 存活串/BigInt);epoch arena
的临时驻留无字节账目(既有口径,数量见 JSON 的 epoch_objects)。Ret B 为 workload 后
晋升 + 完整 GC 的真存活集。
+1016 -608
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -79,7 +79,7 @@ pub fn compare_baseline(current: &[MetricCollection]) -> Vec<Regression> {
let tolerance = match name { let tolerance = match name {
"wall_time_us" => 0.10, "wall_time_us" => 0.10,
"gc_trigger_count" | "gc_bytes_freed" | "gc_objects_scanned" | "gc_collection_us" => 0.50, "gc_trigger_count" | "gc_bytes_freed" | "gc_objects_scanned" | "gc_collection_us" => 0.50,
"session_bytes" => 0.20, "session_bytes" | "peak_bytes" | "retained_bytes" | "retained_objects" => 0.20,
_ => 0.20, _ => 0.20,
}; };
let ratio = cur_val / base_val; let ratio = cur_val / base_val;
+45 -11
View File
@@ -91,25 +91,53 @@ pub fn run_js_stress_bench(config: &BenchConfig, kernel: &Arc<KernelCore>, pool:
let _result = vm.run(&module); let _result = vm.run(&module);
let exec_time = exec_start.elapsed(); let exec_time = exec_start.elapsed();
// 执行期指标须在强制 GC 前读取:GC 会搬移/清扫 session 对象,
// 其后读到的计数是存活集而非本执行期的累计量
let gc_stats = vm.session_gc_stats(); let gc_stats = vm.session_gc_stats();
let gc_trigger_count = gc_stats.total_collections;
let gc_bytes_freed = gc_stats.last_collection_bytes_freed;
let gc_objects_scanned = gc_stats.last_collection_objects_scanned;
let gc_collection_us = gc_stats.last_collection_duration_us;
let session_objects = vm.session_object_count().saturating_sub(pre_session);
let session_bytes = vm.session_bytes_allocated();
let epoch_objects = vm.epoch_object_count().saturating_sub(pre_epoch);
let instruction_count = vm.instruction_count();
let ic_hit_rate = vm.ic_hit_rate();
let ic_hits = vm.ic_hit_count();
let ic_misses = vm.ic_miss_count();
// 留存口径:先让存活集对 session 完全可见——根直接持有的 epoch 对象
// (顶层 var 等)晋升进 session,session 对象持有的 epoch 子引用
// (闭包捕获、原生盒直插)同样晋升(均与 epoch 边界同机制);
// 再强制完整 GC 清掉不可达垃圾,清扫后账目即 workload 留存堆
vm.promote_rooted_epoch_objects();
vm.promote_session_epoch_refs();
vm.collect_session_gc();
let retained_bytes = vm.session_bytes_allocated();
let retained_objects = vm.session_object_count();
// 峰值高水位取执行期采样与 GC 后存活字节的较大者(存活集亦属高水位)
let peak_bytes = vm.session_bytes_peak().max(retained_bytes);
metrics.push(MetricCollection { metrics.push(MetricCollection {
test_name: test_name.clone(), test_name: test_name.clone(),
wall_time_us: (compile_time + exec_time).as_micros() as u64, wall_time_us: (compile_time + exec_time).as_micros() as u64,
session_objects: vm.session_object_count().saturating_sub(pre_session), session_objects,
session_bytes: vm.session_bytes_allocated(), session_bytes,
epoch_objects: vm.epoch_object_count().saturating_sub(pre_epoch), epoch_objects,
epoch_bytes: 0, epoch_bytes: 0,
gc_trigger_count: gc_stats.total_collections, gc_trigger_count,
gc_bytes_freed: gc_stats.last_collection_bytes_freed, gc_bytes_freed,
gc_objects_scanned: gc_stats.last_collection_objects_scanned, gc_objects_scanned,
gc_collection_us: gc_stats.last_collection_duration_us, gc_collection_us,
instruction_count: vm.instruction_count(), instruction_count,
compile_time_us: compile_time.as_micros() as u64, compile_time_us: compile_time.as_micros() as u64,
exec_time_us: exec_time.as_micros() as u64, exec_time_us: exec_time.as_micros() as u64,
ic_hit_rate: vm.ic_hit_rate(), ic_hit_rate,
ic_hits: vm.ic_hit_count(), ic_hits,
ic_misses: vm.ic_miss_count(), ic_misses,
peak_bytes: peak_bytes as u64,
retained_bytes: retained_bytes as u64,
retained_objects: retained_objects as u64,
}); });
} }
@@ -170,6 +198,9 @@ fn average_metrics(metrics: &[MetricCollection]) -> MetricCollection {
ic_hit_rate: 0.0, ic_hit_rate: 0.0,
ic_hits: 0, ic_hits: 0,
ic_misses: 0, ic_misses: 0,
peak_bytes: 0,
retained_bytes: 0,
retained_objects: 0,
}); });
} }
MetricCollection { MetricCollection {
@@ -189,5 +220,8 @@ fn average_metrics(metrics: &[MetricCollection]) -> MetricCollection {
ic_hit_rate: metrics.iter().map(|m| m.ic_hit_rate).sum::<f64>() / n, ic_hit_rate: metrics.iter().map(|m| m.ic_hit_rate).sum::<f64>() / n,
ic_hits: (metrics.iter().map(|m| m.ic_hits as f64).sum::<f64>() / n) as u64, ic_hits: (metrics.iter().map(|m| m.ic_hits as f64).sum::<f64>() / n) as u64,
ic_misses: (metrics.iter().map(|m| m.ic_misses as f64).sum::<f64>() / n) as u64, ic_misses: (metrics.iter().map(|m| m.ic_misses as f64).sum::<f64>() / n) as u64,
peak_bytes: (metrics.iter().map(|m| m.peak_bytes as f64).sum::<f64>() / n) as u64,
retained_bytes: (metrics.iter().map(|m| m.retained_bytes as f64).sum::<f64>() / n) as u64,
retained_objects: (metrics.iter().map(|m| m.retained_objects as f64).sum::<f64>() / n) as u64,
} }
} }
@@ -138,6 +138,9 @@ pub fn run_leak_detect(config: &BenchConfig, kernel: &Arc<KernelCore>, pool: &Ar
ic_hit_rate: vm.ic_hit_rate(), ic_hit_rate: vm.ic_hit_rate(),
ic_hits: vm.ic_hit_count(), ic_hits: vm.ic_hit_count(),
ic_misses: vm.ic_miss_count(), ic_misses: vm.ic_miss_count(),
peak_bytes: vm.session_bytes_peak() as u64,
retained_bytes: vm.session_bytes_allocated() as u64,
retained_objects: vm.session_object_count() as u64,
}); });
} }
} }
+12
View File
@@ -19,6 +19,15 @@ pub struct MetricCollection {
pub ic_hit_rate: f64, pub ic_hit_rate: f64,
pub ic_hits: u64, pub ic_hits: u64,
pub ic_misses: u64, pub ic_misses: u64,
/// 执行期 session 堆账目的峰值高水位(字节)。旧基线文件无此字段,反序列化补 0。
#[serde(default)]
pub peak_bytes: u64,
/// workload 结束并强制完整 GC 后的 session 存活堆字节(留存内存)。
#[serde(default)]
pub retained_bytes: u64,
/// workload 结束并强制完整 GC 后的 session 存活对象数。
#[serde(default)]
pub retained_objects: u64,
} }
impl MetricCollection { impl MetricCollection {
@@ -40,6 +49,9 @@ impl MetricCollection {
("ic_hit_rate", self.ic_hit_rate), ("ic_hit_rate", self.ic_hit_rate),
("ic_hits", self.ic_hits as f64), ("ic_hits", self.ic_hits as f64),
("ic_misses", self.ic_misses as f64), ("ic_misses", self.ic_misses as f64),
("peak_bytes", self.peak_bytes as f64),
("retained_bytes", self.retained_bytes as f64),
("retained_objects", self.retained_objects as f64),
] ]
} }
} }
+35 -4
View File
@@ -5,19 +5,47 @@ pub fn format_json(results: &[MetricCollection]) -> String {
serde_json::to_string_pretty(results).unwrap_or_else(|e| format!("{{\"error\": \"{}\"}}", e)) serde_json::to_string_pretty(results).unwrap_or_else(|e| format!("{{\"error\": \"{}\"}}", e))
} }
/// 内存基准用例(`mem_*` 前缀)的留存形态说明,随表格尾部输出。
const MEMORY_BENCH_NOTES: &str = "\n内存基准用例(mem_*):\n
mem_object_graph 大型对象图留存:15 层满二叉树 32767 节点整体驻留 session 堆
mem_string_accum 字符串累积留存:40000 个互不相同的字符串驻留数组(interner 压力)
mem_closure_chain 闭包/upvalue 长链:10000 层闭包各自捕获前层闭包与 payload 驻留
mem_array_grow 数组增缩容量:扩到 200000 元素后截回 1000,观测元素向量容量是否回落(账目按 Vec capacity 计)
mem_cross_call_retain 跨调用长存活数据:全局 store 经 30000 次调用累积 30000 条记录
mem_map_retain 原生盒边持有:Map 持 20000 组字符串键 + 对象值
mem_set_retain 原生盒边持有:Set 持 20000 个对象(各带 payload 字符串)
mem_string_rope 拼接 rope 链留存:重复二元拼接形成深 Cons 链(账目按逻辑长度计,数值高于实际足迹)
口径:Peak B/Ret B 为 session 堆账目(对象头 + 堆数据 + 存活串/BigInt);epoch arena
的临时驻留无字节账目(既有口径,数量见 JSON 的 epoch_objects)。Ret B 为 workload 后
晋升 + 完整 GC 的真存活集。
";
/// 把结果格式化为对齐的文本表格(供终端打印)。 /// 把结果格式化为对齐的文本表格(供终端打印)。
///
/// `Sess B`/`Peak B`/`Ret B` 为 session 堆账目(字节):执行期累计 / 峰值高水位 /
/// workload 后强制 GC 的存活留存。
pub fn format_text_table(results: &[MetricCollection]) -> String { pub fn format_text_table(results: &[MetricCollection]) -> String {
let mut out = String::new(); let mut out = String::new();
out.push_str(&format!( out.push_str(&format!(
"{:<35} {:>10} {:>10} {:>10} {:>12} {:>8} {:>8} {:>10} {:>10}\n", "{:<35} {:>10} {:>10} {:>10} {:>12} {:>8} {:>8} {:>10} {:>10} {:>10} {:>10}\n",
"Test Name", "Wall(ms)", "Comp(ms)", "Exec(ms)", "Instrs", "GC Ct", "IC Rate", "Sess Objs", "Sess B", "Test Name",
"Wall(ms)",
"Comp(ms)",
"Exec(ms)",
"Instrs",
"GC Ct",
"IC Rate",
"Sess Objs",
"Sess B",
"Peak B",
"Ret B",
)); ));
out.push_str(&"-".repeat(120)); out.push_str(&"-".repeat(150));
out.push('\n'); out.push('\n');
for m in results { for m in results {
out.push_str(&format!( out.push_str(&format!(
"{:<35} {:>10.2} {:>10.3} {:>10.3} {:>12} {:>8} {:>7.1}% {:>10} {:>10}", "{:<35} {:>10.2} {:>10.3} {:>10.3} {:>12} {:>8} {:>7.1}% {:>10} {:>10} {:>10} {:>10}",
truncate(&m.test_name, 35), truncate(&m.test_name, 35),
m.wall_time_us as f64 / 1000.0, m.wall_time_us as f64 / 1000.0,
m.compile_time_us as f64 / 1000.0, m.compile_time_us as f64 / 1000.0,
@@ -27,9 +55,12 @@ pub fn format_text_table(results: &[MetricCollection]) -> String {
m.ic_hit_rate * 100.0, m.ic_hit_rate * 100.0,
m.session_objects, m.session_objects,
m.session_bytes, m.session_bytes,
m.peak_bytes,
m.retained_bytes,
)); ));
out.push('\n'); out.push('\n');
} }
out.push_str(MEMORY_BENCH_NOTES);
out out
} }
+35 -1
View File
@@ -130,7 +130,7 @@ impl Vm {
/// 函数目标豁免均直落 epoch 值);Map/Set 等原生盒按键值直插 epoch 值。 /// 函数目标豁免均直落 epoch 值);Map/Set 等原生盒按键值直插 epoch 值。
/// `rewrite_object_values` 覆盖元素/meta/属性/proto/captured_this/home_object/ /// `rewrite_object_values` 覆盖元素/meta/属性/proto/captured_this/home_object/
/// cell 值;克隆子树经 forwarding 去重,环与共享引用各克隆一次。 /// cell 值;克隆子树经 forwarding 去重,环与共享引用各克隆一次。
pub(crate) fn promote_session_epoch_refs(&mut self) { pub fn promote_session_epoch_refs(&mut self) {
let objects = std::mem::take(&mut self.gc_state.session_object_ptrs); let objects = std::mem::take(&mut self.gc_state.session_object_ptrs);
if objects.is_empty() { if objects.is_empty() {
return; return;
@@ -185,6 +185,40 @@ impl Vm {
self.gc_state.session_object_ptrs.extend(objects); self.gc_state.session_object_ptrs.extend(objects);
} }
/// 把根直接持有的 epoch 对象(顶层 var 寄存器、挂起句柄等)晋升进 session,
/// 并把根引用改写到克隆体。
///
/// # 副作用
/// - 每个根 epoch 对象深克隆进 session(含 native 状态盒,子引用递归晋升),
/// 账目计入克隆字节;根引用按转发表重写。
///
/// # 注意事项
/// - 供 workload 后观测点(基准留存测量)使用:本方法之后存活集完全对
/// session 可见,后续完整 GC 的留存账目不再漏"仅驻留 epoch arena 的对象"。
/// - 与 `promote_session_epoch_refs` 互补:本方法处理根直接持有的 epoch 对象,
/// 后者处理 session 对象持有的 epoch 子引用(闭包捕获等绕过写屏障的来源)。
pub fn promote_rooted_epoch_objects(&mut self) {
let mut epoch_roots = Vec::new();
self.for_each_root(|value| {
if value.is_object() {
let ptr = value.as_js_object_ptr();
if !ptr.is_null() {
// SAFETY: 根上的对象值均为 VM 自有存活 JsObject。
if unsafe { &*ptr }.is_epoch() {
epoch_roots.push(ptr);
}
}
}
});
let mut forwarding = std::mem::take(&mut self.gc_state.forwarding);
for ptr in epoch_roots {
self.promote_object_inner(ptr, &mut forwarding);
}
crate::session_gc::rewrite_vm_roots(self, &forwarding);
forwarding.clear();
self.gc_state.forwarding = forwarding;
}
/// 逃逸写屏障:写向全局/session 对象的对象值不指向 epoch,否则 epoch /// 逃逸写屏障:写向全局/session 对象的对象值不指向 epoch,否则 epoch
/// 重置后悬垂。session 函数目标豁免晋升——克隆会令写入侧与字节码持有的 /// 重置后悬垂。session 函数目标豁免晋升——克隆会令写入侧与字节码持有的
/// 原始对象分裂(类构造器在原型上续建方法会落到非克隆体);函数持有的 /// 原始对象分裂(类构造器在原型上续建方法会落到非克隆体);函数持有的
+1 -1
View File
@@ -915,7 +915,7 @@ fn rewrite_forwarded_value(
.unwrap_or(value) .unwrap_or(value)
} }
fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>) { pub(crate) fn rewrite_vm_roots(vm: &mut Vm, forwarding: &HashMap<*mut JsObject, *mut JsObject, FxBuildHasher>) {
vm_debug!("[GC] rewrite_vm_roots: {} forwarded objects", forwarding.len()); vm_debug!("[GC] rewrite_vm_roots: {} forwarded objects", forwarding.len());
// 统一遍历:与 for_each_value 共用同一字段清单。 // 统一遍历:与 for_each_value 共用同一字段清单。
vm.rewrite_values(|value| rewrite_forwarded_value(value, forwarding)); vm.rewrite_values(|value| rewrite_forwarded_value(value, forwarding));
+28 -2
View File
@@ -969,6 +969,26 @@ impl Vm {
self.gc_state.session_bytes_allocated self.gc_state.session_bytes_allocated
} }
/// 执行期 session 堆账目的峰值高水位(顶层指令边界采样,全量重置清零)。
pub fn session_bytes_peak(&self) -> usize {
self.gc_state.session_bytes_peak
}
/// 无条件执行一次完整 session GC(mark + 移动式 sweep + 串/BigInt 清扫)。
///
/// # 副作用
/// - 存活对象复制进新 session arena,全部根与原生盒按转发表重写;
/// `session_bytes_allocated` 重置为清扫后的存活字节。
///
/// # 注意事项
/// - 须在执行外的安全点调用(无在途 builtin 局部裸指针、dispatch 未重入);
/// 执行期触发仍走水位路径,本入口供事后观测(如基准测 workload 后留存堆)。
pub fn collect_session_gc(&mut self) {
let mut session_gc = std::mem::take(&mut self.gc_state.session_gc);
session_gc.collect(self);
self.gc_state.session_gc = session_gc;
}
/// 当前 epoch 中已分配并跟踪的对象数量(未晋升到 session 的临时对象)。 /// 当前 epoch 中已分配并跟踪的对象数量(未晋升到 session 的临时对象)。
pub fn epoch_object_count(&self) -> usize { pub fn epoch_object_count(&self) -> usize {
self.gc_state.epoch_object_ptrs.len() self.gc_state.epoch_object_ptrs.len()
@@ -1517,10 +1537,16 @@ impl Vm {
// inline 状态(非 GC 根),此时回收会把调用方 regs 中的活值当死值释放。 // inline 状态(非 GC 根),此时回收会把调用方 regs 中的活值当死值释放。
// 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅 // 返回顶层后检查恢复,存活值此时已回拷为执行根。账目未超水位时仅
// 少量字段比较。 // 少量字段比较。
if self.native_call_depth == 0 && self.gc_state.session_bytes_allocated >= self.gc_state.string_gc_watermark if self.native_call_depth == 0 {
{ // 峰值高水位:同一边界采样 session 堆账目上界
let bytes = self.gc_state.session_bytes_allocated;
if bytes > self.gc_state.session_bytes_peak {
self.gc_state.session_bytes_peak = bytes;
}
if bytes >= self.gc_state.string_gc_watermark {
self.maybe_collect_session_strings(); self.maybe_collect_session_strings();
} }
}
if let Some(max_steps) = max_steps { if let Some(max_steps) = max_steps {
if steps > max_steps { if steps > max_steps {
vm_warn!("dispatch: step limit {} exceeded at pc={}", max_steps, self.pc); vm_warn!("dispatch: step limit {} exceeded at pc={}", max_steps, self.pc);
+3
View File
@@ -38,6 +38,9 @@ pub(crate) struct GcState {
/// `cell.value` 中的对象引用),只在 full_reset 统一释放。 /// `cell.value` 中的对象引用),只在 full_reset 统一释放。
pub(crate) session_cell_ptrs: RefCell<Vec<*mut UpvalueCell>>, pub(crate) session_cell_ptrs: RefCell<Vec<*mut UpvalueCell>>,
pub(crate) session_bytes_allocated: usize, pub(crate) session_bytes_allocated: usize,
/// 执行期 session 堆账目的峰值高水位(`session_bytes_allocated` 的采样上界),
/// 顶层指令边界采样,全量重置清零。留存内存观测用。
pub(crate) session_bytes_peak: usize,
/// 执行期字符串 GC 的触发水位:本次收集后的存活字节 + 阈值增量。 /// 执行期字符串 GC 的触发水位:本次收集后的存活字节 + 阈值增量。
/// 仅当账目超过水位才在指令边界触发回收——活串超阈值时不会每指令重复 /// 仅当账目超过水位才在指令边界触发回收——活串超阈值时不会每指令重复
/// 触发无死串可回收的白跑,且保证触发点恒在无 builtin 局部活值的边界。 /// 触发无死串可回收的白跑,且保证触发点恒在无 builtin 局部活值的边界。
+3
View File
@@ -100,6 +100,7 @@ impl Vm {
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()), session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_cell_ptrs: std::cell::RefCell::new(Vec::new()), session_cell_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0, session_bytes_allocated: 0,
session_bytes_peak: 0,
string_gc_watermark: gc_threshold, string_gc_watermark: gc_threshold,
gc_threshold_cached: gc_threshold, gc_threshold_cached: gc_threshold,
gc_watermark: gc_threshold, gc_watermark: gc_threshold,
@@ -212,6 +213,7 @@ impl Vm {
session_bigint_ptrs: std::cell::RefCell::new(Vec::new()), session_bigint_ptrs: std::cell::RefCell::new(Vec::new()),
session_cell_ptrs: std::cell::RefCell::new(Vec::new()), session_cell_ptrs: std::cell::RefCell::new(Vec::new()),
session_bytes_allocated: 0, session_bytes_allocated: 0,
session_bytes_peak: 0,
string_gc_watermark: gc_threshold, string_gc_watermark: gc_threshold,
gc_threshold_cached: gc_threshold, gc_threshold_cached: gc_threshold,
gc_watermark: gc_threshold, gc_watermark: gc_threshold,
@@ -329,6 +331,7 @@ impl Vm {
self.gc_state.epoch_object_ptrs.clear(); self.gc_state.epoch_object_ptrs.clear();
self.gc_state.session_epoch.reset(); self.gc_state.session_epoch.reset();
self.gc_state.session_bytes_allocated = 0; self.gc_state.session_bytes_allocated = 0;
self.gc_state.session_bytes_peak = 0;
self.gc_state.string_gc_watermark = self.kernel_core.config().session_gc_threshold; self.gc_state.string_gc_watermark = self.kernel_core.config().session_gc_threshold;
self.gc_state.session_gc = crate::session_gc::SessionGc::new(); self.gc_state.session_gc = crate::session_gc::SessionGc::new();
self.symbols.reset(); self.symbols.reset();
+8
View File
@@ -0,0 +1,8 @@
// 数组增缩容量:填满 200000 元素后截回 1000,观测容量是否回落
// 堆账目按元素 Vec capacity 计——截短后若不收缩,留存账目仍反映大容量
var arr = [];
for (var i = 0; i < 200000; i++) {
arr[i] = i * 3 + 1;
}
arr.length = 1000;
arr.length
+17
View File
@@ -0,0 +1,17 @@
// 闭包/upvalue 长链留存:10000 层闭包,每层同时捕获前层闭包与自身 payload 对象,
// 自 head 沿 prev 链回溯第一层时全部驻留(函数对象、upvalue cell、payload 串)
var head = null;
for (var i = 0; i < 10000; i++) {
(function (prev, payload) {
head = function () {
return [prev, payload];
};
})(head, { id: i, data: "closure_payload_padding_" + i });
}
var cur = head;
var hops = 0;
while (cur !== null) {
cur = cur()[0];
hops++;
}
hops === 10000
+12
View File
@@ -0,0 +1,12 @@
// 跨调用长存活数据:全局 store 经 30000 次函数调用累积 30000 条记录,
// 记录在函数内构造、写出 global 后跨帧存活(session 堆)
var store = { records: [], total: 0 };
function logRecord(tag, value) {
var r = { seq: store.total, tag: tag, value: value };
store.records.push(r);
store.total++;
}
for (var i = 0; i < 30000; i++) {
logRecord("tag_" + (i % 16), "record_payload_" + i);
}
store.records.length
+7
View File
@@ -0,0 +1,7 @@
// 原生盒边持有:Map 持 20000 组字符串键 + 对象值,
// 键串与值对象(及其 payload 串)全部经 Map 原生盒边驻留
var m = new Map();
for (var i = 0; i < 20000; i++) {
m.set("map_key_" + i, { id: i, data: "map_value_payload_" + i });
}
m.size
+16
View File
@@ -0,0 +1,16 @@
// 大型对象图留存:15 层满二叉树共 32767 个节点,顶层 root 整体驻留 session 堆
// 每节点 id 唯一(免意外共享),内节点带 left/right 子引用,叶节点带 leaf 标记
function makeNode(depth, base) {
if (depth === 0) {
return { id: base, leaf: 1 };
}
return {
id: base,
depth: depth,
left: makeNode(depth - 1, base * 2),
right: makeNode(depth - 1, base * 2 + 1),
};
}
var root = makeNode(14, 1);
root.left.id + root.right.right.id
+7
View File
@@ -0,0 +1,7 @@
// 原生盒边持有:Set 持 20000 个对象,每个对象带 payload 字符串,
// 全部经 Set 原生盒边驻留
var s = new Set();
for (var i = 0; i < 20000; i++) {
s.add({ id: i, data: "set_value_payload_" + i });
}
s.size
+7
View File
@@ -0,0 +1,7 @@
// 字符串累积留存:40000 个互不相同的字符串驻留数组
// 内容由索引推导(无去重可合并),数组本身经顶层 var 驻留
var pool = [];
for (var i = 0; i < 40000; i++) {
pool[i] = "str_" + i + "_" + (i * 7 % 997) + "_padding_padding_padding";
}
pool.length
+10
View File
@@ -0,0 +1,10 @@
// 拼接 rope 链留存:对 200 字节字面量重复二元拼接,形成深 Cons 链
// 每链节点账目按拼接逻辑长度计,报告值高于实际字节足迹;
// 链自顶层 var 驻留,节点地址稳定不搬移
var piece =
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
var chain = "seed_seed_seed";
for (var i = 0; i < 1000; i++) {
chain = chain + piece;
}
chain.length