Files
reflux/docs/flow-new.svg
T
what 50f5531d3b docs: 讲清"值传递的开销只在 New 这一次", 并补上浅拷贝/深拷贝的分岔
两处之前没说清楚的地方。

一、值传递的代价只发生在构造那一次, 不影响之后的操作

原来只列了 New(值) 3227ns vs New(&v) 25ns, 容易被读成"传值之后一直慢"。
实际上构造完成后两者的内部表示完全相同(都是类型描述符 + 地址), 后续
Get/Set 走同一条代码路径:

                     构造后 Get          构造后 Set
  New(v)  传值构造    49.7ns / 2 allocs   27.2ns / 0 allocs
  New(&v) 传指针构造  50.3ns / 2 allocs   27.1ns / 0 allocs

差异在噪声范围内。所以要看的是**构造频次**而不是访问频次: 构造一次访问很多次
的话那点开销会被摊薄; 每个请求都构造、对象又含 map/slice 时才值得计较。

二、流程图补上值分支内部的二次分岔

图是在 needsClone 那次优化之前画的, 一直写着"传值 = DeepClone", 已经不准:

  不含引用成分(纯值 struct/数组/字符串) -> 逐字节浅拷贝, 约 85 ns
    字符串虽然内部有指针, 但底层数组不可变, 共享是安全的
  含指针/切片/map/interface(递归包含字段与数组元素) -> DeepClone

这也是"能不能 copy 一份再取指针"的答案: 纯值类型可以(库里已经这么做了),
含引用的类型不行 —— 浅拷贝只复制 slice/map 的头部, 底层数据仍与调用方共享,
写入会穿透, 那就不再是隔离副本了。

顺带把代价数字更正为实测值(原来写的 1518ns/44allocs 是另一个更小的样本)。
2026-08-31 14:52:23 +08:00

141 lines
10 KiB
XML

