一、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。
14 KiB
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)
改动
Toolbar::BuildBoxes里唯一的dynamic_cast是多余强转(Button::widget本来就是UiButtonWidget*)。改为直接遍历m_buttons(与m_root->Children()同源同序)。- 仅关 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换成自己的处理函数,行为等效。 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)
改动
- 新增
RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables,只加在 Release 的两条 编译规则上。-fno-exceptions下绝大多数函数不再需要.pdata/.xdata。- 代价(已评估接受):Release 崩溃时 WER 拿不到可靠调用栈;不打印栈回溯的程序 功能不受影响,Debug 构建保留完整展开表。
-fno-unwind-tables(全关)会链接失败,不采用。
- 修正真实缺陷:
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)
改动
.gitignore:删除整目录忽略的Tools/,改为Tools/**/Build/(只忽略各工具产物)。- 新增
Tools/Makefile:make/make UnitTests/make OffscreenChecks/make clean/make help; 工具链接本体../Build/Release/*.o(排除Main.o),-MMD -MP跟踪头文件依赖。 - 新增
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 均回退) |