rustfs-erasure-codec 7.0.1

Rust implementation of Reed-Solomon erasure coding
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
# 阶段 4:SIMD 架构升级与运行时分发

## 1. 阶段目标

将当前 crate 的 SIMD 能力从“编译期开关 + 静态路径”升级为“运行时多 ISA 分发 + 后端可扩展架构”。

## 2. 为什么这个阶段重要

当前 SIMD 不是没有,而是架构还不够现代:

- 构建期选路
- 通用分发不够灵活
- 缺少高层 dispatch 层
- 缺少 GFNI 的显式优先级控制

相比之下,MinIO 所依赖的底层库在这方面更成熟。

## 3. 改造目标

1. 运行时探测 CPU 能力
2. 运行时选择最优后端
3. 保留 scalar fallback
4. 支持 x86_64 与 aarch64 的多后端
5. 后端与 core 编码逻辑解耦
6. 明确 C 与 Rust 双路线的迁移策略

## 3.1 SIMD 实现语言决策

本阶段明确采用以下技术决策:

1. 现有 `C` SIMD 内核继续保留
2. 现有 `C` SIMD 内核不再被视为长期唯一实现
3. 后续新增 SIMD 后端优先采用 Rust `std::arch`
4. 全量 Rust 重写不在本阶段一次性完成
5. C 与 Rust 后端必须并存一段时间,并通过一致性测试对照

### 为什么不继续长期纯 C

原因:

- 构建与交叉编译更复杂
- 与 Rust 核心逻辑边界较硬
- runtime dispatch 管理不够自然
- 后续接入新 ISA 时维护成本更高

### 为什么不现在立刻全量改写为 Rust

原因:

- 范围过大,风险高
- GFNI / AVX512 / NEON 这些路径重写难度高
- 容易在短期内丢失已有性能优势
- 若没有 benchmark 基线和 backend 抽象,重写价值难证明

### 最终方向

最终技术方向不是“继续 C”也不是“立刻纯 Rust”,而是:

- 短期:C 作为稳定高性能基线
- 中期:Rust backend 逐步接管
- 长期:Rust 成为主实现,C 仅保留为 fallback 或 legacy feature

## 4. 建议架构

### 4.1 backend 层

建议抽象出:

```rust
struct GaloisBackend {
    mul_slice: fn(u8, &[u8], &mut [u8]),
    mul_slice_xor: fn(u8, &[u8], &mut [u8]),
    name: &'static str,
}
```

### 4.2 backend 分类

建议后端至少包含:

- scalar
- sse2
- ssse3
- avx2
- avx512
- gfni
- neon

### 4.3 运行时选择

初始化时探测:

- x86_64:
  - SSE2
  - SSSE3
  - AVX2
  - AVX512
  - GFNI
- aarch64:
  - NEON
  - 后续可扩展 SVE

### 4.4 与现有 C SIMD 的关系

第一步不需要立刻完全放弃 `simd_c/reedsolomon.c`。

建议策略:

1. 先保留现有 C 内核
2. 增加 runtime dispatch 包装层
3. 将 C 路径降级为 backend,而不是默认唯一实现
4. 新增 Rust intrinsic/backend 路径并并存验证
5. 再逐步迁移到 Rust 主导实现

### 4.5 Rust 后端引入策略

建议的引入顺序如下:

1. `scalar`:先整理纯 Rust 标量路径,作为对照基线
2. `ssse3` / `avx2`:优先迁移中等复杂度且收益稳定的 x86 路径
3. `neon`:补齐 ARM64 现代路径
4. `gfni`:在 runtime dispatch 和 benchmark 稳定后再引入
5. `avx512`:最后迁移,避免过早陷入高复杂度实现

原因:

- 先迁易验证、收益稳定的路径
- 先把 backend 架构和测试矩阵打稳
- 最复杂的路径留到后面,降低整体失败概率

## 5. 任务拆解

### 任务 1:抽象 SIMD 后端接口

要求:

- `galois_8` 不再直接耦合单一路径
- 上层只关心统一接口
- C 与 Rust 后端都可挂接到同一接口

### 任务 2:实现 runtime dispatch

要求:

- 只探测一次
- 后续使用缓存的 backend
- 提供调试输出或名称查询接口
- 能区分当前 backend 来自 C 还是 Rust

建议新增调试接口:

```rust
pub fn active_backend_name() -> &'static str
pub fn active_backend_kind() -> BackendKind
```

### 任务 3:补 GFNI 路径

这是本阶段最重要的性能专项之一。

目标:

- 在支持 GFNI 的 CPU 上显著优于 AVX2

### 任务 4:补 ARM64 路径治理

要求:

- 明确 NEON 路径是否已足够稳定
- 评估 SVE 的未来接入点

### 任务 5:细化 build.rs

改造方向:

- 避免默认强绑定单一 `-march`
- 构建期只负责生成可用后端
- 最优选择放到运行时

### 任务 6:Rust SIMD 后端第一批迁移

建议首批迁移:

- `mul_slice`
- `mul_slice_xor`

先从以下路径开始:

- `ssse3`
- `avx2`
- `neon`

这一步的目标不是一次性替掉全部 C,而是建立 Rust SIMD 后端开发模式。

### 任务 7:C/Rust 双实现对照测试

要求:

- 同一输入同时跑 scalar、C backend、Rust backend
- 结果做字节级一致性比较
- 固定 golden vector 与随机数据都覆盖

### 任务 8:退役策略设计

在文档中明确:

- 何时允许默认走 Rust backend
- 何时允许把 C backend 改为 fallback
- 何时允许完全移除 C backend

建议门槛:

1. Rust backend 在主流机器上不低于 C backend
2. 所有 ISA 路径有稳定一致性结果
3. benchmark 至少连续多轮没有明显退化

## 6. 验收标准

1. scalar fallback 正确
2. 每个 ISA 后端通过一致性测试
3. 运行时 dispatch 正确选路
4. GFNI 机器上显著优于 AVX2
5. C backend 与 Rust backend 至少在一组公共 ISA 上完成对照
6. Rust 新后端具备明确性能数据,不低于可接受门槛

## 7. 风险点

- 多 ISA 路径显著增加测试矩阵
- runtime dispatch 若处理不当,可能有 silent corruption 风险
- C/Rust 双实现并存期间维护成本会上升
- 过早迁移高复杂度 ISA 可能导致性能回退

## 8. 风险应对

- 引入 golden vector 和 cross-backend 对照测试
- 每个后端都用同一输入做 hash 对比
- 严格控制迁移顺序,先易后难
- 用 benchmark 数据决定是否推进某条 Rust 后端替换

## 9. 完成后的收益

- crate 可更适合通用二进制分发
- 后续 SIMD 优化能独立演进,不必反复触碰 core 逻辑
- 后续维护会逐步从“依赖外部 C 热路径”转向“Rust 内部统一治理”

## 10. 明确结论

本阶段的最终结论如下:

1. 继续使用现有 C SIMD,但只作为过渡期核心后端之一
2. 后续 SIMD 的长期目标是 Rust 主导实现
3. 迁移方式是“先并存、后对照、再替换”
4. 没有 benchmark 与一致性验证支撑时,不允许贸然移除 C backend

## 11. 当前落地状态(2026-05-25)

已完成:

- [x] 已有编译期开关 `simd-accel`,并保留纯 Rust 标量 fallback
- [x] SIMD-C 路径仍可用,当前与纯 Rust 路径共存(按编译条件)
- [x] 已引入统一 backend 抽象与 `name/kind` 观测接口
- [x] 已实现 runtime dispatch,一次探测并缓存 backend 结果
- [x] `std` / `no_std` 边界已打通:`std` 下可 runtime 探测,`no_std` 下保守回退 scalar
- [x] 已有首条 Rust SIMD backend pilot:
  - aarch64: `rust-neon` 已接通并完成与 scalar / simd-c 的一致性对照
  - x86_64: `rust-avx2` 已接通,并完成 `x86_64-apple-darwin` 目标下的对照测试编译与运行级一致性验证
- [x] benchmark smoke 已记录 runtime 实际 backend,并支持 `RSE_BACKEND_OVERRIDE`
- [x] 已新增 release 内核 benchmark:`galois_backend`

未完成 / 差距(本阶段核心):

- [x] `rust-neon` 当前 correctness 已通过,并在 release 内核 benchmark / throughput bench 上显示出优于当前 `simd-c` 的趋势
- [ ] 仍需继续观察更广泛 case 与更稳定基准,以确认是否长期保持默认优先级
- [x] `rust-avx2` 已在 native `x86_64` 主机(`AMD EPYC 9V45`)完成 runtime benchmark 与 release smoke 复核,当前结论支持继续作为默认首选
- [x] GFNI 路径代码与 backend override 已引入,当前同时具备 `rust-gfni-avx2``rust-gfni-avx512`,并保持 override-only;剩余缺口是 native GFNI 主机上的性能验证与默认优先级结论
- [x] ARM64 路径的治理收口与 SVE 预留结构已完成,包括目录边界、feature-detect 插槽、override/metadata 验证与 profiling 契约
- [ ] ARM64 更深度的性能治理与可用 SVE backend 仍属后续实现任务

补充核查(2026-05-26,远程 `AMD EPYC 9V45` `x86_64` 主机):