<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 960 742" role="img"
aria-label="New 的流程:先按具体类型走快速分支(nil 和 valuex.Nil 返回 Nil、R 原样返回、[]R 直接包装),否则经 normalizeInputValue 解包、递归解引用指针和 interface、校验目标类型,再按 isPtr 分岔:传指针则直接借用,传值则再按类型是否含引用成分分两条:纯值逐字节浅拷贝,含 map/slice/指针才 DeepClone 递归,最后构造 rfx">
<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="n-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="n-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="n-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="n-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>
<!-- R1 entry -->
<rect x="90" y="20" width="280" height="42" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
<text class="n-label" x="230" y="46" text-anchor="middle" fill="var(--fg)">New(v any)</text>
<line x1="230" y1="62" x2="230" y2="92" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#n-ah)"/>
<!-- R2 type switch -->
<rect x="90" y="92" width="280" height="58" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
<text class="n-label" x="230" y="114" text-anchor="middle" fill="var(--fg)">按具体类型快速分支</text>
<text class="n-sub" x="230" y="133" text-anchor="middle" fill="var(--muted)">switch v.(type) · 命中即返回</text>
<!-- early exits -->
<path d="M 370 121 L 660 121" fill="none" stroke="var(--accent)" stroke-width="1.5" marker-end="url(#n-ah-fast)"/>
<text class="e-label" x="515" y="112" text-anchor="middle" fill="var(--accent)">命中</text>
<rect x="660" y="80" width="272" height="84" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
<text class="n-sub" x="676" y="101" fill="var(--fg)">nil / valuex.Nil → 返回 Nil 单例</text>
<text class="n-sub" x="676" y="122" fill="var(--fg)">已经是 R → 原样返回,不重新包装</text>
<text class="n-sub" x="676" y="143" fill="var(--fg)">[]R → 直接包装,不深拷贝</text>
<text class="n-sub" x="676" y="158" fill="var(--muted)">(保住 Raw() 是 Slice、Array() 能取到 R)</text>
<line x1="230" y1="150" x2="230" y2="182" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#n-ah)"/>
<text class="e-label" x="242" y="171" fill="var(--muted)">其它</text>
<!-- R3 normalizeInputValue -->
<rect x="60" y="182" width="340" height="66" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
<text class="n-label" x="230" y="204" text-anchor="middle" fill="var(--fg)">normalizeInputValue</text>
<text class="n-sub" x="230" y="222" text-anchor="middle" fill="var(--muted)">Accessor → 取 Raw() · []Accessor → 合并成切片</text>
<text class="n-sub" x="230" y="238" text-anchor="middle" fill="var(--muted)">reflect.Value → 直接用 · 同时算出 isPtr</text>
<line x1="230" y1="248" x2="230" y2="280" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#n-ah)"/>
<!-- R4 deref -->
<rect x="60" y="280" width="340" height="62" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
<text class="n-label" x="230" y="302" text-anchor="middle" fill="var(--fg)">递归解引用 Ptr / Interface</text>
<text class="n-sub" x="230" y="322" text-anchor="middle" fill="var(--muted)">中途遇到指针会把 isPtr 置真</text>
<path d="M 400 305 L 660 305" fill="none" stroke="var(--stop)" stroke-width="1.5" marker-end="url(#n-ah-stop)"/>
<text class="e-label" x="530" y="296" text-anchor="middle" fill="var(--stop)">遇到 nil</text>
<rect x="660" y="284" width="272" height="42" rx="5" fill="var(--stop-soft)" stroke="var(--stop)" stroke-width="1.5"/>
<text class="n-sub" x="796" y="310" text-anchor="middle" fill="var(--fg)">panic ErrTargetNilPointer</text>
<line x1="230" y1="342" x2="230" y2="374" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#n-ah)"/>
<!-- R5 type check -->
<rect x="60" y="374" width="340" height="62" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
<text class="n-label" x="230" y="396" text-anchor="middle" fill="var(--fg)">校验目标类型</text>
<text class="n-sub" x="230" y="416" text-anchor="middle" fill="var(--muted)">map/struct/slice/array/string/bool/float</text>
<path d="M 400 399 L 660 399" fill="none" stroke="var(--stop)" stroke-width="1.5" marker-end="url(#n-ah-stop)"/>
<text class="e-label" x="530" y="390" text-anchor="middle" fill="var(--stop)">int / chan / func …</text>
<rect x="660" y="378" width="272" height="42" rx="5" fill="var(--stop-soft)" stroke="var(--stop)" stroke-width="1.5"/>
<text class="n-sub" x="796" y="404" text-anchor="middle" fill="var(--fg)">panic 不支持的目标类型</text>
<line x1="230" y1="436" x2="230" y2="468" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#n-ah)"/>
<!-- R6 fork -->
<rect x="112" y="468" width="236" height="42" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
<text class="n-label" x="230" y="494" text-anchor="middle" fill="var(--fg)">isPtr ?</text>
<path d="M 230 510 L 230 528 L 150 528 L 150 556" fill="none" stroke="var(--accent)" stroke-width="1.5" marker-end="url(#n-ah-fast)"/>
<text class="e-label" x="140" y="547" text-anchor="end" fill="var(--accent)">是 · 传的是指针</text>
<path d="M 230 510 L 230 528 L 631 528 L 631 556" fill="none" stroke="var(--fallback)" stroke-width="1.5" marker-end="url(#n-ah-slow)"/>
<text class="e-label" x="643" y="547" fill="var(--fallback)">否 · 传的是值</text>
<!-- R7a borrow -->
<rect x="30" y="556" width="240" height="88" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
<text class="n-label" x="150" y="578" text-anchor="middle" fill="var(--accent)">直接借用该指针</text>
<text class="n-sub" x="150" y="598" text-anchor="middle" fill="var(--fg)">零拷贝 · 约 25 ns</text>
<text class="n-sub" x="150" y="616" text-anchor="middle" fill="var(--fg)">写入直接反映到原数据</text>
<text class="n-sub" x="150" y="634" text-anchor="middle" fill="var(--muted)">指针模式</text>
<!-- R7b 值模式: 再按 needsClone 分两条 -->
<rect x="330" y="556" width="602" height="88" rx="5" fill="none" stroke="var(--line)" stroke-width="1.5" stroke-dasharray="5 4"/>
<text class="grp" x="344" y="574" fill="var(--faint)">cloneForValueInput · 看类型描述符的 needsClone</text>
<rect x="344" y="582" width="278" height="50" rx="5" fill="var(--accent-soft)" stroke="var(--accent)" stroke-width="1.5"/>
<text class="n-sub" x="483" y="600" text-anchor="middle" fill="var(--accent)">不含引用成分 → 逐字节浅拷贝</text>
<text class="n-sub" x="483" y="618" text-anchor="middle" fill="var(--fg)">纯值 struct / 数组 · 约 85 ns</text>
<rect x="638" y="582" width="278" height="50" rx="5" fill="var(--fallback-soft)" stroke="var(--fallback)" stroke-width="1.5"/>
<text class="n-sub" x="777" y="600" text-anchor="middle" fill="var(--fallback)">含引用成分 → DeepClone 递归</text>
<text class="n-sub" x="777" y="618" text-anchor="middle" fill="var(--fg)">含 map/slice/指针 · 10 键 map 约 3200 ns</text>
<!-- converge -->
<path d="M 150 644 L 150 672 L 230 672" fill="none" stroke="var(--fg)" stroke-width="1.5"/>
<path d="M 631 644 L 631 672 L 230 672" fill="none" stroke="var(--fg)" stroke-width="1.5"/>
<line x1="230" y1="672" x2="230" y2="686" stroke="var(--fg)" stroke-width="1.5" marker-end="url(#n-ah)"/>
<!-- R8 newRfx -->
<rect x="60" y="686" width="340" height="48" rx="5" fill="var(--surface)" stroke="var(--fg)" stroke-width="1.5"/>
<text class="n-label" x="230" y="707" text-anchor="middle" fill="var(--fg)">rfx{ td, ptr, writable, ptrRoot }</text>
<text class="n-sub" x="230" y="725" text-anchor="middle" fill="var(--muted)">两条路最终都持有一个指针,ptrRoot = true</text>
<!-- note on ptrRoot -->
<path d="M 400 710 L 660 710" fill="none" stroke="var(--line)" stroke-width="1.5" stroke-dasharray="4 3"/>
<rect x="660" y="672" width="272" height="62" rx="5" fill="var(--surface)" stroke="var(--line)" stroke-width="1.5"/>
<text class="n-sub" x="676" y="693" fill="var(--muted)">ptrRoot 让 Raw() 返回 reflect.Ptr,</text>
<text class="n-sub" x="676" y="711" fill="var(--muted)">与旧实现一致。少了它,[]R 场景会</text>
<text class="n-sub" x="676" y="729" fill="var(--muted)">解成 []map 而不是 []*map。</text>
</svg>