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 |
|
what
|
cb84176381
|
docs: 补上 New 的解析流程,并用测试钉住 rfx 的大小
原来的流程文档只画了 Get 和 Set, New 在图里就是一个方框。但 New 的分支其实不少,
而且"指针模式 vs 值模式"是对外承诺的语义, 值得单独说清楚。
新增 docs/flow-new.svg 与对应章节, 覆盖四步: 快速分支 -> 解包 -> 解引用与校验
-> 按 isPtr 分岔。其中三处是使用者真正需要知道的:
1. 快速分支里 []R 不深拷贝也不包指针。这样 Raw() 才是 Slice kind、
Array() 才能取到里面的 R。
2. 传值比传指针贵约 60 倍(约 1518ns/44allocs vs 约 25ns), 因为要 DeepClone
递归复制整个对象图, 代价随对象规模增长。不需要副本语义就一律传指针。
顺带记下 DeepClone 的已知限制: 循环引用会栈溢出(CloneValue 没有已访问
集合), 这是旧实现就有的行为, 当前实现复用了它所以两边一致。
3. 为什么两条路最后都标 ptrRoot。漏掉它会连锁出问题: Raw() 返回值类型而不是
指针时, normalizeAccessorSlice 会把 []R 解成 []map 而不是 []*map ——
这个 bug 只在 []R 相关用例上才暴露得出来。
另外补 rfx_size_test.go: 文档里"24 字节"这个数字此前没有测试守着, 而 rfx 在
walk 循环里全程按值传递, 加字段会直接拖慢热路径。加了 ptrRoot 之后仍是 24 字节
(落在原有的对齐填充里), 用测试钉住, 以后加字段时会先失败。
同时修正: 源码对照表补上 New/newRfx 与 normalizeInputValue/DeepClone 的位置,
Get 章节的 rfx 字段列表补上 ptrRoot, 开头改成三个入口。
|
2026-08-31 11:02:00 +08:00 |
|