what
|
1cc43562ba
|
docs: 把"该传值还是传指针"讲清楚, 代替新增 Ref 选项
起因是想给 New 加一个跳过深拷贝的开关。做出来之后对比发现,现有的
"传指针"已经在三个维度上都更好, 那个开关是多余的 API:
New(m, WithRef()) New(&m)
速度 47.5ns / 3 allocs 24.8ns / 1 alloc
map/slice 共享底层 是 是
struct 写回调用方 否 是
新增 API 一个选项 + 一套语义 零
WithRef 对 struct 还有个说不清的半吊子语义 —— 共享 map/slice 字段的底层数据,
但写不回 struct 本身。这种"部分穿透"很难讲明白也容易踩坑, 而 & 没有这个问题。
所以撤掉选项, 改为把这件事在文档里讲透。
README 新增"该传值还是传指针"一节, 给出实测对比:
10 键嵌套 map 传值 3227ns/119allocs 传指针 24.8ns/1alloc 130x
50 元素 slice 传值 594ns/ 6allocs 传指针 25ns/1alloc 24x
8 字段纯值 struct 传值 85.5ns/ 2allocs 传指针 25.5ns/1alloc 3.4x
判断标准就一句: 这份数据我会不会通过 R 去写? 写了穿透出去有没有问题?
只读或本来就想改原数据就传指针; 需要互不干扰的副本就传值(或传指针后 Scope)。
另附取不到地址的表达式该怎么落局部变量。
代码改动只剩一处无关的重构: 把 cloneForValueInput 里的浅拷贝抽成 shallowBox。
新增 TestPointerInputSharesAndSkipsClone 把 README 的承诺钉住:
传指针必须共享底层 map/slice、能写回 struct 变量、不触发深拷贝,
且 Scope 仍能在此基础上取到隔离副本。
|
2026-08-31 14:40:37 +08:00 |
|
what
|
a2c0c9783e
|
perf: New(值) 对不含引用成分的类型改走浅拷贝
New(值) 的语义是"改副本不影响调用方的原数据", 原来一律走 DeepClone 递归。
但对于不含引用成分的类型, 逐字节拷贝就已经完全独立了, 没必要递归。
在类型描述符里加 needsClone(构建期算一次, 不在热路径):
含指针/切片/map/interface/chan/func/unsafe.Pointer 即为真, 递归包含字段与
数组元素。字符串不算 —— 底层数组不可变, 共享是安全的。
New(纯值 struct) 145.0ns -> 85.5ns 1.70x
New(含引用 struct) 304.9ns 不变(深拷贝确实必要)
New(&ptr) 25.8ns 不变
注意 1.70x 而不是更多: New 还要做类型断言、normalizeInputValue、递归解引用、
类型校验和描述符查找, 省掉的只是深拷贝那一段(约 59ns)。
needsClone 必须在递归之前按 Kind 置位, 否则自引用类型会漏标: 类型图里的环
一定要经过某种间接(指针/切片/map), 先置位才能让递归回来的自引用类型读到
正确的值。测试里专门有一条 cNode{Next *cNode} 守这个。
判错的后果是静默的数据串改 —— 浅拷贝一个含引用的类型, 写入会穿透到调用方
的原数据, 不报错也不 panic。所以测试分两层:
1. needsClone 判定本身: 14 个用例, 含"引用藏在两层嵌套下"、"藏在数组元素里"、
自引用、time.Time(内含 *Location)等容易漏判的形态
2. 隔离语义: 7 个用例, 无论走哪条路都断言原数据未被改写
3. 与旧实现对拍: 5 种输入 x 5 个观察方法
已验证测试有效: 把 needsClone 强制为 false 后, 6 个隔离用例全部失败, 包括
藏在两层嵌套和数组元素里的引用。
|
2026-08-31 11:10:49 +08:00 |
|