1 Commits
Author SHA1 Message Date
what ca2a8f911a perf: map 分段走 reflect, 离开后切回快路径
walk 逐跳调用 step, 而 step 的 map 分支每次都要 boxCopy —— map 元素不可寻址,
rfx 的表示又需要一个地址, 只能 reflect.New 复制一份。层级越深累积越多,
导致纯 map 路径比改造前的旧实现还慢, 是整个重写里唯一的性能回归。

改法要两头兼顾:
  - 一路 reflect 走到底能省掉逐跳装箱, 但 map 后面的 struct 跳会丢掉偏移量
    快路径、退回 FieldByName —— 实测 map→struct→struct 反而比逐跳装箱慢 5.8%
  - 所以只在**连续的 map 段**内用 reflect, 一离开就装箱一次切回快路径

  map→map→struct→struct  303.2ns/16a -> 205.6ns/10a   1.47x
  map→struct→struct→map  309.0ns/16a -> 246.3ns/11a   1.25x
  map→struct→struct      224.6ns/11a -> 140.3ns/ 6a   1.60x
  纯 map 三层            300.9ns/20a -> 260.8ns/15a   1.15x  (原 0.72x)
  纯 map 单跳            120.0ns/ 8a -> 126.8ns/ 7a   0.95x
  struct 三层(对照)      200.4ns/ 9a ->  59.6ns/ 2a   3.36x

单跳 map 仍慢 5%: 只有一跳时没有 boxCopy 可省, 而 valueAt 那次 reflect.NewAt
省不掉。其余形态全部转正。

两个实现上的坑, 都是测试抓出来的:

1. walkMapRun 发现已离开 map 段时, 那一跳已经从迭代器取走但还没用, 必须交还
   给调用方。最初用 *it = save 回退, 结果**零分配快路径全线退化成 1 次分配** ——
   通过指针写回会让逃逸分析认为 it 指向的内容可能逃逸, 进而判定 Get 的可变参数
   切片逃逸, 波及所有路径, 连纯 struct 的路径都跟着中枪。改成把待处理的段
   作为返回值交出去就好了。

2. 若干处清理: normalize 从 step 提到 walk(step 只有 walk 一个调用方);
   移除 step 里已不可达的 Map 分支。

测试 252 个对拍用例覆盖 map 之后的各种后继类型与失败形态, 另加 19 个交界用例
专压"离开 map 段"那一跳。已验证有效: 把交还 pending 的那行改成丢弃后,
53 个用例失败。
2026-08-31 14:15:39 +08:00