- [x] `benchmarks/x86_64-simd/2026-05-26-amd-epyc-9v45-96-core-processor.json` 与配套 `*.run-meta.json` 已齐备,远程实机结果和运行元数据都已归档。
- [x] machine json 中 `adoption_decision_stub.override_mismatch_count = 0`,说明同机 release smoke override 没有漂移。
- [x] machine json 中 `adoption_decision_stub.status = manual-review-required`,符合“推荐顺序与默认策略有差异时必须人工评审”的治理规则。
- [x] benchmark 推荐顺序高于当前 runtime policy 的候选 backend 仅作为文档结论记录;当前 `src/galois_8/backend.rs``x86_64` 自动选路顺序仍保持 `rust-avx2 -> rust-avx512 -> rust-ssse3 -> simd-c -> scalar-rust`- [x] 因 benchmark 推荐与当前 runtime policy 存在差异,本轮不自动切换默认优先级,只完成远程实机验证收口与文档回填。

补充核查(2026-05-26,aarch64-apple-darwin 本机):

- [x] `rust-neon` / `scalar` override 在 `RSE_STRICT_BACKEND_OVERRIDE=1` 下均可严格命中;
- [x] `test_active_backend_metadata``test_backend_override_affects_active_backend``std + simd-accel` 下通过;
- [x] `benchmark_smoke` 结果文件包含 backend 元数据字段(`backend/backend_id/backend_kind/backend_override/override_honored`)并与 override 行为一致。
- [x] 当前机器为 `arm64` Darwin,Rust host triple 为 `aarch64-apple-darwin`,现有核查结论与本机真实架构一致。

## 12. 执行待办(按优先级)

### P0(阶段核心闭环)

- [x] 定义 backend 抽象:
  - 建议文件:`src/galois_8/backend.rs`
  - 结构:`mul_slice` / `mul_slice_xor` / `name` / `kind`
- [x] 实现 runtime dispatch:
  - 首次探测 CPU feature 并缓存选择结果
  - x86_64:已支持 `scalar` / `simd-c` / `rust-avx2`
  - aarch64:已支持 `scalar` / `simd-c` / `rust-neon`
- [x] 暴露调试能力:
  - `active_backend_name()`
  - `active_backend_kind()`
- [x] 保证 `no_std``std` 的编译边界清晰(`no_std` 仍可用)

### P1(能力扩展)

- [x] 引入首批 Rust SIMD backend(当前已接入 `neon` pilot,`avx2` pilot 也已接线)
- [x] 建立 C backend 与 Rust backend 对照测试框架(固定长度 + 随机输入基础对照已具备)
- [x] 调整 `build.rs`,已显式暴露 SIMD-C 构建级别,runtime 可据此安全判定
- [x] 在 native AVX2 主机上补齐 `rust-avx2` benchmark,并与 `simd-c` 做同机对照
- [x] 已对 `rust-neon` 做进一步 profiling,当前结论更接近“debug smoke 低估性能,release bench 更能反映真实上限”
- [x] 已为 `encode/verify/reconstruct` 增加矩阵/调度层 profiling 观测(`RuntimeProfileStats` + `throughput_matrix` profile 导出)

### P2(高级优化)

- [x] GFNI 路径已完成实验性引入与本机正确性验证;后续仍需补 native GFNI 主机上的性能验证
- [x] ARM64 路径已补 SVE 预留扩展位(目录与 feature-detect 插槽)
- [x] ARM64 路径治理收口与 SVE 预留结构
- [ ] ARM64 深度性能治理与 SVE backend 实装/验证
- [x] Rust backend 成为默认路径前的退役门槛文档化(见 `docs/benchmark-methodology.md`
## 13. 建议 PR 拆分

1. `phase4-backend-abstraction`: backend trait/struct + 调试接口 + 基础测试
2. `phase4-runtime-dispatch`: runtime feature 探测 + 分发缓存 + 回退路径
3. `phase4-rust-backend-pilot`: 首条 Rust SIMD backend + C/Rust 对照测试
4. `phase4-gfni-arm64`: GFNI/ARM64 强化与基准验证

## 14.1 Rust Backend 默认切换门槛

Rust backend 允许成为默认优先路径前,至少需要满足以下条件:

1. correctness 全通过:
   - scalar 对照
   - backend consistency sweep
   - smoke regression 不出现 correctness 漂移
2. benchmark 证据充分:
   - 同机、同 feature、同 backend override 口径下完成对照
   - `galois_backend` 与 workload 级 `smoke` / `throughput_matrix` 至少一条主线成立
3. 结果稳定:
   - 不能基于单次 noisy run 决策
   - 需要重复运行并以中位数结论为主
4. 回退路径清晰:
   - `scalar` fallback 保留
   - `simd-c` 或前序稳定 backend 仍可通过 override 强制回退
5. 治理同步:
   - baseline 更新理由已记录
   - 默认优先级变更已同步到相关文档与脚本

在未满足上述条件前,Rust backend 可以继续作为:

- correctness-validated backend
- benchmark candidate backend
- explicit override backend

但不应仅凭局部结果直接替换为默认优先路径。

## 14. 验收命令

```bash
cargo check --tests
cargo test --features std
cargo test --features "std simd-accel"
cargo bench --bench galois_backend --features "std simd-accel"
cargo bench --bench throughput_matrix
```

说明:

- `tests/benchmark_smoke.rs` 适合做功能 smoke 与结果导出,不适合单独作为默认 backend 性能裁决依据
- 默认 backend 的性能判断,优先参考 release 模式下的 `galois_backend``throughput_matrix`

## 15. 矩阵与调度层 Profiling(2026-05-25)

本轮新增:

- `ReedSolomon::runtime_profile_stats()` / `reset_runtime_profile_stats()``std`- `ReedSolomon::effective_parallel_policy()``std`),支持环境变量覆盖调度参数
- `RuntimeProfileStats` 包含:
  - `code_some_*`(串并行调用次数、字节量、chunk 数)
  - `parallel_policy_*`(policy 调用次数、串并行决策、jobs、chunk_len 聚合)
  - `reconstruct_*`(重建调用、data_only 调用、缺失 data/parity 聚合)
- `benches/throughput_matrix.rs` 支持 profile 导出:
  - `RSE_WRITE_PROFILE_REPORT=1`
  - `RSE_PROFILE_REPORT_PATH=/tmp/throughput-profile-*.json`

并行调度环境变量(`std`):

- `RS_PARALLEL_POLICY_MIN_PARALLEL_SHARD_BYTES`
- `RS_PARALLEL_POLICY_MIN_BYTES_PER_JOB`
- `RS_PARALLEL_POLICY_MAX_JOBS`
- `RS_RECONSTRUCT_DATA_MIN_PARALLEL_SHARD_BYTES`
- `RS_RECONSTRUCT_FULL_MIN_PARALLEL_SHARD_BYTES`

关键注意:

- 若 benchmark 走 `encode/verify/reconstruct`(非 `_opt`),不会经过 runtime 调度策略入口,`parallel_policy_*` 统计会接近 0。
- 若要分析“矩阵与调度层”行为,必须走 `encode_opt/verify_opt/reconstruct_opt/reconstruct_data_opt` 路径。

本轮(同机、release、同参数)观察结论:

- `*_opt` 路径下,`10x4_1m` / `32x16_1m` 等中大 case 的 `parallel_policy_parallel``code_some_parallel_calls` 明显大于 0,说明调度策略生效。
- 同口径下 `rust-neon``simd-c` 都走到并行路径,但 `rust-neon` 的端到端吞吐仍显著更高,当前没有观察到“调度层导致 rust-neon 吃掉优势”的证据。
- reconstruct 场景中,decode matrix cache 命中持续接近满命中(除首次 miss),当前不是主要瓶颈。

## 16. 调度参数 Sweep 结论(2026-05-25)

在同机、同 benchmark(`throughput_matrix`,`*_opt` 路径,`rust-neon`)下做了以下参数扫描:

1. 默认(无覆盖)
2. `RS_PARALLEL_POLICY_MIN_BYTES_PER_JOB=131072`
3. `RS_PARALLEL_POLICY_MAX_JOBS=4`
4. `RS_PARALLEL_POLICY_MAX_JOBS=8` + `RS_PARALLEL_POLICY_MIN_BYTES_PER_JOB=131072`
5. `RS_PARALLEL_POLICY_MIN_PARALLEL_SHARD_BYTES=65536` + `RS_PARALLEL_POLICY_MAX_JOBS=8`

结论:

- 默认策略仍然是最稳妥配置;在 `10x4_1m` / `32x16_1m` 上表现稳定且整体最佳。
-`min_bytes_per_job``256KiB` 降到 `128KiB`,会增加并行切片数量,但未带来稳定收益,部分 reconstruct 场景出现回退。
- 限制 `max_jobs=4` 整体无明显收益,`verify/reconstruct` 场景有回退风险。
- `max_jobs=8 + min_bytes_per_job=128KiB``verify_10x4_1m` 有局部提升,但整体收益不稳定。
-`min_parallel_shard_bytes` 降到 `64KiB` 会让 `4x2_64k` 被强制并行,吞吐出现显著退化,应避免。

辅助产物:

