两部分改动。
一、更正 README 里关于 map 的性能结论(这是修正一个错误的说法)
原来写的是 "map 只快 1.25 倍", 但那个数字来自 Get("Meta","k") ——
第一跳 Meta 是 struct 字段, 走了快路径, 把第二跳 map 的损失盖了过去。
标成 "map 键" 会误导人。
补测纯 map 路径(根就是 map, 全程没有 struct 跳), 复测 10 轮结果稳定:
1 层 旧 120.7ns/8allocs -> 新 161.7ns/8allocs 0.75x
3 层 旧 314.4ns/20allocs -> 新 439.5ns/20allocs 0.72x
也就是说纯 map 场景当前实现比旧版慢约 28%, 不是变快。原因是内部表示为
"地址 + 类型描述符", 而 map 元素不可寻址, 每跳一次 map 都要 reflect.New
拷贝一份才能拿到地址; 旧实现直接持有 reflect.Value, 没这次拷贝。
表格改成分行列出两种场景, 并加了选型建议: 数据以 map[string]any 为主的
调用方, 这次重写没有收益, 反而略有退化 —— 收益全部集中在 struct 字段访问。
二、新增 docs/flow.md 与两张手写 SVG 流程图
说明 Get/Set 如何把路径逐段分派: 每段先 normalize 剥掉指针和 interface,
再按 Kind 进入 struct/slice/array 三条 unsafe 快路径、map 的 reflect 回退,
或直接拒绝。三色区分快路径 / 回退 / 拒绝, 把两件容易被忽略的事画了出来:
1. map 是唯一在 walk 循环内部就要堆拷贝的分支 —— 正是上面那个负收益的
直接原因, 图里一眼能看到。
2. Set 的 assignField 是四级递降, 第 ④ 级把复合类型整个交回 refx。
原因写在文档里: 指针字段要设置指向的值而不是替换指针、[]any 要逐元素
转换、map 可填充进 struct —— 重新实现必然出偏差, 是实际踩过的坑。
SVG 通过 img 引用时是隔离渲染(拿不到宿主页面的 CSS 变量和 currentColor),
所以配色和 prefers-color-scheme 明暗适配都烘在文件内部, 背景留透明。
README 性能章节开头加一行指向该文档, 正文只留结论。
140 lines
10 KiB
XML
140 lines
10 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 726" role="img"
|
|
aria-label="Get 的流程:New 构造 rfx 后进入 walk 循环,每段路径先经 normalize 解引用,再由 step 按 Kind 分派到 struct、slice、array 三条 unsafe 快路径,或 map 的 reflect 回退分支,或直接拒绝;循环结束后 boxed 装箱一次返回">
|
|
<style>
|
|
/* 独立 SVG 通过 img 引用时是隔离渲染的: 拿不到页面的 CSS 变量, 也继承不到
|
|
currentColor。所以配色和明暗适配都必须写在文件内部。
|
|
背景刻意留透明, 让它贴合宿主页面的底色。 */
|
|
svg { --fg:#171C22; --muted:#5A6672; --faint:#8B96A2;
|
|
--line:#D8DEE5; --surface:#FFFFFF;
|
|
--accent:#0B6E75; --accent-soft:#E3F1F2;
|
|
--fallback:#9A5B08; --fallback-soft:#F7EBDA;
|
|
--stop:#9B3232; --stop-soft:#F7E4E4; }
|
|
@media (prefers-color-scheme: dark) {
|
|
svg { --fg:#DFE5EC; --muted:#93A0AE; --faint:#6B7885;
|
|
--line:#2A333E; --surface:#151B23;
|
|
--accent:#4ECDC4; --accent-soft:#12312F;
|
|
--fallback:#E3A857; --fallback-soft:#33270F;
|
|
--stop:#E07A6E; --stop-soft:#331A18; }
|
|
}
|
|
.n-label { font-family:"IBM Plex Mono",ui-monospace,"SF Mono",Menlo,monospace; font-size:13px; font-weight:500; }
|
|
.n-sub { font-family:"IBM Plex Sans",system-ui,"PingFang SC","Microsoft YaHei",sans-serif; font-size:11.5px; }
|
|
.e-label { font-family:"IBM Plex Sans",system-ui,"PingFang SC","Microsoft YaHei",sans-serif; font-size:11.5px; }
|
|
.grp { font-family:"IBM Plex Mono",ui-monospace,Menlo,monospace; font-size:11px; letter-spacing:0.08em; }
|
|
</style>
|
|
<defs>
|
|
<marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--fg)"/>
|
|
</marker>
|
|
<marker id="ah-fast" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--accent)"/>
|
|
</marker>
|
|
<marker id="ah-slow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--fallback)"/>
|
|
</marker>
|
|
<marker id="ah-stop" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
|
|
<path d="M 0 0 L 10 5 L 0 10 z" fill="var(--stop)"/>
|
|
</marker>
|
|
</defs>
|
|
|
|
<!-- 1. New -->
|
|
<rect x="330" y="16" width="300" height="52" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="480" y="38" text-anchor="middle" fill="var(--fg)">New(&v)</text>
|
|
<text class="n-sub" x="480" y="55" text-anchor="middle" fill="var(--muted)">rfx{ td, ptr, writable } · 24 字节,栈上</text>
|
|
|
|
<line x1="480" y1="68" x2="480" y2="98" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#ah)"/>
|
|
|
|
<!-- 2. Get -->
|
|
<rect x="358" y="98" width="244" height="38" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="480" y="122" text-anchor="middle" fill="var(--fg)">Get(path...)</text>
|
|
|
|
<line x1="480" y1="136" x2="480" y2="166" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#ah)"/>
|
|
|
|
<!-- loop group -->
|
|
<rect x="150" y="166" width="660" height="330" rx="7" fill="none" stroke="var(--line)" stroke-width="1.5" stroke-dasharray="5 4"/>
|
|
<text class="grp" x="166" y="185" fill="var(--faint)">WALK 循环 · 全程栈上,零分配</text>
|
|
|
|
<!-- 3. pathIter -->
|
|
<rect x="330" y="198" width="300" height="52" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="480" y="220" text-anchor="middle" fill="var(--fg)">pathIter.next()</text>
|
|
<text class="n-sub" x="480" y="237" text-anchor="middle" fill="var(--muted)">按 "." 切子串,不新建切片</text>
|
|
|
|
<line x1="480" y1="250" x2="480" y2="280" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#ah)"/>
|
|
<text class="e-label" x="492" y="269" fill="var(--muted)">每段 seg</text>
|
|
|
|
<!-- 4. normalize -->
|
|
<rect x="306" y="280" width="348" height="56" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="480" y="302" text-anchor="middle" fill="var(--fg)">normalize()</text>
|
|
<text class="n-sub" x="480" y="320" text-anchor="middle" fill="var(--muted)">Ptr → loadPtr 逐层解引用 · Interface → 拆包</text>
|
|
|
|
<line x1="480" y1="336" x2="480" y2="366" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#ah)"/>
|
|
|
|
<!-- 5. step -->
|
|
<rect x="358" y="366" width="244" height="38" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="480" y="390" text-anchor="middle" fill="var(--fg)">step(seg) · 按 td.Kind</text>
|
|
|
|
<!-- fan-out spine -->
|
|
<line x1="480" y1="404" x2="480" y2="422" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<line x1="108" y1="422" x2="852" y2="422" stroke="var(--fg)" stroke-width="1.5"/>
|
|
|
|
<!-- five drops -->
|
|
<line x1="108" y1="422" x2="108" y2="446" stroke="var(--accent)" stroke-width="1.5" marker-end="url(#ah-fast)"/>
|
|
<line x1="294" y1="422" x2="294" y2="446" stroke="var(--accent)" stroke-width="1.5" marker-end="url(#ah-fast)"/>
|
|
<line x1="480" y1="422" x2="480" y2="446" stroke="var(--accent)" stroke-width="1.5" marker-end="url(#ah-fast)"/>
|
|
<line x1="666" y1="422" x2="666" y2="446" stroke="var(--fallback)" stroke-width="1.5" marker-end="url(#ah-slow)"/>
|
|
<line x1="852" y1="422" x2="852" y2="446" stroke="var(--stop)" stroke-width="1.5" marker-end="url(#ah-stop)"/>
|
|
|
|
<!-- branch boxes -->
|
|
<rect x="28" y="446" width="160" height="64" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
|
|
<text class="n-label" x="108" y="468" text-anchor="middle" fill="var(--accent)">Struct</text>
|
|
<text class="n-sub" x="108" y="485" text-anchor="middle" fill="var(--fg)">lookupField 查偏移</text>
|
|
<text class="n-sub" x="108" y="500" text-anchor="middle" fill="var(--fg)">fieldAt(ptr, off)</text>
|
|
|
|
<rect x="214" y="446" width="160" height="64" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
|
|
<text class="n-label" x="294" y="468" text-anchor="middle" fill="var(--accent)">Slice</text>
|
|
<text class="n-sub" x="294" y="485" text-anchor="middle" fill="var(--fg)">parseIndex</text>
|
|
<text class="n-sub" x="294" y="500" text-anchor="middle" fill="var(--fg)">sliceElemAt</text>
|
|
|
|
<rect x="400" y="446" width="160" height="64" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
|
|
<text class="n-label" x="480" y="468" text-anchor="middle" fill="var(--accent)">Array</text>
|
|
<text class="n-sub" x="480" y="485" text-anchor="middle" fill="var(--fg)">parseIndex</text>
|
|
<text class="n-sub" x="480" y="500" text-anchor="middle" fill="var(--fg)">arrayElemAt</text>
|
|
|
|
<rect x="586" y="446" width="160" height="64" rx="5" fill="var(--fallback-soft)" stroke="var(--fallback)" stroke-width="1.5"/>
|
|
<text class="n-label" x="666" y="468" text-anchor="middle" fill="var(--fallback)">Map</text>
|
|
<text class="n-sub" x="666" y="485" text-anchor="middle" fill="var(--fg)">tryMapFieldValue</text>
|
|
<text class="n-sub" x="666" y="500" text-anchor="middle" fill="var(--fg)">boxCopy · 堆拷贝</text>
|
|
|
|
<rect x="772" y="446" width="160" height="64" rx="5" fill="var(--stop-soft)" stroke="var(--stop)" stroke-width="1.5"/>
|
|
<text class="n-label" x="852" y="468" text-anchor="middle" fill="var(--stop)">其它 / 未导出</text>
|
|
<text class="n-sub" x="852" y="485" text-anchor="middle" fill="var(--fg)">拒绝, ok = false</text>
|
|
<text class="n-sub" x="852" y="500" text-anchor="middle" fill="var(--fg)">→ 无效值</text>
|
|
|
|
<!-- loop back: from the 4 continuing branches -->
|
|
<path d="M 108 510 L 108 546 L 666 546" fill="none" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<path d="M 294 510 L 294 546" fill="none" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<path d="M 480 510 L 480 546" fill="none" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<path d="M 666 510 L 666 546" fill="none" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<path d="M 666 546 L 892 546 L 892 224 L 634 224" fill="none" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#ah)"/>
|
|
<text class="e-label" x="884" y="392" text-anchor="end" fill="var(--muted)">还有下一段 → 继续循环</text>
|
|
|
|
<!-- exit -->
|
|
<line x1="480" y1="546" x2="480" y2="500" stroke="none"/>
|
|
<path d="M 480 546 L 240 546 L 240 588" fill="none" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#ah)"/>
|
|
<text class="e-label" x="252" y="574" fill="var(--muted)">路径走完</text>
|
|
|
|
<!-- 6. boxed -->
|
|
<rect x="90" y="588" width="300" height="56" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="240" y="610" text-anchor="middle" fill="var(--fg)">boxed() → &rfx{}</text>
|
|
<text class="n-sub" x="240" y="628" text-anchor="middle" fill="var(--muted)">唯一一次堆分配 · 24 字节</text>
|
|
|
|
<line x1="240" y1="644" x2="240" y2="672" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#ah)"/>
|
|
<rect x="118" y="672" width="244" height="38" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="240" y="696" text-anchor="middle" fill="var(--fg)">R 接口</text>
|
|
|
|
<!-- Get[T] shortcut -->
|
|
<path d="M 602 617 L 400 617" fill="none" stroke="var(--accent)" stroke-width="1.5" stroke-dasharray="4 3" marker-end="url(#ah-fast)"/>
|
|
<rect x="602" y="588" width="320" height="56" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
|
|
<text class="n-label" x="762" y="610" text-anchor="middle" fill="var(--accent)">Get[T] 泛型直取</text>
|
|
<text class="n-sub" x="762" y="628" text-anchor="middle" fill="var(--fg)">跳过装箱,直接写进调用方变量 · 0 分配</text>
|
|
</svg>
|