两部分改动。
一、更正 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 性能章节开头加一行指向该文档, 正文只留结论。
107 lines
7.7 KiB
XML
107 lines
7.7 KiB
XML
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 566" role="img"
|
|
aria-label="Set 的流程:先把 key 拆成父路径和末段,走 walk 定位父级;父级不存在或不可写时整个交回 refx 慢路径;否则按 Kind 分派,再经过 assignField 的四级递降,从零分配的直接写内存一直退到交回 refx">
|
|
<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="s-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="s-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="s-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>
|
|
</defs>
|
|
|
|
<!-- Set -->
|
|
<rect x="40" y="16" width="290" height="52" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="185" y="38" text-anchor="middle" fill="var(--fg)">Set(key, val)</text>
|
|
<text class="n-sub" x="185" y="55" text-anchor="middle" fill="var(--muted)">splitLastStr → 父路径 + 末段</text>
|
|
|
|
<line x1="185" y1="68" x2="185" y2="98" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#s-ah)"/>
|
|
|
|
<!-- walk parent -->
|
|
<rect x="40" y="98" width="290" height="38" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="185" y="122" text-anchor="middle" fill="var(--fg)">walk(父路径)</text>
|
|
|
|
<line x1="185" y1="136" x2="185" y2="168" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#s-ah)"/>
|
|
|
|
<!-- gate -->
|
|
<rect x="26" y="168" width="318" height="56" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="185" y="190" text-anchor="middle" fill="var(--fg)">父级存在?且 writable?</text>
|
|
<text class="n-sub" x="185" y="208" text-anchor="middle" fill="var(--muted)">unsafe 路径没有 CanSet(),必须自己判</text>
|
|
|
|
<!-- gate: no -> refx -->
|
|
<path d="M 344 196 L 560 196" fill="none" stroke="var(--fallback)" stroke-width="1.5" marker-end="url(#s-ah-slow)"/>
|
|
<text class="e-label" x="452" y="187" text-anchor="middle" fill="var(--fallback)">否 · 典型是路径穿过 map</text>
|
|
|
|
<rect x="560" y="168" width="368" height="56" rx="5" fill="var(--fallback-soft)" stroke="var(--fallback)" stroke-width="1.5"/>
|
|
<text class="n-label" x="744" y="190" text-anchor="middle" fill="var(--fallback)">整个交回 refx.Set()</text>
|
|
<text class="n-sub" x="744" y="208" text-anchor="middle" fill="var(--fg)">map 的取出-修改-写回 SetMapIndex 在那边</text>
|
|
|
|
<!-- gate: yes -->
|
|
<line x1="185" y1="224" x2="185" y2="256" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#s-ah)"/>
|
|
<text class="e-label" x="197" y="245" fill="var(--muted)">是</text>
|
|
|
|
<rect x="40" y="256" width="290" height="38" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="185" y="280" text-anchor="middle" fill="var(--fg)">setField · 按 Kind</text>
|
|
|
|
<line x1="185" y1="294" x2="185" y2="326" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#s-ah)"/>
|
|
<text class="e-label" x="197" y="315" fill="var(--muted)">Struct / Slice / Array</text>
|
|
|
|
<!-- assignField ladder -->
|
|
<rect x="26" y="326" width="902" height="196" rx="7" fill="none" stroke="var(--line)" stroke-width="1.5" stroke-dasharray="5 4"/>
|
|
<text class="grp" x="42" y="345" fill="var(--faint)">ASSIGNFIELD · 四级递降,能多快就多快</text>
|
|
|
|
<rect x="46" y="358" width="418" height="60" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
|
|
<text class="n-label" x="66" y="380" fill="var(--accent)">① storeFast</text>
|
|
<text class="n-sub" x="66" y="399" fill="var(--fg)">类型完全一致的 string/int/bool/float64</text>
|
|
<text class="n-sub" x="66" y="413" fill="var(--muted)">按 *T 直接写内存 · 0 分配</text>
|
|
|
|
<rect x="490" y="358" width="418" height="60" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="510" y="380" fill="var(--fg)">② 类型精确匹配</text>
|
|
<text class="n-sub" x="510" y="399" fill="var(--fg)">rv.Type() == td.rtype</text>
|
|
<text class="n-sub" x="510" y="413" fill="var(--muted)">valueAt().Set(rv),不绕道</text>
|
|
|
|
<rect x="46" y="440" width="418" height="60" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
|
|
<text class="n-label" x="66" y="462" fill="var(--fg)">③ 标量目标</text>
|
|
<text class="n-sub" x="66" y="481" fill="var(--fg)">assignReflect + spf13/cast 转换</text>
|
|
<text class="n-sub" x="66" y="495" fill="var(--muted)">溢出截断等边角行为与旧实现对齐</text>
|
|
|
|
<rect x="490" y="440" width="418" height="60" rx="5" fill="var(--fallback-soft)" stroke="var(--fallback)" stroke-width="1.5"/>
|
|
<text class="n-label" x="510" y="462" fill="var(--fallback)">④ 复合目标 → refx</text>
|
|
<text class="n-sub" x="510" y="481" fill="var(--fg)">指针 / 切片 / 结构体 / map / interface</text>
|
|
<text class="n-sub" x="510" y="495" fill="var(--muted)">语义琐碎,重写必然出偏差</text>
|
|
|
|
<!-- ladder arrows -->
|
|
<path d="M 464 388 L 490 388" fill="none" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#s-ah)"/>
|
|
<path d="M 908 418 L 928 418 L 928 430 L 26 430 L 26 470 L 46 470" fill="none" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#s-ah)"/>
|
|
<path d="M 464 470 L 490 470" fill="none" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#s-ah)"/>
|
|
<text class="e-label" x="477" y="382" text-anchor="middle" fill="var(--muted)">miss</text>
|
|
<text class="e-label" x="477" y="464" text-anchor="middle" fill="var(--muted)">miss</text>
|
|
|
|
<!-- ok exit -->
|
|
<path d="M 255 418 L 255 530" fill="none" stroke="var(--accent)" stroke-width="1.5" stroke-dasharray="4 3" marker-end="url(#s-ah-fast)"/>
|
|
<text class="e-label" x="267" y="543" fill="var(--accent)">命中即返回</text>
|
|
</svg>
|