- `/tmp/policy-default-rust-neon.json`
- `/tmp/policy-minbytes128k-rust-neon.json`
- `/tmp/policy-maxjobs4-rust-neon.json`
- `/tmp/policy-maxjobs8-minbytes128k-rust-neon.json`
- `/tmp/policy-minparallel64k-maxjobs8-rust-neon.json`

### 16.1 `simd-c` 同口径串行 Sweep(2026-05-25)

为避免并发 benchmark 互相干扰,本轮 `simd-c` 参数扫描按同机串行执行(每轮独立命令,固定 Criterion 参数):

- `--sample-size 12 --warm-up-time 1 --measurement-time 2`
- backend 固定:`RSE_BACKEND_OVERRIDE=simd-c`

结果摘要(12 个 workload):

- `default` 在 12 个 workload 中有 11 个为最佳吞吐。
- 唯一非 default 最优点:`reconstruct_10x4_1m``minparallel64k-maxjobs8` 下较 default 提升约 `+0.28%`,幅度接近噪声区间。
- `minbytes128k` 相对 default 平均 `-2.86%`,最差约 `-5.61%`- `maxjobs4` 相对 default 平均 `-3.22%`,最差约 `-6.16%`- `maxjobs8-minbytes128k` 相对 default 平均 `-4.11%`,最差约 `-8.54%`- `minparallel64k-maxjobs8` 相对 default 平均 `-14.67%`;在 `encode_4x2_64k`/`verify_4x2_64k` 上出现约 `-63%` 级别退化(小分片被过早强制并行)。

结论:

- `simd-c` 与前述 `rust-neon` sweep 趋势一致:default 仍是当前最稳妥的默认调度策略。
- 当前不建议把 `RS_PARALLEL_POLICY_MIN_PARALLEL_SHARD_BYTES` 下调到 `64KiB`
辅助产物:

- profile JSON:
  - `/tmp/policy-default-simd-c-serial.json`
  - `/tmp/policy-minbytes128k-simd-c-serial.json`
  - `/tmp/policy-maxjobs4-simd-c-serial.json`
  - `/tmp/policy-maxjobs8-minbytes128k-simd-c-serial.json`
  - `/tmp/policy-minparallel64k-maxjobs8-simd-c-serial.json`
- bench 日志:
  - `/tmp/policy-default-simd-c-serial.log`
  - `/tmp/policy-minbytes128k-simd-c-serial.log`
  - `/tmp/policy-maxjobs4-simd-c-serial.log`
  - `/tmp/policy-maxjobs8-minbytes128k-simd-c-serial.log`
  - `/tmp/policy-minparallel64k-maxjobs8-simd-c-serial.log`

## 17. `rust-neon` 内核级 Profiling(2026-05-25)

本轮新增可观测性(`std`):

- `galois_8::rust_neon_profile_stats()`
- `galois_8::reset_rust_neon_profile_stats()`
- `RustNeonProfileStats` 字段:
  - `mul_calls` / `mul_xor_calls`
  - `total_bytes`
  - `vector_64b_chunks` / `vector_16b_chunks`
  - `tail_bytes` / `tail_calls`
  - `table_lookups`(按 `vqtbl1q_u8` 次数聚合)

并已把这些字段接入 `benches/throughput_matrix.rs` 的 profile JSON 导出。

### 17.1 首轮观测结论

基于:

- `RSE_BACKEND_OVERRIDE=rust-neon`
- `cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1`
- profile: `/tmp/throughput-neon-kernel-profile.json`

观察:

- 各 operation/case 的 `neon_tail_bytes` 全为 `0``neon_vector_16b_chunks` 全为 `0`- `neon_table_lookups / neon_total_bytes` 稳定为 `0.125`(即每字节固定 `1/8` 次 lookup 聚合,符合当前 64B 向量路径的理论比例)。
- 说明当前 benchmark 主要命中 64B 向量主路径,几乎没有尾部 fallback;`rust-neon``simd-c` 的性能差异更可能来自主内核实现质量,而非尾部路径或调度参数。

### 17.2 直接结论(针对“`vqtbl1q_u8` 是否天然很难追上 C”)

- 当前证据不支持“慢在 tail/fallback”这一假设。
- 当前数据更接近:热点确实集中在 `vqtbl1q_u8` 主路径,若要继续逼近/超越 C,需要优化主路径的指令组织与数据装载,而不是继续调 `parallel policy`
辅助产物:

- `/tmp/throughput-neon-kernel-profile.json`
- `/tmp/policy-default-rust-neon-lite.log`

## 18. `rust-neon mul_slice_xor` 微内核 A/B(2026-05-25)

本轮新增:

- `RS_NEON_MUL_SLICE_XOR_UNROLL=2|4``std` 下读取一次并缓存,默认 `4`-`rust_neon_mul_slice_xor_impl` 中支持:
  - `unroll4`:原 64B 循环
  - `unroll2`:32B 循环(降低寄存器压力的对照组)

同机同参数(`galois_backend`, `sample-size 10`, `warm-up 1s`, `measurement 1s`, backend=`rust-neon`)结果:

