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, 开头改成三个入口。
This commit is contained in:
@@ -0,0 +1,135 @@
|
|||||||
|
<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 分岔:传指针则直接借用,传值则 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 560 528 L 560 556" fill="none" stroke="var(--fallback)" stroke-width="1.5" marker-end="url(#n-ah-slow)"/>
|
||||||
|
<text class="e-label" x="572" 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 deep clone -->
|
||||||
|
<rect x="400" y="556" width="320" height="88" rx="5" fill="var(--fallback-soft)" stroke="var(--fallback)" stroke-width="1.5"/>
|
||||||
|
<text class="n-label" x="560" y="578" text-anchor="middle" fill="var(--fallback)">DeepClone 深拷贝</text>
|
||||||
|
<text class="n-sub" x="560" y="598" text-anchor="middle" fill="var(--fg)">递归复制整个对象图,返回新指针</text>
|
||||||
|
<text class="n-sub" x="560" y="616" text-anchor="middle" fill="var(--fg)">写入不影响原数据 · 值模式</text>
|
||||||
|
<text class="n-sub" x="560" y="634" text-anchor="middle" fill="var(--muted)">比借用贵得多,且循环引用会栈溢出</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 560 644 L 560 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>
|
||||||
|
After Width: | Height: | Size: 9.7 KiB |
+59
-3
@@ -1,9 +1,12 @@
|
|||||||
# 内部流程:路径是怎么被分派的
|
# 内部流程:路径是怎么被分派的
|
||||||
|
|
||||||
`Get` 和 `Set` 都不直接操作数据,而是把路径拆成段,逐段按当前值的 `Kind` 分派。
|
`New` 把各种输入收敛成统一的内部表示;`Get` 和 `Set` 则不直接操作数据,
|
||||||
|
而是把路径拆成段,逐段按当前值的 `Kind` 分派。
|
||||||
**分派到哪个分支,决定了这一跳走 unsafe 偏移量寻址还是退回 reflect,
|
**分派到哪个分支,决定了这一跳走 unsafe 偏移量寻址还是退回 reflect,
|
||||||
也决定了它要不要分配内存。**
|
也决定了它要不要分配内存。**
|
||||||
|
|
||||||
|
三张图分别对应这三个入口,配色编码一致。
|
||||||
|
|
||||||
图例:
|
图例:
|
||||||
|
|
||||||
| 标记 | 含义 |
|
| 标记 | 含义 |
|
||||||
@@ -14,6 +17,57 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 〇、构造:`New(v any)`
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
`New` 要把五花八门的输入收敛成统一的内部表示 `rfx{td, ptr, writable, ptrRoot}`。
|
||||||
|
过程分四步:**快速分支 → 解包 → 解引用与校验 → 按 isPtr 分岔**。
|
||||||
|
|
||||||
|
### 快速分支(命中即返回,不走后面的流程)
|
||||||
|
|
||||||
|
| 输入 | 结果 | 为什么特殊 |
|
||||||
|
|---|---|---|
|
||||||
|
| `nil` / `valuex.Nil` | 返回 `Nil` 单例 | 不分配 |
|
||||||
|
| 已经是 `R` | **原样返回** | 避免重复包装 |
|
||||||
|
| `[]R` | 直接包装,**不深拷贝** | 保住 `Raw()` 是 Slice kind、`Array()` 能取到里面的 R |
|
||||||
|
|
||||||
|
### 解包与校验
|
||||||
|
|
||||||
|
`normalizeInputValue` 负责把输入收敛成 `reflect.Value`:`valuex.Accessor` 取 `Raw()`,
|
||||||
|
`[]valuex.Accessor` / `[]R` 合并成一个切片,`reflect.Value` 直接用。
|
||||||
|
同时算出 `isPtr` —— 这个标记决定后面走哪条路。
|
||||||
|
|
||||||
|
然后递归解引用 `Ptr`/`Interface`(中途遇到指针会把 `isPtr` 置真),
|
||||||
|
最后校验目标类型:只接受 **map / struct / slice / array / string / bool / float**。
|
||||||
|
`int`、`chan`、`func` 这些不是容器的类型会 panic。
|
||||||
|
|
||||||
|
### 关键分岔:指针模式 vs 值模式
|
||||||
|
|
||||||
|
这是对外承诺的语义,也是 `New` 里唯一有显著性能差异的地方:
|
||||||
|
|
||||||
|
| | 做法 | 代价 | 语义 |
|
||||||
|
|---|---|---|---|
|
||||||
|
| **传指针** `New(&v)` | 直接借用该指针 | 约 25 ns,零拷贝 | 写入**直接反映到原数据** |
|
||||||
|
| **传值** `New(v)` | `DeepClone` 递归复制整个对象图 | **约 1518 ns / 44 allocs**(10 元素 + map 的对象) | 写入**不影响原数据** |
|
||||||
|
|
||||||
|
**传值比传指针贵约 60 倍**,而且随对象规模增长。如果不需要副本语义,一律传指针。
|
||||||
|
|
||||||
|
`DeepClone` 还有一个已知限制:**遇到循环引用会栈溢出**(`CloneValue` 没有已访问集合)。
|
||||||
|
这是旧实现就有的行为,当前实现直接复用了它,所以两边表现一致 ——
|
||||||
|
`New(&循环对象)` 本身没问题,`New(循环值)` 和 `Scope()` 会 fatal。
|
||||||
|
|
||||||
|
### 为什么最后要标 `ptrRoot`
|
||||||
|
|
||||||
|
两条路最终都持有一个指针(`DeepClone` 返回的也是指针),所以统一标 `ptrRoot = true`,
|
||||||
|
让 `Raw()` 返回 `reflect.Ptr` —— 与旧实现保持一致。
|
||||||
|
|
||||||
|
这个标记看着不起眼,但漏掉会连锁出问题:`Raw()` 返回值类型而不是指针时,
|
||||||
|
`normalizeAccessorSlice` 会把 `[]R` 解成 `[]map[string]any` 而不是 `[]*map[string]any`。
|
||||||
|
这个 bug 只有在 `[]R` 相关的用例上才暴露得出来。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 一、取值:`Get(path...)`
|
## 一、取值:`Get(path...)`
|
||||||
|
|
||||||

|

|
||||||
@@ -25,7 +79,7 @@
|
|||||||
|
|
||||||
| 步骤 | 函数 | 说明 |
|
| 步骤 | 函数 | 说明 |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| 构造 | `New(&v)` | 得到 `rfx{td, ptr, writable}`,24 字节,栈上传递 |
|
| 构造 | `New(&v)` | 见上一节,得到 `rfx{td, ptr, writable, ptrRoot}`,24 字节 |
|
||||||
| 取段 | `pathIter.next()` | 按 `.` 切子串,**不新建切片** —— 旧实现的 `expandPath` 每次要分配 4 次 |
|
| 取段 | `pathIter.next()` | 按 `.` 切子串,**不新建切片** —— 旧实现的 `expandPath` 每次要分配 4 次 |
|
||||||
| 解引用 | `normalize()` | `Ptr` 走 `loadPtr` 逐层解;`Interface` 退回 reflect 拆包 |
|
| 解引用 | `normalize()` | `Ptr` 走 `loadPtr` 逐层解;`Interface` 退回 reflect 拆包 |
|
||||||
| 分派 | `step(seg)` | 按 `td.Kind` 进入下面五个分支之一 |
|
| 分派 | `step(seg)` | 按 `td.Kind` 进入下面五个分支之一 |
|
||||||
@@ -118,6 +172,8 @@
|
|||||||
|
|
||||||
| 图中节点 | 位置 |
|
| 图中节点 | 位置 |
|
||||||
|---|---|
|
|---|---|
|
||||||
|
| `New` / `newRfx` | `reflux.go` |
|
||||||
|
| `normalizeInputValue` / `DeepClone` | `util.go` |
|
||||||
| `walk` / `step` / `normalize` / `fromReflect` | `rfx.go` |
|
| `walk` / `step` / `normalize` / `fromReflect` | `rfx.go` |
|
||||||
| `Set` / `setField` / `assignField` / `storeFast` | `rfx.go` |
|
| `Set` / `setField` / `assignField` / `storeFast` | `rfx.go` |
|
||||||
| `pathIter` | `path.go` |
|
| `pathIter` | `path.go` |
|
||||||
@@ -125,7 +181,7 @@
|
|||||||
| `fieldAt` / `sliceElemAt` / `boxCopy` 等全部 unsafe 操作 | `unsafeptr.go` |
|
| `fieldAt` / `sliceElemAt` / `boxCopy` 等全部 unsafe 操作 | `unsafeptr.go` |
|
||||||
| `refx` 慢路径实现 | `rfx_reflect.go` |
|
| `refx` 慢路径实现 | `rfx_reflect.go` |
|
||||||
|
|
||||||
改动这几个函数时记得同步这两张图。SVG 源文件就在本目录下,
|
改动这几个函数时记得同步这三张图。SVG 源文件就在本目录下,
|
||||||
是手写的(没有用绘图工具),直接编辑坐标即可。
|
是手写的(没有用绘图工具),直接编辑坐标即可。
|
||||||
|
|
||||||
实测环境:Apple M4 Pro / darwin-arm64 / go1.25.5,数字取多轮中位数。
|
实测环境:Apple M4 Pro / darwin-arm64 / go1.25.5,数字取多轮中位数。
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package reflux
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// rfx 必须保持在 24 字节: 它在 walk 循环里全程按值传递, 变大会直接拖慢热路径,
|
||||||
|
// 也会让 boxed() 的那次堆分配跨到更大的 size class。
|
||||||
|
// docs/flow.md 里的说明依赖这个数字。
|
||||||
|
func TestRfxStaysSmall(t *testing.T) {
|
||||||
|
const want = 24
|
||||||
|
if got := unsafe.Sizeof(rfx{}); got != want {
|
||||||
|
t.Fatalf("rfx 大小 = %d 字节, 期望 %d —— 加字段前请确认是否真的值得", got, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user