From 50f5531d3be93adba436688b815c1fab4ff4c3ee Mon Sep 17 00:00:00 2001 From: what Date: Mon, 31 Aug 2026 14:52:23 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E8=AE=B2=E6=B8=85"=E5=80=BC=E4=BC=A0?= =?UTF-8?q?=E9=80=92=E7=9A=84=E5=BC=80=E9=94=80=E5=8F=AA=E5=9C=A8=20New=20?= =?UTF-8?q?=E8=BF=99=E4=B8=80=E6=AC=A1",=20=E5=B9=B6=E8=A1=A5=E4=B8=8A?= =?UTF-8?q?=E6=B5=85=E6=8B=B7=E8=B4=9D/=E6=B7=B1=E6=8B=B7=E8=B4=9D?= =?UTF-8?q?=E7=9A=84=E5=88=86=E5=B2=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 两处之前没说清楚的地方。 一、值传递的代价只发生在构造那一次, 不影响之后的操作 原来只列了 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 是另一个更小的样本)。 --- README.md | 13 ++++++++++++- docs/flow-new.svg | 25 +++++++++++++++---------- docs/flow.md | 30 ++++++++++++++++++++++++++++-- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 207d96a..9219669 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,18 @@ rfx.Set("Name", "Bob") | 50 元素 slice | 594 ns / 6 allocs | 25 ns / 1 alloc | **24x** | | 8 字段纯值 struct | 85.5 ns / 2 allocs | 25.5 ns / 1 alloc | 3.4x | -判断标准就一句:**这份数据我会不会通过 R 去写?写了穿透出去有没有问题?** +**注意这笔开销只发生在 `New` 这一次。** 构造完成后两者的内部表示完全相同, +后续 `Get`/`Set` 走同一条代码路径,速度没有差别: + +| | 构造后 `Get` | 构造后 `Set` | +|---|---|---| +| `New(v)` 传值构造 | 49.7 ns / 2 allocs | 27.2 ns / 0 allocs | +| `New(&v)` 传指针构造 | 50.3 ns / 2 allocs | 27.1 ns / 0 allocs | + +所以要看的是**构造频次**而不是访问频次:构造一次访问很多次的话, +传值那点开销会被摊薄;每个请求都构造、对象又含 map/slice 时才值得计较。 + +至于选哪个,标准就一句:**这份数据我会不会通过 R 去写?写了穿透出去有没有问题?** - 只读,或者本来就想改调用方的数据 → **传指针** - 需要一份互不干扰的副本 → 传值(或者传指针之后用 `Scope()` 取局部副本) diff --git a/docs/flow-new.svg b/docs/flow-new.svg index db4ce13..55b0f97 100644 --- a/docs/flow-new.svg +++ b/docs/flow-new.svg @@ -1,5 +1,5 @@ + aria-label="New 的流程:先按具体类型走快速分支(nil 和 valuex.Nil 返回 Nil、R 原样返回、[]R 直接包装),否则经 normalizeInputValue 解包、递归解引用指针和 interface、校验目标类型,再按 isPtr 分岔:传指针则直接借用,传值则再按类型是否含引用成分分两条:纯值逐字节浅拷贝,含 map/slice/指针才 DeepClone 递归,最后构造 rfx">