- `len_65536`: `unroll2` vs `unroll4` = `50.815` vs `51.362` GiB/s(`-1.06%`- `len_1048576`: `48.796` vs `49.000` GiB/s(`-0.42%`- `len_4194304`: `47.733` vs `48.249` GiB/s(`-1.07%`
结论:

- 当前实现下,`unroll2` 没有带来收益,三档长度均回退。
- 默认仍应保持 `unroll4`- 开关会保留,便于后续继续尝试更激进路径(例如不同 load/store 组合或指令重排)时快速 A/B。

辅助产物:

- `/tmp/neon-xor-unroll4.log`
- `/tmp/neon-xor-unroll2.log`
- `/tmp/neon-xor-ab.csv`

## 19. 参考 `reed-solomon-simd` 的实现对照(2026-05-25)

本轮对照了本机 registry 中 `reed-solomon-simd-3.1.0`(`engine/engine_neon.rs`)的 Neon 规则,重点观察:

- 以 64B 固定块为核心处理单元;
- 内部以 2x128-bit 子块组合计算(`mul_128` / `muladd_128`);
- 主路径同样基于 `vqtbl1q_u8`,并尽量避免尾部路径进入热循环。

### 19.1 可迁移尝试

基于上面的规则,我们在 `rust_neon_mul_slice_xor_impl` 上做了对照实验:

- 将 64B 循环重排为“pair-wise 32B + 32B”顺序处理(降低单次寄存器活跃度);
- 保持 `RS_NEON_MUL_SLICE_XOR_UNROLL=2|4` A/B 开关不变,便于同机复测。

### 19.2 结果与处理

同机同参数(`galois_backend`, `sample-size 10`, `warm-up 1s`, `measurement 1s`)下,相比原 `unroll4`:

- `len_65536`: `-1.99%`
- `len_1048576`: `-0.77%`
- `len_4194304`: `-0.54%`

结论:

- 这版“pair-wise 64B 重排”在当前代码结构和编译产物下为负收益;
- 已回退到原 `unroll4` 主路径,避免默认性能回退;
- 保留 `RS_NEON_MUL_SLICE_XOR_UNROLL` 作为后续 A/B 框架,后续更值得尝试的方向是:
  - 针对 `mul_slice_xor` 的 load/store 次序与 `vqtbl` 指令重排做更细粒度实验;
  -`throughput_matrix``neon_*` profile 字段下联动验证端到端收益,而不仅看单点 microbench。

辅助产物:

- `/tmp/neon-xor-unroll4-pair.log`
- `/tmp/neon-xor-unroll2-pair.log`
- `/tmp/neon-xor-compare.csv`

## 20. 64B 对齐快速路径尝试(2026-05-25)

为了继续参考 `reed-solomon-simd` 的“固定块优先”思路,本轮额外尝试了:

- `rust_neon_mul_slice` / `rust_neon_mul_slice_xor` 中为 `len % 64 == 0` 引入专用 fast-path(直接 64B 循环,跳过 16B/tail 分支)。

同机同参数(`galois_backend`, `sample-size 10`, `warm-up 1s`, `measurement 1s`)对照结果(`mul_slice_xor`):

- `unroll4`:
  - `len_65536`: `-0.77%`
  - `len_1048576`: `-0.34%`
  - `len_4194304`: `-1.32%`
- `unroll2`:
  - `len_65536`: `-0.95%`
  - `len_1048576`: `-1.28%`
  - `len_4194304`: `-1.99%`

结论:

- 本轮 fast-path 在当前实现上没有带来收益,反而稳定小幅回退;
- 已回滚该 fast-path,避免默认路径性能回退;
- 后续优先继续在现有 `unroll4` 主循环内部做更细粒度指令级重排实验。

辅助产物:

- `/tmp/neon-xor-unroll4-fast64.log`
- `/tmp/neon-xor-unroll2-fast64.log`
- `/tmp/neon-fast64-compare.csv`

## 21. `mul_slice_xor` 指令调度 A/B(2026-05-25)

本轮新增了一个仅用于微调度对比的开关:

- `RS_NEON_MUL_SLICE_XOR_SCHEDULE=split`
- 默认(未设置)维持原有 `veorq(vqtbl(low), vqtbl(high))` 路径
- `split` 路径改为先批量做 `low_tbl`/`high_tbl` lookup,再做 xor 聚合

同机同参数(`galois_backend`, `sample-size 10`, `warm-up 1s`, `measurement 1s`, backend=`rust-neon`)对比:

- `len_65536`: `split` vs `base` = `50.331` vs `50.687` GiB/s(`-0.70%`- `len_1048576`: `48.709` vs `48.669` GiB/s(`+0.08%`- `len_4194304`: `47.974` vs `47.845` GiB/s(`+0.27%`
结论:

- `split` 在大块上有轻微正向,但在 64K 回退,整体属于噪声级混合收益;
- 默认策略保持不变(不启用 `split`),以稳定性优先;
- 开关保留,便于后续与更细粒度重排策略组合验证。

辅助产物:

- `/tmp/neon-xor-sched-base2.log`
- `/tmp/neon-xor-sched-split.log`
- `/tmp/neon-sched-compare.csv`

## 22. `UNROLL × SCHEDULE` 小型 Sweep(2026-05-25)

本轮做了 4 组组合:

1. `base`
2. `schedule_split`
3. `unroll2`
4. `unroll2 + schedule_split`

同机同参数(`galois_backend`, `sample-size 10`, `warm-up 1s`, `measurement 1s`, backend=`rust-neon`)三档长度结果汇总(`mul_slice_xor`):

- `base`: `50.505 / 48.015 / 47.429` GiB/s
- `schedule_split`: `49.991 / 48.020 / 46.513` GiB/s
- `unroll2`: `49.389 / 47.911 / 46.894` GiB/s
- `unroll2 + schedule_split`: `49.956 / 47.964 / 47.078` GiB/s

结论:

- 4 组里没有任何一组超过默认 `base`- `schedule_split``unroll2` 都是轻微回退;
- `unroll2 + schedule_split` 虽然比单独 `unroll2` 好一点,但仍未超过默认;
- 当前应保留默认 `base`,继续向更细粒度的指令级/寄存器级优化推进。

辅助产物:

- `/tmp/neon-sweep-base.log`
- `/tmp/neon-sweep-schedule_split.log`
- `/tmp/neon-sweep-unroll2.log`
- `/tmp/neon-sweep-unroll2_schedule_split.log`
- `/tmp/neon-sweep-matrix.csv`

## 23. 汇编审视与 `veor3` 试验(2026-05-25)

本轮先用 `cargo rustc --release --lib --features "std simd-accel" -- --emit=asm` 生成 release 汇编,定位到:

- `rust_neon_mul_slice_xor` 的热循环主体;
- 结果显示当前循环已经由 LLVM 合成出紧凑的 `tbl` / `eor3` 形态,没有明显的灾难性 spill;
- 因此直接把 `veor3` 作为源码级优化目标并不稳妥,优先级低于更细的 load/store 级调整。

尝试结果:

- `veor3` / `split` 组合没有形成稳定优势;
- 经过修正与回退后,默认路径性能保持正常;
- 这条路线不建议继续深挖,后续应转向更局部的寄存器活跃区间压缩与 load/store 互相错峰。

辅助产物:

- `target/release/deps/rustfs_erasure_codec-ec9534e3728d9d83.s`

## 24. 压测卫生规则(2026-05-25)

本阶段后续所有 benchmark / 压测验证,建议统一先清空 `target/` 后再跑,以避免增量编译产物影响结论:

```bash
cargo clean
```

说明:

- 这样可以避免旧编译缓存、残留目标文件、或切换实验分支后的产物污染;
- 尤其适合我们现在这种“多开关 A/B、每次只看 1% 级差异”的场景;
- 后续跑基线或对照时,优先采用 clean-build 再压测。

## 25. clean-build 并排对比:`rust-neon` vs `simd-c`(2026-05-25)

为避免增量编译污染,本轮所有对照均采用:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=<backend> cargo bench --bench <bench> --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

并将完整日志保存到:

- `/tmp/galois-clean-rust-neon.log`
- `/tmp/galois-clean-simd-c.log`
- `/tmp/throughput-clean-rust-neon.log`
- `/tmp/throughput-clean-simd-c.log`

### 25.1 `galois_backend`(中位吞吐,GiB/s)

`mul_slice`:

- `len_65536`: `rust-neon=51.264` vs `simd-c=25.390``2.02x`- `len_1048576`: `rust-neon=51.010` vs `simd-c=25.117``2.03x`- `len_4194304`: `rust-neon=51.054` vs `simd-c=25.230``2.02x`
`mul_slice_xor`:

- `len_65536`: `rust-neon=50.662` vs `simd-c=21.132``2.40x`- `len_1048576`: `rust-neon=48.636` vs `simd-c=21.081``2.31x`- `len_4194304`: `rust-neon=47.718` vs `simd-c=20.633``2.31x`
### 25.2 `throughput_matrix`(中位吞吐,GiB/s)

`4x2_64k`:

- `encode`: `23.145` vs `10.608``2.18x`- `verify`: `17.014` vs `9.118``1.87x`- `reconstruct`: `14.109` vs `8.140``1.73x`- `reconstruct_data`: `14.215` vs `8.143``1.75x`
`10x4_1m`:

- `encode`: `32.942` vs `16.472``2.00x`- `verify`: `24.048` vs `14.343``1.68x`- `reconstruct`: `15.197` vs `8.459``1.80x`- `reconstruct_data`: `20.774` vs `13.613``1.53x`
`32x16_1m`:

- `encode`: `23.986` vs `12.187``1.97x`- `verify`: `16.062` vs `9.889``1.62x`- `reconstruct`: `14.278` vs `8.197``1.74x`- `reconstruct_data`: `20.827` vs `12.934``1.61x`
### 25.3 结论(当前机器)

- `rust-neon``galois_backend``throughput_matrix` 两条线上均显著领先 `simd-c`- 这台 `aarch64` 机器上,`simd-c` 不适合继续作为默认性能路径候选。
- 下一步应继续深挖 `rust-neon` 主内核(`vqtbl1q_u8` 路径)与矩阵调度层,而不是继续投入 `simd-c` 的默认路径争夺。

## 26. `reconstruct` 分段 profile(2026-05-25)

本轮新增了 `RuntimeProfileStats` / `throughput_matrix` profile 导出字段:

- `reconstruct_data_stage_calls`
- `reconstruct_data_stage_bytes`
- `reconstruct_parity_stage_calls`
- `reconstruct_parity_stage_bytes`

用于把 `reconstruct` 热路径拆成两段看:

1. 补缺失 data shard
2. 在 data 完整后补缺失 parity shard

profile 基于:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
RSE_WRITE_PROFILE_REPORT=1 \
RSE_PROFILE_REPORT_PATH=/tmp/throughput-clean-rust-neon-profile-v2.json \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

### 26.1 观察结果

`reconstruct`(当前 smoke 缺失模式:丢 1 data + 1 parity):

- `4x2_64k`: data/parity = `50% / 50%`
- `10x4_1m`: data/parity = `50% / 50%`
- `32x16_1m`: data/parity = `50% / 50%`

`reconstruct_data`:

- `4x2_64k`: data/parity = `100% / 0%`
- `10x4_1m`: data/parity = `100% / 0%`
- `32x16_1m`: data/parity = `100% / 0%`

### 26.2 直接结论

- 当前 benchmark 形态下,`reconstruct` 不是单独慢在 parity-stage,而是 data-stage / parity-stage 平分矩阵乘工作量。
- `reconstruct_data` 的全部热点都落在 data-stage。
- 因此下一步最优先的矩阵/调度层优化目标,应是 data-stage 的矩阵乘与调度策略;它会同时改善 `reconstruct``reconstruct_data`
辅助产物:

- `/tmp/throughput-clean-rust-neon-profile-v2.json`
- `/tmp/throughput-clean-rust-neon-profile-v2.log`

## 27. data-stage 调度 sweep:`min_bytes_per_job`(2026-05-25)

为验证 data-stage 是否受更细切块策略影响,本轮在 clean-build 下对 `throughput_matrix` 做了两类 sweep:

1. 全局调 `RS_PARALLEL_POLICY_MIN_BYTES_PER_JOB`
2. 仅对 reconstruct 路径调 `RS_RECONSTRUCT_MIN_BYTES_PER_JOB`

### 27.1 全局 `min_bytes_per_job`

对比基线(默认 `256 KiB`):

- `RS_PARALLEL_POLICY_MIN_BYTES_PER_JOB=128 KiB`
  - `reconstruct_10x4_1m`: `15.225 -> 15.564` GiB/s
  - `reconstruct_data_10x4_1m`: `20.830 -> 21.294` GiB/s
  - `reconstruct_32x16_1m`: `14.575 -> 14.184` GiB/s
  - `encode_32x16_1m`: `24.252 -> 23.286` GiB/s

- `RS_PARALLEL_POLICY_MIN_BYTES_PER_JOB=64 KiB`
  - `reconstruct_10x4_1m`: `15.225 -> 15.927` GiB/s
  - `reconstruct_data_10x4_1m`: `20.830 -> 21.084` GiB/s
  - `reconstruct_32x16_1m`: `14.575 -> 14.831` GiB/s
  - `reconstruct_data_32x16_1m`: `20.995 -> 21.016` GiB/s
  -`encode_32x16_1m`: `24.252 -> 21.438` GiB/s
  -`verify_32x16_1m`: `16.296 -> 15.111` GiB/s

结论:

- 全局降到 `64 KiB` 确实能抬 `reconstruct` / `reconstruct_data`- 但会明显伤到大矩阵的 `encode` / `verify`- 因此不适合把更细 chunk 直接作为全局默认。

### 27.2 reconstruct 专用 `min_bytes_per_job`

新增环境覆盖:

```bash
RS_RECONSTRUCT_MIN_BYTES_PER_JOB=65536
```

目标是只影响 reconstruct 并行路径,不影响 encode / verify。

结果:

- `encode_32x16_1m`: `24.252 -> 23.193` GiB/s(仍回退)
- `verify_32x16_1m`: `16.296 -> 16.514` GiB/s(轻微波动)
- `reconstruct_10x4_1m`: `15.225 -> 15.381` GiB/s(小幅正向)
- `reconstruct_data_10x4_1m`: `20.830 -> 20.541` GiB/s(回退)
- `reconstruct_32x16_1m`: `14.575 -> 14.189` GiB/s(回退)
- `reconstruct_data_32x16_1m`: `20.995 -> 19.236` GiB/s(明显回退)

结论:

- `64 KiB` 只下放到 reconstruct 路径,并没有稳定保住收益;
- 尤其 `reconstruct_data_32x16_1m` 明显回退,说明 data-stage 并不是“越细 chunk 越好”;
- 下一步不应继续围绕单一 `min_bytes_per_job` 常量做默认值调整,而应转向:
  -`missing_data` / `missing_total` 分层决策;
  - 或直接优化 data-stage 的矩阵乘组织,而不是继续单参 sweep。

辅助产物:

- `/tmp/throughput-neon-minjob-128k.log`
- `/tmp/throughput-neon-minjob-64k.log`
- `/tmp/throughput-neon-reconstruct-minjob-64k.log`

## 28. `missing_data / missing_total` 分层决策实验(2026-05-25)

在完成单参数 `min_bytes_per_job` sweep 后,本轮继续尝试把 reconstruct 调度从“单一常量”升级到“按缺失模式分层决策”:

1. 先尝试较宽的启发式:
   - 让部分 small-data-set / mixed-missing 场景自动使用更细 chunk;
2. 再收紧为只命中:
   - `!data_only`
   - `missing_data == 1`
   - `missing_total <= 2`
   - 小数据分片规模

验证仍统一采用 clean-build:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

### 28.1 第一版分层启发式

产物:

- `/tmp/throughput-neon-tiered-reconstruct.log`

关键结果(对比默认基线):

- `reconstruct_10x4_1m`: `15.225 -> 15.386` GiB/s
- `reconstruct_data_10x4_1m`: `20.830 -> 20.800` GiB/s
- `reconstruct_32x16_1m`: `14.575 -> 14.817` GiB/s
- `reconstruct_data_32x16_1m`: `20.995 -> 20.413` GiB/s

结论:

- 相比“全局 64 KiB”,这版分层启发式更稳一些;
- 但它仍然会明显伤到 `reconstruct_data_32x16_1m`- 因此还不适合直接默认启用。

### 28.2 第二版收紧启发式

将策略进一步收紧为:只在 small-data-set 的 full reconstruct mixed-missing 场景下调细 chunk。

产物:

- `/tmp/throughput-neon-tiered-reconstruct-v2.log`

关键结果(对比默认基线):

- `reconstruct_10x4_1m`: `15.225 -> 14.671` GiB/s
- `reconstruct_data_10x4_1m`: `20.830 -> 20.767` GiB/s
- `reconstruct_32x16_1m`: `14.575 -> 15.207` GiB/s
- `reconstruct_data_32x16_1m`: `20.995 -> 21.888` GiB/s

结论:

- 第二版虽然改善了 `32x16``reconstruct / reconstruct_data`- 但却把 `10x4``reconstruct` 拉低了;
- 说明“仅靠一层简单 `missing_data / missing_total` 启发式”仍不足以形成稳定净收益。

### 28.3 最终处理

- 这两版分层启发式都已做过 clean-build 验证;
- 结论已记录,但没有保留为默认逻辑;
- 当前代码已回到稳定默认路径,只保留:
  - reconstruct 专用 env 覆盖能力;
  - reconstruct 分段 profiling 能力;
  - 后续继续做更细粒度实验所需的基线设施。

### 28.4 下一步建议

- 不再继续扩大默认启发式范围;
- 更值得推进的方向是:
  - 直接针对 `10x4``32x16` 分开建模;
  - 或下探 data-stage 的矩阵乘组织本身,而不是继续只调调度常量或粗粒度 heuristic。

### 28.5 回退到稳定默认后的 clean 校验

在撤回实验性默认启发式后,重新做了一次 stable clean-build 验证:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

产物:

- `/tmp/throughput-neon-post-revert-stable.log`

关键结果:

- `reconstruct_10x4_1m`: `15.245` GiB/s
- `reconstruct_data_10x4_1m`: `20.945` GiB/s
- `reconstruct_32x16_1m`: `14.771` GiB/s
- `reconstruct_data_32x16_1m`: `21.236` GiB/s

说明:

- `reconstruct / reconstruct_data` 已回到此前基线区间,没有因为撤回实验性启发式而出现新的结构性退化;
- `encode / verify` 会有一定 run-to-run 波动,后续若要做默认路径裁决,仍应以多轮 clean-build 对照为准,而不是单次结果。

## 29. 与 `x86_64` ISA 路径的冲突核实与拆分(2026-05-25)

### 29.1 核实结论

当前 `reconstruct` 调度逻辑位于 `src/galois_8.rs` 的高层 `ReedSolomon` 包装层,而不是 Neon / AVX2 内核本身。

这意味着:

- 它不会直接修改 `rust-neon` / `rust-avx2` / `simd-c` 的 SIMD 指令实现;
- 但如果把某个 reconstruct heuristic 直接默认化,它会同时影响 `aarch64``x86_64` 的上层调度行为;
- 因而从“默认策略”层面看,确实存在跨平台互相污染的风险。

### 29.2 本轮处理

为避免后续 `aarch64` 实验误伤 `x86_64`,已经先把 reconstruct policy 入口拆分为:

- `reconstruct_parallel_policy_default(...)`
- `reconstruct_parallel_policy_aarch64(...)`
- `reconstruct_parallel_policy(...)` 作为架构分发入口

当前默认行为保持不变:

- `aarch64` 先经过独立入口,但暂时仍回落到默认 policy;
-`aarch64` 平台继续走默认 policy;
- 因此这次拆分本身不引入行为变化,只建立后续按架构继续优化的隔离边界。

### 29.3 验证

已跑的验证包括:

- `cargo test --features "std simd-accel" test_reconstruct_parallel_policy_respects_min_bytes_per_job_env`
- `cargo test --features "std simd-accel" test_reconstruct_parallel_policy_has_data_only_and_full_tiers`

另外还新增了一个仅在非 `aarch64` 下编译的保护测试:

- `test_reconstruct_parallel_policy_default_arch_stays_on_default_chunk`

说明:

- 在当前这台 `aarch64` 机器上,该测试会被 `cfg(not(target_arch = "aarch64"))` 过滤掉;
- 这正是预期行为,它的作用是保证将来在 `x86_64` 上不会被 `aarch64` 专用 heuristic 静默污染。

### 29.4 结论

- “先按平台和 ISA 拆分,再继续做定向优化”是完全可行的;
- 而且这一步已经先做了入口拆分;
- 后续若继续做 `aarch64/neon` 专用 reconstruct heuristic,可以只改 `reconstruct_parallel_policy_aarch64(...)`,不必再碰 `x86_64` 默认路径。

### 29.5 `aarch64` 专属 reconstruct policy 入口验证

在完成入口拆分后,本轮进一步把 `reconstruct_parallel_policy_aarch64(...)` 变成真正独立可调入口:

- 新增 `RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB`
- 仅在 `target_arch = "aarch64"` 下生效
- 不影响 `x86_64` / 非 `aarch64` 的默认 reconstruct policy

已验证:

- `cargo test --features "std simd-accel" test_reconstruct_parallel_policy_respects_min_bytes_per_job_env`
- `cargo test --features "std simd-accel" test_aarch64_reconstruct_parallel_policy_has_arch_specific_override`

在当前 `aarch64` 机器上做的 clean-build bench:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB=131072 \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

产物:

- `/tmp/throughput-aarch64-reconstruct-128k.log`

关键结果(对比 stable 基线):

- `reconstruct_10x4_1m`: `15.245 -> 14.968` GiB/s
- `reconstruct_data_10x4_1m`: `20.945 -> 20.200` GiB/s
- `reconstruct_32x16_1m`: `14.771 -> 14.383` GiB/s
- `reconstruct_data_32x16_1m`: `21.236 -> 19.428` GiB/s

结论:

- 这次 `aarch64` 专属 `128 KiB` override 没有形成稳定收益;
- 因此不应默认启用;
- 但这条专属入口本身是有价值的,因为后续继续做 `aarch64` reconstruct 调参时,已经可以完全和 `x86_64` 主线隔离。

### 29.6 `aarch64` 专属调参入口补齐

在初版 `RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB` 之上,进一步补齐了 `aarch64` reconstruct policy 的三参数入口:

- `RS_AARCH64_RECONSTRUCT_MIN_PARALLEL_SHARD_BYTES`
- `RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB`
- `RS_AARCH64_RECONSTRUCT_MAX_JOBS`

作用:

- 只在 `target_arch = "aarch64"` 下生效;
- 不影响 `x86_64` / 非 `aarch64` 默认 reconstruct policy;
- 后续可以只靠 env 做 `aarch64` 定向试验,不必反复改代码。

已验证:

- `cargo check --tests --benches --features "std simd-accel"`
- `cargo test --features "std simd-accel" test_reconstruct_parallel_policy_respects_min_bytes_per_job_env`
- `cargo test --features "std simd-accel" test_aarch64_reconstruct_parallel_policy_has_arch_specific_override`

说明:

- 当前还没有把这三参数中的任何一组固化成默认值;
- 但从这一刻起,`aarch64` reconstruct 调参与 `x86_64` 已经在“入口能力”层彻底解耦。

### 29.7 `aarch64` 专属三参数首轮 sweep

在入口能力补齐后,先做了两组 clean-build `aarch64` 定向实验:

1. `RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB=65536`
2. `RS_AARCH64_RECONSTRUCT_MIN_PARALLEL_SHARD_BYTES=131072` +
   `RS_AARCH64_RECONSTRUCT_MIN_BYTES_PER_JOB=131072`

统一命令:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
... aarch64-only reconstruct env overrides ... \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

产物:

- `/tmp/throughput-aarch64-reconstruct-64k.log`
- `/tmp/throughput-aarch64-reconstruct-128k-threshold128k.log`

对比 stable 基线:

`aarch64-64k`

- `reconstruct_10x4_1m`: `15.245 -> 15.525`
- `reconstruct_data_10x4_1m`: `20.945 -> 20.751`
- `reconstruct_32x16_1m`: `14.771 -> 14.453`
- `reconstruct_data_32x16_1m`: `21.236 -> 20.699`

`aarch64-128k-threshold128k`

- `reconstruct_10x4_1m`: `15.245 -> 15.288`
- `reconstruct_data_10x4_1m`: `20.945 -> 20.951`
- `reconstruct_32x16_1m`: `14.771 -> 14.382`
- `reconstruct_data_32x16_1m`: `21.236 -> 21.288`

结论:

- `64 KiB` 细切块会小幅改善 `10x4``reconstruct`,但同时会伤到 `32x16`- `128 KiB + 128 KiB` 联动则整体更接近 stable 默认,没有形成明确超额收益;
- 说明在当前 smoke 矩阵下,还没有出现一个“明显优于 stable 默认”的 `aarch64` 单组参数。

下一步建议:

- 暂不把任何 `aarch64` 专属参数组固化成默认值;
- 更值得继续推进的是 data-stage 的实现组织优化,而不是继续扩大常量 sweep 规模。

### 29.8 data-stage / parity-stage 分离试验

为验证“是否可以只调 data-stage 而不牵连 parity-stage”,本轮进一步把 `aarch64` reconstruct policy 拆成了 stage-level 入口:

- `RS_AARCH64_RECONSTRUCT_DATA_MIN_BYTES_PER_JOB`
- `RS_AARCH64_RECONSTRUCT_PARITY_MIN_BYTES_PER_JOB`

并补了对应测试:

- `test_aarch64_reconstruct_stage_policies_allow_data_parity_split`

首轮 clean-build 试验参数:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
RS_AARCH64_RECONSTRUCT_DATA_MIN_BYTES_PER_JOB=65536 \
RS_AARCH64_RECONSTRUCT_PARITY_MIN_BYTES_PER_JOB=262144 \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

产物:

- `/tmp/throughput-aarch64-stage-split.log`

对比 stable 基线:

- `reconstruct_10x4_1m`: `15.245 -> 14.827`
- `reconstruct_data_10x4_1m`: `20.945 -> 20.346`
- `reconstruct_32x16_1m`: `14.771 -> 14.110`
- `reconstruct_data_32x16_1m`: `21.236 -> 19.978`

结论:

- 仅把 data-stage 调细、parity-stage 保持默认,在当前 smoke 矩阵下仍然是负收益;
- 因此可以阶段性收束这条路线:
  - 不是 parity-stage 拖累了调度效果;
  - 而是当前 data-stage 的实现组织本身,并不适合继续靠 chunk 常量来逼收益。

直接结论:

- 继续扩大 reconstruct policy 常量 sweep 的性价比已经很低;
- 下一步应直接转向 `aarch64/neon` data-stage 的实现组织优化,而不是继续在 policy 层打转。

### 29.9 direct write-back 实现试验

在 policy 层基本收束后,本轮尝试了一个实现层思路:

- `reconstruct_internal_option_vec_par_with_stage_policies(...)``Option<Vec<u8>>` 并行路径,从
  “先分配临时 `Vec<Vec<_>>`,计算完成后再写回 `shards`  改为
  “直接在 `shards` 内部 buffer 上写回”

目标:

- 减少 data-stage / parity-stage 的额外分配与拷贝;
- 看实现层减法是否比 policy 常量调参更有效。

验证:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

产物:

- `/tmp/throughput-direct-writeback.log`

对比 stable 基线:

- `reconstruct_10x4_1m`: `15.245 -> 14.824`
- `reconstruct_data_10x4_1m`: `20.945 -> 20.742`
- `reconstruct_32x16_1m`: `14.771 -> 14.703`
- `reconstruct_data_32x16_1m`: `21.236 -> 20.825`

结论:

- 这版 direct write-back 在当前实现下同样是负收益;
- 因此没有保留为默认实现,代码已回退到稳定版本;
- 说明当前瓶颈并不只是“临时 buffer 分配/拷贝”本身,而更可能还在 data-stage 的矩阵乘组织与访存形态。

## 31. 小输出 shard 的 chunk 内并行修正(2026-05-25)

在前面的 profile 与调度实验中,我们发现一个关键不一致:

- `ParallelPolicy` 会根据 `chunk_len``jobs` 计算出“应该有多少并行工作”;
- 但原先的 `code_some_slices_par_chunked(...)` 只按“输出 shard 数”做 `par_iter`- 对于 `reconstruct_data` / `reconstruct` 这类只缺 1-2 个 shard 的场景,哪怕 policy 计算出很多 `jobs`,实际运行时也吃不到,因为单个输出内部的 chunk 仍然是串行处理的。

### 31.1 修正方式

本轮对 `code_some_slices_par_chunked(...)` 做了一个很小但关键的实现修正:

- `outputs.len() <= 2``chunk_count > 1` 时,
- 不再只按输出 shard 维度并行,
- 而是进入单个输出内部,对 `par_chunks_mut(chunk_len)` 做并行处理。

也就是说:

- 大输出 shard 数场景,仍保持原有“按输出并行”;
- 小输出 shard 数场景(尤其 reconstruct),改为“输出内 chunk 也并行”。

### 31.2 clean-build 结果

两轮 clean-build 复测结果:

首轮:

- `/tmp/throughput-small-output-chunk-par.log`

复测:

- `/tmp/throughput-small-output-chunk-par-repeat.log`

相对 stable 基线:

`run1`

- `reconstruct_10x4_1m`: `15.245 -> 18.610`
- `reconstruct_data_10x4_1m`: `20.945 -> 21.105`
- `reconstruct_32x16_1m`: `14.771 -> 24.109`
- `reconstruct_data_32x16_1m`: `21.236 -> 24.393`

`run2`

- `reconstruct_10x4_1m`: `15.245 -> 18.538`
- `reconstruct_data_10x4_1m`: `20.945 -> 21.016`
- `reconstruct_32x16_1m`: `14.771 -> 24.106`
- `reconstruct_data_32x16_1m`: `21.236 -> 24.076`

### 31.3 直接结论

- 这不是 policy 常量的噪声级收益,而是实现层真正把 `jobs` 用起来后的明显正收益;
- 说明之前的瓶颈之一,确实是“小输出 shard 场景下并行度没有落到单输出内部”;
- 这条修正比前面的 `min_bytes_per_job` sweep、stage-split 调参、direct write-back 都更接近问题本质。

### 31.4 下一步

- 这批实现应优先保留并继续验证;
- 后续可以继续补更细的 runtime 观测字段,确认“小输出 shard + chunk 内并行”在真实 reconstruct 场景下的命中频率;
- 但从当前两轮 clean-build 结果看,这已经是一个值得保留并继续推进的方向。

### 31.5 命中统计补齐

为确认这条优化不是“偶然变快”,本轮补了三项 runtime 观测字段:

- `code_some_small_output_chunk_parallel_calls`
- `code_some_small_output_chunk_parallel_outputs`
- `code_some_small_output_chunk_parallel_chunks`

并在 profile 导出里接入。

基于:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
RSE_WRITE_PROFILE_REPORT=1 \
RSE_PROFILE_REPORT_PATH=/tmp/throughput-small-output-chunk-par-profile-v2.json \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

产物:

- `/tmp/throughput-small-output-chunk-par-profile-v2.json`
- `/tmp/throughput-small-output-chunk-par-profile-v2.log`

关键命中数据:

- `reconstruct_10x4_1m`
  - `small_output_calls=7724`
  - `small_output_outputs=7724`
  - `small_output_chunks=30896`
  - `parallel_policy_jobs=30896`

- `reconstruct_data_10x4_1m`
  - `small_output_calls=6295`
  - `small_output_outputs=12590`
  - `small_output_chunks=25180`
  - `parallel_policy_jobs=50360`

- `reconstruct_32x16_1m`
  - `small_output_calls=3586`
  - `small_output_outputs=3586`
  - `small_output_chunks=14344`
  - `parallel_policy_jobs=14344`

- `reconstruct_data_32x16_1m`
  - `small_output_calls=1848`
  - `small_output_outputs=3696`
  - `small_output_chunks=7392`
  - `parallel_policy_jobs=14784`

说明:

- `reconstruct` 而言,这条新路径的 `small_output_chunks``parallel_policy_jobs` 基本一一对应;
- 说明 policy 里之前“算出来但没被执行”的并行度,现在已经真正落到执行层;
-`reconstruct_data` 来说,由于缺 2 个 data shard,`parallel_policy_jobs` 仍高于 `small_output_chunks`,这说明后续仍有继续榨取并行度的空间,但当前版本已经带来了稳定收益。

### 31.6 本轮收益复核

在 profile 版 clean-build 里,再次观测到:

- `reconstruct_10x4_1m`: `15.245 -> 18.226`
- `reconstruct_data_10x4_1m`: `20.945 -> 21.072`
- `reconstruct_32x16_1m`: `14.771 -> 23.074`
- `reconstruct_data_32x16_1m`: `21.236 -> 24.586`

结论:

- “小输出 shard + chunk 内并行”不是一次性的偶然结果;
- 它已经具备:实现修正、clean-build 收益、profile 命中率 三条证据;
- 这批改动现在已经具备单独提交的价值。

### 31.7 触发条件与行为说明

当前实现里,“小输出 shard + chunk 内并行”只在以下条件同时满足时触发:

- `outputs.len() <= 2`
- `chunk_count > 1`

也就是:

- `encode` / `verify` 这种通常有更多输出 shard 的路径不生效;
-`reconstruct` / `reconstruct_data` 这类常见的“只缺 1-2 个 shard”场景生效;
- 并且只有在 `chunk_len` 切分后真的有多个 chunk 时才进入该路径。

补充测试:

- `test_parallel_policy_creates_multiple_chunks_for_small_output_reconstruct_case`

这条测试锁住了一个典型场景:

- `shard_len = 1 MiB`
- `output_shards = 2`
- `available_parallelism = 8`

会得到:

- `jobs = 4`
- `chunk_len = 256 KiB`

说明这类 reconstruct 场景确实具备“输出内 chunk 并行”的前提条件,而不只是理论上的 policy 结果。

## 30. `galois_8` 目录结构第一步重构(2026-05-25)

本轮先按“只做结构迁移、不改行为”的原则,把 `galois_8` 从单文件大模块开始拆成目录模块:

- `src/galois_8.rs` 已迁为 `src/galois_8/mod.rs`
- 新增:
  - `src/galois_8/aarch64/mod.rs`
  - `src/galois_8/aarch64/neon.rs`
  - `src/galois_8/x86/mod.rs`
  - `src/galois_8/x86/avx2.rs`
  - `src/galois_8/legacy/mod.rs`
  - `src/galois_8/legacy/simd_c.rs`

当前这一刀的目标不是一次性补齐 `ssse3 / avx512 / gfni`,而是先把现有实现按平台边界拆开,保证:

- 公共 API 继续集中在 `galois_8/mod.rs`
- backend 注册与 runtime 选路继续集中在 `galois_8/backend.rs`
- `aarch64/neon``x86/avx2``legacy/simd_c` 已从公共入口层分离

### 30.1 已验证

已通过:

- `cargo check --tests --benches --features "std simd-accel"`
- `cargo test --features "std simd-accel" test_active_backend_metadata`
- `cargo test --features "std simd-accel" test_backend_override_affects_active_backend`

说明:

- 这次目录重构在当前阶段没有破坏对外 `galois_8::*` API;
- runtime dispatch 与 backend override 在新结构下仍工作正常。

### 30.2 当前状态

- `aarch64` 逻辑已经可以在 `src/galois_8/aarch64/*` 下独立演进;
- `x86_64` 当前先拆出了 `avx2` 子模块;
- `simd_c` 已明确落到 `legacy/`- 后续仍可继续补齐:
  - `src/galois_8/x86/ssse3.rs`
  - `src/galois_8/x86/avx512.rs`
  - `src/galois_8/x86/gfni.rs`

### 30.3 直接结论

- 目录级拆分是可行的,并且已经完成第一步;
- 后续继续做 `aarch64` 定向调度/内核优化时,不再需要在同一个大文件里和 `x86_64` / `simd_c` 主线互相干扰。

### 30.4 `x86` 骨架补齐

在第一步目录迁移之后,继续补齐了 `x86` 子模块骨架:

- `src/galois_8/x86/ssse3.rs`
- `src/galois_8/x86/avx2.rs`
- `src/galois_8/x86/avx512.rs`
- `src/galois_8/x86/gfni.rs`

当前状态:

- `avx2.rs` 已承接现有 `rust-avx2` 实现;
- `ssse3.rs` / `avx512.rs` / `gfni.rs` 先作为 placeholder 模块存在;
- 这样后续继续补 ISA 时,不需要再回到 `mod.rs` 里做大规模剪切。

验证:

- `cargo check --tests --benches --features "std simd-accel"`
- `cargo test --features "std simd-accel" test_active_backend_metadata`

说明:

- 这一步仍然是“结构演进,不改行为”;
- 当前 runtime dispatch 仍保持原有 `scalar / simd-c / rust-neon / rust-avx2` 选路语义不变。

### 30.5 `mod.rs` 进一步瘦身

在补齐目录骨架后,继续完成了第二轮拆分:

- 新增:
  - `src/galois_8/profile.rs`
  - `src/galois_8/policy.rs`

拆分后职责更清晰:

- `src/galois_8/mod.rs`
  - 公共 API
  - GF(2^8) 标量表驱动逻辑
  - shared scalar baseline
  - 现有单元测试入口
- `src/galois_8/backend.rs`
  - backend 注册
  - runtime override
  - CPU feature 探测
  - runtime 选路
- `src/galois_8/profile.rs`
  - `RustNeonProfileStats`
  - Neon profiling metrics / env parsing
- `src/galois_8/policy.rs`
  - `encode_opt / verify_opt / reconstruct_opt / reconstruct_data_opt / reconstruct_some_opt`
  - reconstruct parallel policy helper

验证:

- `cargo check --tests --benches --features "std simd-accel"`
- `cargo test --features "std simd-accel" test_active_backend_metadata`
- `cargo test --features "std simd-accel" test_backend_override_affects_active_backend`

结果:

- `galois_8` 已不再是单文件混合承载 backend、ISA、profiling、policy、scalar 的“大杂烩”结构;
- 后续继续做 `aarch64/neon``x86/*` 定向优化时,改动边界会更清晰,也更不容易互相污染。

## 31. 小输出 shard 的 chunk 内并行修正(2026-05-25)

在前面的 profile 与调度实验中,我们发现一个关键不一致:

- `ParallelPolicy` 会根据 `chunk_len``jobs` 计算出“应该有多少并行工作”;
- 但原先的 `code_some_slices_par_chunked(...)` 只按“输出 shard 数”做 `par_iter`- 对于 `reconstruct_data` / `reconstruct` 这类只缺 1-2 个 shard 的场景,哪怕 policy 计算出很多 `jobs`,实际运行时也吃不到,因为单个输出内部的 chunk 仍然是串行处理的。

### 31.1 修正方式

对 `code_some_slices_par_chunked(...)` 做了一个很小但关键的实现修正:

- `outputs.len() <= 2``chunk_count > 1` 时,
- 不再只按输出 shard 维度并行,
- 而是进入单个输出内部,对 `par_chunks_mut(chunk_len)` 做并行处理。

也就是说:

- 大输出 shard 数场景,仍保持原有“按输出并行”;
- 小输出 shard 数场景(尤其 reconstruct),改为“输出内 chunk 也并行”。

### 31.2 clean-build 结果

两轮 clean-build 复测结果:

首轮:

- `/tmp/throughput-small-output-chunk-par.log`

复测:

- `/tmp/throughput-small-output-chunk-par-repeat.log`

相对 stable 基线:

`run1`

- `reconstruct_10x4_1m`: `15.245 -> 18.610`
- `reconstruct_data_10x4_1m`: `20.945 -> 21.105`
- `reconstruct_32x16_1m`: `14.771 -> 24.109`
- `reconstruct_data_32x16_1m`: `21.236 -> 24.393`

`run2`

- `reconstruct_10x4_1m`: `15.245 -> 18.538`
- `reconstruct_data_10x4_1m`: `20.945 -> 21.016`
- `reconstruct_32x16_1m`: `14.771 -> 24.106`
- `reconstruct_data_32x16_1m`: `21.236 -> 24.076`

### 31.3 直接结论

- 这不是 policy 常量的噪声级收益,而是实现层真正把 `jobs` 用起来后的明显正收益;
- 说明之前的瓶颈之一,确实是“小输出 shard 场景下并行度没有落到单输出内部”;
- 这条修正比前面的 `min_bytes_per_job` sweep、stage-split 调参、direct write-back 都更接近问题本质。

### 31.4 命中统计补齐

为确认这条优化不是“偶然变快”,补了三项 runtime 观测字段:

- `code_some_small_output_chunk_parallel_calls`
- `code_some_small_output_chunk_parallel_outputs`
- `code_some_small_output_chunk_parallel_chunks`

并在 profile 导出里接入。

基于:

```bash
cargo clean
RSE_BACKEND_OVERRIDE=rust-neon \
RSE_WRITE_PROFILE_REPORT=1 \
RSE_PROFILE_REPORT_PATH=/tmp/throughput-small-output-chunk-par-profile-v2.json \
cargo bench --bench throughput_matrix --features "std simd-accel" -- --sample-size 10 --warm-up-time 1 --measurement-time 1
```

产物:

- `/tmp/throughput-small-output-chunk-par-profile-v2.json`
- `/tmp/throughput-small-output-chunk-par-profile-v2.log`

关键命中数据:

- `reconstruct_10x4_1m`
  - `small_output_calls=7724`
  - `small_output_outputs=7724`
  - `small_output_chunks=30896`
  - `parallel_policy_jobs=30896`

- `reconstruct_data_10x4_1m`
  - `small_output_calls=6295`
  - `small_output_outputs=12590`
  - `small_output_chunks=25180`
  - `parallel_policy_jobs=50360`

- `reconstruct_32x16_1m`
  - `small_output_calls=3586`
  - `small_output_outputs=3586`
  - `small_output_chunks=14344`
  - `parallel_policy_jobs=14344`

- `reconstruct_data_32x16_1m`
  - `small_output_calls=1848`
  - `small_output_outputs=3696`
  - `small_output_chunks=7392`
  - `parallel_policy_jobs=14784`

说明:

- `reconstruct` 而言,这条新路径的 `small_output_chunks``parallel_policy_jobs` 基本一一对应;
- 说明 policy 里之前“算出来但没被执行”的并行度,现在已经真正落到执行层;
-`reconstruct_data` 来说,由于缺 2 个 data shard,`parallel_policy_jobs` 仍高于 `small_output_chunks`,这说明后续仍有继续榨取并行度的空间,但当前版本已经带来了稳定收益。

### 31.5 触发条件与行为说明

当前实现里,“小输出 shard + chunk 内并行”只在以下条件同时满足时触发:

- `outputs.len() <= 2`
- `chunk_count > 1`

也就是:

- `encode` / `verify` 这种通常有更多输出 shard 的路径不生效;
-`reconstruct` / `reconstruct_data` 这类常见的“只缺 1-2 个 shard”场景生效;
- 并且只有在 `chunk_len` 切分后真的有多个 chunk 时才进入该路径。

补充测试:

- `test_parallel_policy_creates_multiple_chunks_for_small_output_reconstruct_case`

这条测试锁住了一个典型场景:

- `shard_len = 1 MiB`
- `output_shards = 2`
- `available_parallelism = 8`

会得到:

- `jobs = 4`
- `chunk_len = 256 KiB`

说明这类 reconstruct 场景确实具备“输出内 chunk 并行”的前提条件,而不只是理论上的 policy 结果。

### 31.6 结论

- “小输出 shard + chunk 内并行”不是一次性的偶然结果;
- 它已经具备:实现修正、clean-build 收益、profile 命中率 三条证据;
- 这批改动现在已经具备单独提交的价值。

### 31.7 `1-output / 2-output` 专用 fast path

在上一版“小输出 shard + chunk 内并行”基础上,又进一步做了一个更贴近 data-stage 的实现层优化:

- `outputs.len() == 1` 时,走专用单输出 fast path;
-`outputs.len() == 2` 时,走专用双输出 fast path;
-`2-output` 场景下,同一段输入 chunk 由同一个任务顺手服务两个输出,减少重复的输入切片访问,并更好利用输入局部性。

相对上一版 `small-output-par`:

- `reconstruct_10x4_1m`: `18.538 -> 19.029`
- `reconstruct_data_10x4_1m`: `21.016 -> 25.144`
- `reconstruct_32x16_1m`: `24.106 -> 24.936`
- `reconstruct_data_32x16_1m`: `24.076 -> 26.911`

产物:

- `/tmp/throughput-small-output-fastpath.log`

结论:

- 这条 `1-output / 2-output` 专用 fast path 比泛化版“小输出并行”还要更强;
- 尤其 `reconstruct_data` 的提升最明显,说明 data-stage 在“少输出 shard”场景下确实非常受益于这种更专用的执行形态;
- 当前这批实现已经不仅是“可提交”,而是值得优先保留并继续在此基础上深挖的主方向。

### 31.8 `aarch64 + missing_data==2` 更窄专用路径试验

在 `1-output / 2-output` 专用 fast path 基础上,又尝试了一条更窄的实现:

- 仅在 `aarch64`
-`active_backend_id() == BackendId::RustNeon`
-`reconstruct_data_opt` 命中 `missing_data == 2 && missing == 2`

时,直接走一个更窄的双输出 data-stage 专用路径。

clean-build 结果相对上一版 `small-output-fastpath`:

- `reconstruct_10x4_1m`: `19.029 -> 18.396`
- `reconstruct_data_10x4_1m`: `25.144 -> 24.839`
- `reconstruct_32x16_1m`: `24.936 -> 23.849`
- `reconstruct_data_32x16_1m`: `26.911 -> 26.179`
-`reconstruct_data_4x2_64k`: `14.040 -> 2.6318`

结论:

- 这条更窄的 `aarch64 + missing_data==2` 专用路径没有超过上一版通用 `1/2-output` fast path;
- 并且在小 case(`4x2_64k`)上出现了明显回退;
- 因此没有保留为默认实现,代码已回退到上一版 `1/2-output` fast path。

### 31.9 neon 双输出共享输入 helper 试验

进一步尝试过把“双输出共享输入加载”的专用 helper 直接下沉到 `aarch64/neon` 内核层,
并只在 `reconstruct_data_opt` 的 `missing_data == 2 && missing == 2` 场景下命中。

clean-build 结果相对上一版 `small-output-fastpath`:

- `reconstruct_10x4_1m`: `19.029 -> 18.394`
- `reconstruct_data_10x4_1m`: `25.144 -> 24.839`
- `reconstruct_32x16_1m`: `24.936 -> 23.910`
- `reconstruct_data_32x16_1m`: `26.911 -> 26.179`

结论:

- 直接把“双输出共享输入加载”下沉到当前 neon helper 形态,并没有超过上一版 `1/2-output` fast path;
- 因此这条试验性实现没有保留,代码已回退到更强、更通用的上一版实现;
- 说明下一步若要继续深挖 `aarch64/neon`,应更聚焦在 `vqtbl1q_u8` 主路径的 load/store 与寄存器组织,而不是仅仅把现有逻辑下沉一层。

### 31.10 真正下沉到 neon 双输出 helper 的再次验证

随后又尝试了一版更“正统”的下沉方案:

- `src/galois_8/aarch64/neon.rs` 中新增
  - `rust_neon_mul_slice_two_outputs(...)`
  - `rust_neon_mul_slice_xor_two_outputs(...)`
- 并在 `reconstruct_data_opt``missing_data == 2 && missing == 2` 场景下直接调用它们

目标是:

- 不再只是高层路径专用分支;
- 而是让双输出共享输入加载真正发生在 neon helper 本身。

clean-build 结果相对上一版 `small-output-fastpath`:

- `reconstruct_10x4_1m`: `19.029 -> 17.711`
- `reconstruct_data_10x4_1m`: `25.144 -> 17.140`
- `reconstruct_32x16_1m`: `24.936 -> 24.017`
- `reconstruct_data_32x16_1m`: `26.911 -> 16.455`

结论:

- 这版“真正下沉到 neon helper”的实现比上一版通用 `1/2-output` fast path 明显更差;
- 因此同样没有保留,代码已回退;
- 进一步说明:当前最优结果并不是“越下沉越好”,而是“在 `core``1/2-output` fast path 形态下,让 policy 计算出的 chunk 并行度真正落地”。

### 31.11 `vqtbl1q_u8` 主循环表达式收紧试验

为进一步压缩寄存器活跃区间,本轮尝试把 `rust_neon_mul_slice_xor_impl` 的 `unroll4` 主循环从:

- 先生成 `product0..3`
- 再统一做 `veorq(outs, product)`

改成更紧的表达式组织:

- 先装载 `outs`
- 再直接在每个 lane 上构造 `veorq(outs.N, veorq(vqtbl(low), vqtbl(high)))`

目标是:

- 缩短 `product0..3` 的活跃区间;
- 看 LLVM 是否会生成更紧凑的寄存器分配与调度。

clean-build 结果相对当前最强的 `small-output-fastpath`:

- `reconstruct_10x4_1m`: `19.029 -> 18.695`
- `reconstruct_data_10x4_1m`: `25.144 -> 25.162`
- `reconstruct_32x16_1m`: `24.936 -> 24.893`
- `reconstruct_data_32x16_1m`: `26.911 -> 27.033`

结论:

- 这次主循环表达式收紧只表现为混合收益,没有形成足够稳定的正向结果;
- 因此没有保留,代码已回退到上一版更稳的实现;
- 说明当前 `vqtbl1q_u8` 主路径若继续深挖,更值得试的不是“单纯缩短表达式”,而是更成体系的 load/store 组织变化。