Files
Just-DataInspector/Documents/Code_Review_Progress.md
T
DeepSeek V4.1 Flash 5c48bf922e review(follow-ups): 落地上轮建议并给出实测结论(#2/#4 回退、#3 不重写)
一、Tools 完善
* OffscreenChecks `edgestats`:改为自动挑"最有起伏"的信号。旧实现固定取
  `signals[0]`,而 MF4 的第一条信号是常量(6000)⇒ 三个阈值的断言退化成
  "0 == 0" 的空转。现在扫描前 200 条挑 `max-min` 最大者,SCATTER_TEST 上
  三个阈值分别得到 (20,19)/(20,19)/(20,19),与独立参考实现逐位相同。
* UnitTests 新增 `stdio_mingw_vs_ucrt_formats`:把 MinGW ANSI stdio 与系统
  UCRT 的 printf 在**本程序实际用到的全部格式**(`%.*f` / `%.6g` / `%g` /
  `%.4e` / `%.2f %ls` / `%llu` / `%lld` / `%02X` / `%.2f%%`)上做逐字符比对,
  13.6 万次调用 **0 处差异**(覆盖 1e-40~1e40)。它是"把 swprintf 让给 UCRT"
  这条体积优化的验收标准。
* Tools/README.md:同步新增用例与断言数量(219 项 / 18 用例)。

二、第 33 轮报告 §10 后续建议的结论(详细数字见 Progress 第 34 轮)

1. **`EdgeScanFor` 状态查找哈希化:实测后回退。**
   实现方式为 `deque` + `unordered_multimap<keyHash, EdgeScanState*>`(桶内仍
   逐字段比对,碰撞安全;新建/淘汰/定向作废/清空四处同步索引),功能与像素
   验证全绿。但 198 个状态的整轮查询只从 0.097 降到 0.073 us/query(一轮省
   ≈4.8 us),而产物体积 +5,632 B。⇒ 回退。
   同时修正报告的量级:原估"288 信号 ≈0.3ms/帧",实测 ≈20us/帧。

2. **`EdgePanelLayoutFor` 表驱动化:不做。** 实测 1.9~2.5 us/次(每帧约 14 次
   ≈30us),不是热点;第 33 轮已消除"按种类分派"的重复,剩余 957 行是几何算式
   本身,无法表驱动。本轮改为交付"让这类重写可验证"的手段:
   `-run panels -mode record/compare` 逐面板比对几何 + 像素哈希。

3. **`wcstod` 自写替身:实测 0 字节收益。** 只加
   `-D__USE_MINGW_ANSI_STDIO=0` 全量重编后产物仍是 788,480 B、`mingw_wpformat.o`
   仍被链接。原因是预编译 libstdc++ 自己引用 `__mingw_swprintf`:只要还调用
   `std::to_wstring` / `std::to_string`(全仓约 80 处,若干处实参是浮点),
   gdtoa 必然被链入。⇒ 回退,并在 Makefile 留下说明防止重复尝试。

三、文档
* `Documents/Code_Review_Plan.md` 新增 §六(第 34 轮节点与验收标准)。
* `Documents/Code_Review_Progress.md` 新增第 34 轮逐节点记录(含全部实测数字)。
* `Documents/Code_Review_Report.md` 新增 §11(结论表 + 两处量级修正 + 更新的后续建议)。
* `Documents/agent_mem.md` 新增第 34 轮规则:Tools 约定、两个构建陷阱、
  两条"别再重复做"的实验结论。

验证:
* `make release`(删 .o 全量)零 error / 零 warning,产物 **788,480 B**(与改动前一致)。
* `make -C Tools` 零 error / 零 warning。
* `UnitTests.exe` → checks=219 failed=0 cases=18。
* `OffscreenChecks.exe -all` → 5/5 检查 PASS,checks=71 failed=0。
2026-11-04 11:52:10 +08:00

244 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Code Review 进度(分支 `Code_Review`)
> 每个节点完成后追加:节点号、改动摘要、验证方式、实测数字。
> 计划见 `Documents/Code_Review_Plan.md`;最终报告见 `Documents/Code_Review_Report.md`。
## 基线(起点 `764e5be`)
| 项 | 数值 |
| --- | --- |
| Release 产物 | **890,880 B** |
| `.text` | 753,136 B |
| `.rdata` | 36,936 B |
| `.pdata` | 30,420 B |
| `.xdata` | 29,496 B |
| `.rsrc` | 20,656 B |
| `.idata` | 12,856 B |
| Debug 产物 | 1,801,617 B |
| 源码规模 | 约 5.6 万行 |
关键发现(详见计划 §一):
* `dynamic_cast`(`Toolbar.cpp:370`)使最小复现程序从 39,936 B 涨到 115,200 B
⇒ **+75,264 B**;map 中 `cp-demangle.o` 占 **56,320 B**。
* 每个 `EdgePanelKind` 在源码中出现 21–26 次(6 种面板 ⇒ 约 140 处手写分支)。
---
## 节点记录
(下面按完成顺序追加)
### P1 移除 `dynamic_cast` + 关闭 RTTI —— ✅ 完成(提交 `5de3763`)
**改动**
1. `Toolbar::BuildBoxes` 里唯一的 `dynamic_cast` 是**多余强转**(`Button::widget` 本来就是
`UiButtonWidget*`)。改为直接遍历 `m_buttons`(与 `m_root->Children()` 同源同序)。
2. 仅关 RTTI **不够**:`cp-demangle.o` 仍在链接里。真凶是 libstdc++ 的 `vterminate.o`
(引用 `__cxa_demangle`),它被 `eh_terminate.o` 的 `std::terminate` 拉入。
⇒ 在 `Utilities/Src/MemProbe.cpp` 里**自行定义** `__gnu_cxx::__verbose_terminate_handler()`,
归档成员 `vterminate.o` 不再被拉入,demangler 整体消失。
应用启动后本来就会 `std::set_terminate` 换成自己的处理函数,行为等效。
3. `Makefile` 新增 `NO_RTTI=1`(默认开)→ `CXXFLAGS += -fno-rtti`。
**实测**
| 指标 | 改前 | 改后 | 差 |
| --- | --- | --- | --- |
| Release | 890,880 B | **829,440 B** | **−61,440 B** |
| `.text` | 753,136 B | 703,616 B | −49,520 B |
| `.rdata` | 36,936 B | 26,616 B | −10,320 B |
| Debug | 1,801,617 B | 1,734,578 B | −67,039 B |
**验证**:release + debug **全量重建**(删 `*.o`/`*.d`)零 error 零 warning;
map 中 `cp-demangle.o` 已无 `.text` 段分配。
### P2 Release 关闭异步展开表 + 修正 Debug 构建参数 —— ✅ 完成(提交 `94fc1c4`)
**改动**
1. 新增 `RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables`,**只**加在 Release 的两条
编译规则上。`-fno-exceptions` 下绝大多数函数不再需要 `.pdata`/`.xdata`。
* 代价(已评估接受):Release 崩溃时 WER 拿不到可靠调用栈;不打印栈回溯的程序
功能不受影响,Debug 构建保留完整展开表。
* `-fno-unwind-tables`(全关)会链接失败,不采用。
2. **修正真实缺陷**:`DEBUG_OPT = -O0` 定义了却**从未被使用** —— Debug 的两条编译规则
误用 `$(RELEASE_OPT)`(`-Oz`),且全程没有 `-g`。所谓 "Debug 构建" 其实只是
"不 strip 的 Release",根本无法调试。现改用 `$(DEBUG_OPT) -g`,链接也加 `-g`。
**实测**
| 指标 | 改前 | 改后 |
| --- | --- | --- |
| Release | 829,440 B | **793,088 B**(−36,352) |
| `.pdata` | 30,420 B | 12,684 B |
| `.xdata` | 28,904 B | 10,156 B |
| Debug | 1,734,578 B | 5,992,064 B(真实 `-O0 -g`,属预期) |
**验证**:release 与 debug 均全量重建,零 error 零 warning(`-O0` 下也没有新警告)。
### P7 面板状态/滚动量收敛为唯一派发点 —— ✅ 完成(提交 `7a3fefb`)
**问题(屎山核心之一)**:`Lane` 里 7 种实验性功能各有 1 份 `LanePanelState` + 1 个滚动偏移
(14 个字段)。旧实现在**每一处**“按种类取用”的地方各写一遍嵌套三元式或 `switch`:
`EdgePanelLayoutFor` 5 条 7 层嵌套三元式;标题栏拖拽、✕ 关闭、滚轮、滚动条、错开序数各写一遍。
新增面板要改 20+ 处,漏一处就是「拖不动 / 关不掉 / 滚轮滚不到底」
(`MARK`、`RESAMPLE`、`CLIP` 均真实踩过)。
**改动**:`PlotViewInternal.hpp` 新增唯一派发点
`LanePanelEnabled` / `LanePanelStateOf` / `LanePanelScrollOf`(可写 + 只读)。
这些 `switch` **穷举 7 种且不写 `default:`** ⇒ `-Wswitch` 在漏登记时编译报警
(已用最小程序验证 `-Wswitch` 确实会报)。
调用点:布局 5 条三元式 → 3 行;标题栏拖拽 7 分支 switch → 1 次写入;
滚轮/滚动条/✕ 关闭/折叠切换全部改为穷举无 `default`。
**验证**:release + debug 零 warning;产物 **793,088 B 不变**(等价变换,说明只是结构收敛)。
**未做(受验证条件限制)**:尚未把 `EdgePanelLayoutFor` 的 957 行分支整体改成
“每面板一行元数据表”。原因:没有可用的离屏像素对比工具(`.preflight` 已删除,
且崩溃会弹模态框),纯逻辑重写无法自证“像素完全一致”。已通过上面的派发收敛
消除最容易出错的一类重复,剩余重复属于“几何算式本身”,风险高于收益。
### P11 拆分 `ApplicationCommands.cpp` —— ✅ 完成(提交 `8437100`)
原文件 **2,165 行**,混杂六类职责。按职责**纯搬移**(零逻辑改动):
| 新文件 | 行数 | 内容 |
| --- | --- | --- |
| `ApplicationTabs.cpp` | 355 | 标签页新建/关闭/切换/重排、视图同步、窗口标题、标签栏回调、标签页右键菜单 |
| `ApplicationDerive.cpp` | 786 | 派生烘焙、重采样、选区截取(新建/就地)、克隆(含带调整) |
| `ApplicationMenus.cpp` | 409 | 绘图区右键菜单、菜单数值回写、主题切换、设置菜单、菜单结果派发 |
| `ApplicationCommands.cpp` | 642 | 分组/信号增删、移动与重排、文件对话框、工具栏动作、键盘鼠标分发、布局边界绘制 |
Makefile 按目录通配收集源文件,**无需登记新文件**。
**验证**:release 零 error 零 warning;产物 793,088 → 794,112 B(+1,024,LTO 内联边界
变化,< 0.13%)。
### P15 注释/文档校正 —— ✅ 完成(提交 `f139440`)
* `PlotView.hpp`:删除指向已删除代码的 `FormulaEnvelopeEntry` 注释块。
* `agent_mem.md` §2:`-Os` → `-Oz`;补体积开关(`NO_EXCEPTIONS` / `NO_RTTI` / `LTO` /
`RELEASE_ONLY_OPT`)与「改 Makefile 必须删 `.o` 全量重编」提醒;补 `msimg32`;
忽略列表补 `Temp/`、`.preflight/`、`Tools/`。
* `agent_mem.md` §3:目录结构按现状重写(删除不存在的 `CsvTable/`、`DataModel/`、
`PaintUtil/` 等旧条目;写明 `Components/Shared` 与 `Utilities` 的真实职责和依赖方向)。
* `agent_mem.md` 末尾新增「第 33 轮」记录。
### P16 最终验证与报告 —— ✅ 完成
* release(全量)+ debug(全量 `-O0 -g`)**零 error 零 warning**。
* 最终产物 **794,112 B**(起点 890,880 B,−96,768,−10.9%)。
* `cp-demangle.o` 已无 `.text` 段分配;`__gnu_cxx::__verbose_terminate_handler`
定义在本产物内(`0x140057ed1`);导入 DLL 无新增。
* 报告:`Documents/Code_Review_Report.md`。
### 未完成/搁置的节点(均已在报告中说明原因)
* **P8** `SceneContentHash` 混入收敛:无逐像素验证手段,合并会改变哈希值,风险不划算。
* **P9** `EdgePanelLayoutFor` 表驱动化:同上(957 行几何分支,无法自证像素一致)。
* **P10** 缓存五件套抽象:同上,且热路径复杂。
* **P12** `PlotView.hpp` 拆分:嵌套类型外移需改 70+ 处引用,收益仅为可读性。
* **P13/P14** UI 基础层与绘制边界核对:已做**依赖方向与引用统计**,未发现重叠或反向依赖,
因此不再拆分。
* **性能项**:未发现新的可复现热点;已记录 3 个"还能再快一点"的点(见报告 §6.2)。
---
## 第 34 轮(2026-11-04):验证基础设施 + §10 后续建议落地
### T1 验证工具入库 —— ✅ 完成(提交 `97e9163`)
**改动**
1. `.gitignore`:删除整目录忽略的 `Tools/`,改为 `Tools/**/Build/`(只忽略各工具产物)。
2. 新增 `Tools/Makefile`:`make` / `make UnitTests` / `make OffscreenChecks` / `make clean` / `make help`;
工具链接本体 `../Build/Release/*.o`(排除 `Main.o`),`-MMD -MP` 跟踪头文件依赖。
3. 新增 `Tools/UnitTests`(`Inc` + `Src` + `Build`)与 `Tools/OffscreenChecks`(同构)与 `Tools/README.md`。
**踩到并修掉的两个构建陷阱**
* 工具源码也加 `-flto` 会让 GCC 15.2.0 在链接阶段 `choose_baseaddr`(i386.cc:7447)**ICE**
⇒ 工具源码不带 `-flto`,只在链接参数里带 `-flto -flto-partition=one`。
* `$(foreach)` 生成的工具目标早于 `all` 出现 ⇒ make 把**第一个**显式目标当默认目标,
直接 `make` 只会构建 `UnitTests` ⇒ 显式 `.DEFAULT_GOAL := all`。
**实测**:`make -C Tools` 零 warning;两个产物分别在 `Tools/<Tool>/Build/<Tool>.exe`。
### T2 单元测试 —— ✅ 完成(219 条断言 / 18 个用例)
覆盖:`TryParseDouble` 与 libc `strtod` **逐位一致**(6 万条随机十进制串)、
输入接受性(尾随字符 / 十六进制浮点 / `inf` / `nan`)、`FormatDecimal*` 永不科学计数法、
`NiceStep` / `BuildTicks`、公式语法与语义、`FormulaProgram` 与 `EvaluateFormula` 逐位一致、
`ApplyDrawTransform` 顺序、`TransformValueRange`、滑动平均多阶等价性、
`DataSet::FormulaTransformFor` 值域与分组极值(逐点参考实现比对、命中同指针、样式变化失效)、
以及新增的 `stdio_mingw_vs_ucrt_formats`(见 T6)。
**过程中修正了 5 处"我以为的语义"**(都以源码为准,测试跟着产品改):
`9.999999999999999` 属 >15 位有效数字(允许 1 ULP);`1..2` 其实合法;
`FormulaIsIdentity` 不去空格;非法公式求值时 `EvaluateFormula` **原样返回采样值**(不是 NaN)。
### T3 离屏验证 —— ✅ 完成(5 个检查 / 71 条断言)
* `smoke`:初始化 + 加载 + 渲染 + 落盘;**同一状态两帧逐像素相同**(实测差异 0 像素)。
* `frames`:连续帧抓取(PNG + 区域 FNV 哈希 + 耗时),帧序列至少出现一次可见变化。
* `panels`:7 个面板全部展开后的几何不变式(必须被绘图区夹住)+ 像素基线 `-mode record/compare`。
* `edgestats`:边沿统计与工具内**独立朴素实现**逐位比对(阈值 2.4875/4.975/7.4625 →
20/19、20/19、20/19 完全一致;中心轴 1200/1179 也能完成)。
* `perf`:整帧中位数、`EdgePanelLayoutFor` 单次、198 个统计状态的整轮查询耗时。
**全程离屏**:窗口 `SW_HIDE`、"鼠标"用 `Application::HandleMouseMove()` 合成注入、
像素从后备缓冲区读取;进程用 `std::_Exit()` 收尾(避免单例析构晚于 `GdiplusShutdown`)。
### T4 `EdgeScanFor` 状态查找哈希化 —— ⛔ **实测后回退**(建议 #2)
* **改动前**:`mutable std::vector<EdgeScanState>` + 逐字段线性扫描。
* **改法**:容器改 `std::deque`(指针稳定)+ `mutable std::unordered_multimap<uint64_t, EdgeScanState*>`
按身份哈希索引,桶内仍逐字段比对(碰撞安全);新建 / 淘汰 / 定向作废 / 清空四处同步索引。
* **正确性**:`edgestats` 全绿(真实数据的计数与独立参考实现逐位相同),`panels` 像素基线不变。
* **性能(198 个状态,`MF4-Record.MF4`)**:整轮查询 `0.097 → 0.073 us/query`
(一轮 198 次查询省 ≈4.8 μs)。
* **体积代价**:`788,480 → 794,112 B`(**+5,632 B**)。
* **结论:回退**。5.6 KB 的体积换 ≈5 μs/帧(约占 5 ms 整帧的 0.1%)不成立 ——
这与 §6.2 原本的判断一致,本轮把它从"估计"变成了**实测**。
⚠️ 同时修正了 §6.2 的量级:报告原估"288 信号时 ≈0.3 ms/帧",实测 198 状态整轮 **≈20 μs/帧**,
差一个数量级(线性扫描的常数很小)。
### T5 `EdgePanelLayoutFor` 表驱动化 —— ⏸ 只交付"可验证手段",**未重写**
* 第 33 轮已把**按种类分派**收敛成唯一派发点(`LanePanelEnabled` / `LanePanelStateOf` /
`LanePanelScrollOf`,且 `switch` 不写 `default:` 让 `-Wswitch` 兜底)。
* 剩余的重复是 957 行**几何算式本身**(每个面板的内容行高、表头、按钮、滚动区各不相同),
无法用"每面板一行元数据"表达。
* 本轮实测该函数 **1.9~2.5 μs/次**(2000 轮 × 7 种),按每帧约 14 次调用计约 **30 μs/帧**,
不是热点。
* ⇒ 重写的收益是"可读性",代价是 957 行几何公式的可回归风险。
**本轮交付的是让这类重写变得可验证的工具**:`-run panels -mode record/compare`
会逐面板比对**几何 + 像素哈希**(7 个面板、含内容区/滚动条/按钮)。
### T6 `wcstod` 自写替身(省 gdtoa)—— ⛔ **实测 0 字节收益**(建议 #4)
* 报告 §1.3 的估计是"≈20 KB"。本轮先做**最小实验**而不是先写代码:
在 `Makefile` 里加 `-D__USE_MINGW_ANSI_STDIO=0`(把 `swprintf` 交给系统 UCRT),全量重编。
* **实测:产物仍然 788,480 B,一个字节都没变**;map 里 `mingw_wpformat.o` 依旧被链接。
* **原因(用 3 行最小程序确认)**:预编译的 **libstdc++ 自己引用 `__mingw_swprintf`** ——
只要工程里还调用 `std::to_wstring` / `std::to_string`(全仓约 80 处,其中若干处实参是浮点),
链接器就必须把 MinGW 的 printf 引擎(含 gdtoa)拉进来。
* ⇒ 真正的 33 KB 收益要以"替换全部 80 处 `std::to_*string`(含浮点格式语义)"为代价,
风险与收益不匹配,**不实施**。该实验已回退,只在 `Makefile` 里留下一段说明防止重复尝试。
* **顺带产出可复用的验收测试** `stdio_mingw_vs_ucrt_formats`:
同一批真实格式(`%.*f` / `%.6g` / `%g` / `%.4e` / `%.2f %ls` / `%llu` / `%lld` / `%02X` / `%.2f%%`)
× 13.6 万次调用做**逐字符比对**,**0 处差异**。将来若真做迁移,这条测试就是验收标准。
### 本轮验证汇总
| 项 | 结果 |
| --- | --- |
| `make release`(删 `.o` 全量) | 零 error、零 warning |
| `make -C Tools` | 零 error、零 warning |
| `Tools/UnitTests/Build/UnitTests.exe` | `checks=219 failed=0 cases=18` |
| `Tools/OffscreenChecks/Build/OffscreenChecks.exe -all` | 5 个检查全 PASS,`checks=71 failed=0` |
| Release 产物 | **788,480 B**(与第 33 轮末一致;T4/T6 均回退) |