Code Review :代码审查 + 产物体积 −12.2%(890,880 → 782,336 B)+ 面板结构收敛 + 离屏验证工具入库 #3
No files matched your search
@@ -174,3 +174,32 @@
|
|||||||
|
|
||||||
进度与每节点的实测数字见 `Documents/Code_Review_Progress.md`。
|
进度与每节点的实测数字见 `Documents/Code_Review_Progress.md`。
|
||||||
最终结论见 `Documents/Code_Review_Report.md`。
|
最终结论见 `Documents/Code_Review_Report.md`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、第 34 轮追加(2026-11-04):验证基础设施 + 后续建议落地
|
||||||
|
|
||||||
|
> 触发:§三/§10 之后剩下的三项建议(`EdgeScanFor` 哈希化、`EdgePanelLayoutFor` 表驱动化、
|
||||||
|
> `wcstod` 自写替身)都卡在**"没有可自证的验证手段"**上。
|
||||||
|
> 用户要求:先把验证工具做成仓库内的正式资产(`Tools/` 入库),再动被建议的代码。
|
||||||
|
|
||||||
|
### 6.1 节点与验收标准
|
||||||
|
|
||||||
|
| 节点 | 内容 | 验收标准 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **T1** | `Tools/` 解除忽略;新增 `Tools/Makefile`;两个工具目录 + `README.md` | `make -C Tools` 构建全部、`make UnitTests` 构建单个、`make clean` 只清工具产物;改头文件/增删源文件自动重编重链 |
|
||||||
|
| **T2** | `Tools/UnitTests`:数据层语义单元测试 | `result=PASS`,断言数 ≥ 200 |
|
||||||
|
| **T3** | `Tools/OffscreenChecks`:隐藏窗口渲染 + 帧序列 + 面板像素基线 + 边沿统计参考实现 + 性能 | 零失败;全程不显示窗口、不动真实鼠标/键盘 |
|
||||||
|
| **T4** | 建议 #2:`EdgeScanFor` 状态查找哈希化 | **先量后改**;性能收益必须大于体积代价,否则回退 |
|
||||||
|
| **T5** | 建议 #3:`EdgePanelLayoutFor` 表驱动化 | 面板像素基线 A/B **逐像素一致**才允许合入 |
|
||||||
|
| **T6** | 建议 #4:`wcstod` 自写替身(省 gdtoa 链) | 体积收益可测且数值语义逐位一致 |
|
||||||
|
|
||||||
|
### 6.2 本轮新增的硬约束
|
||||||
|
|
||||||
|
1. **工具不得把产物写进 `Tools/`**:截图 / 基线 / 报告一律写 `Temp/tools/`(已被忽略);
|
||||||
|
`Tools/*/Build/` 同样不入库(其它协作者本地 `make -C Tools` 重建)。
|
||||||
|
2. **工具与本体 ABI 必须一致**:`-fno-exceptions`、`-fno-rtti`、链接时 `-flto -flto-partition=one`;
|
||||||
|
工具源码自身**不**编成 LTO 字节码(GCC 15.2.0 会在 `choose_baseaddr` 处 ICE)。
|
||||||
|
3. **"先量后改"**:任何以性能 / 体积为名的改动,必须在同一台机器、同一次会话内给出前后数字;
|
||||||
|
收益低于噪声、或换来体积上涨的,一律回退,并把数字写进 `Code_Review_Report.md`。
|
||||||
|
4. **像素基线只做本地 A/B**:哈希与 DPI/字体/数据文件绑定,**不入库、不当黄金图**。
|
||||||
@@ -145,6 +145,99 @@ Makefile 按目录通配收集源文件,**无需登记新文件**。
|
|||||||
因此不再拆分。
|
因此不再拆分。
|
||||||
* **性能项**:未发现新的可复现热点;已记录 3 个"还能再快一点"的点(见报告 §6.2)。
|
* **性能项**:未发现新的可复现热点;已记录 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 均回退) |
|
||||||
@@ -323,3 +323,71 @@ Makefile 按目录通配收集源文件,**无需登记**。
|
|||||||
3. **`EdgePanelLayoutFor` 表驱动化**(需第 1 项就绪)。
|
3. **`EdgePanelLayoutFor` 表驱动化**(需第 1 项就绪)。
|
||||||
4. 若要继续压体积:`wcstod` 自写替身(≈20 KB,需逐位一致性验证)、
|
4. 若要继续压体积:`wcstod` 自写替身(≈20 KB,需逐位一致性验证)、
|
||||||
图标帧裁剪(观感优先,不建议)。
|
图标帧裁剪(观感优先,不建议)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 11. 第 34 轮(2026-11-04):§10 建议的落地结果
|
||||||
|
|
||||||
|
> 计划见 `Code_Review_Plan.md` §六,逐节点记录见 `Code_Review_Progress.md` 第 34 轮。
|
||||||
|
|
||||||
|
### 11.1 一句话结论
|
||||||
|
|
||||||
|
**§10 里唯一"必须做"的第一条(重建离屏验证手段)已完成并入库**(`Tools/`),
|
||||||
|
它让其余三条建议第一次变得**可测量、可自证**;而用这套手段一测,
|
||||||
|
**第 2、4 条建议的实际收益远小于报告当初的估计,第 3 条根本不是性能项** ——
|
||||||
|
三条都以"实测数据 + 回退"收尾,而不是靠猜测去改 957 行几何或 80 处字符串转换。
|
||||||
|
|
||||||
|
| §10 建议 | 结果 | 关键数字 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1. 重建离屏验证手段 | ✅ **完成并入库** | 单元测试 **219** 条断言 / 18 用例;离屏 **71** 条断言 / 5 个检查;全程 `SW_HIDE` + 合成消息 |
|
||||||
|
| 2. `EdgeScanFor` 状态查找哈希化 | ⛔ **实测后回退** | 查询 `0.097 → 0.073 us/次`(198 状态,整轮省 ≈4.8 μs);体积 **+5,632 B** ⇒ 不划算 |
|
||||||
|
| 3. `EdgePanelLayoutFor` 表驱动化 | ⏸ **不重写**(交付验证手段) | 实测 **1.9~2.5 μs/次**,每帧约 30 μs ⇒ 不是热点;剩余重复是几何算式本身 |
|
||||||
|
| 4. `wcstod` 自写替身(≈20 KB) | ⛔ **实测 0 字节** | 只切 stdio 实现**体积不变**;真收益需替换 80 处 `std::to_*string`(含浮点) |
|
||||||
|
|
||||||
|
### 11.2 验证基础设施(`Tools/`,已入库)
|
||||||
|
|
||||||
|
```text
|
||||||
|
Tools/UnitTests/ 纯数据层单元测试(不创建窗口)
|
||||||
|
Tools/OffscreenChecks/ 隐藏窗口离屏验证(抓帧 / 哈希 / 面板基线 / 参考统计 / 性能)
|
||||||
|
Tools/Makefile 统一构建入口:make / make <Tool> / make clean / make help
|
||||||
|
Tools/README.md 目录结构、作用、测试项、验收标准、A/B 用法、扩展步骤、已知限制
|
||||||
|
```
|
||||||
|
|
||||||
|
* 工具链接本体 `Build/Release/*.o`(排除 `Main.o`),因此**必须先构建本体**;
|
||||||
|
产物在 `Tools/<Tool>/Build/`,**不入库**;截图 / 基线 / 报告一律 `Temp/tools/`(已忽略)。
|
||||||
|
* `smoke` 检查里有一条硬判据:**同一状态连续两帧逐像素相同**(实测差异 0 像素),
|
||||||
|
这是所有像素 A/B 可信的前提。
|
||||||
|
* `edgestats` 用**独立的朴素逐点实现**当金标准,比对真实数据的上升/下降沿计数
|
||||||
|
(20/19、20/19、20/19 三个阈值全等)——它同时是 `EdgeScanFor` 任何重构的回归网。
|
||||||
|
|
||||||
|
### 11.3 对原报告两处量级的修正
|
||||||
|
|
||||||
|
| 位置 | 原表述 | 实测(本轮) |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| §6.2 第 1 行 | `EdgeScanFor` 线性扫描「~0.3 ms/帧 @288 信号」 | **≈20 μs/帧 @198 状态**(差一个数量级;线性扫描的常数很小) |
|
||||||
|
| §1.3 第 3 行 | 替换 `wcstod`「≈20 KB」 | **0 B**(只要还用 `std::to_*string`,MinGW printf 引擎 + gdtoa 必然被链入) |
|
||||||
|
|
||||||
|
> 这两条修正同样是"结论":它们避免后续接手者按错误量级去做无收益的重写。
|
||||||
|
|
||||||
|
### 11.4 体积现状
|
||||||
|
|
||||||
|
| 项 | 数值 |
|
||||||
|
| --- | --- |
|
||||||
|
| 第 33 轮末 | 794,112 B(本机实测 **788,480 B**,工具链差异) |
|
||||||
|
| 第 34 轮末 | **788,480 B**(T4 / T6 均回退,产品代码零改动) |
|
||||||
|
| 红线 | 1 MB 预警(`Makefile` 注释),距红线 **≈260 KB** |
|
||||||
|
|
||||||
|
### 11.5 后续建议(替代本报告 §10)
|
||||||
|
|
||||||
|
1. **保持 `Tools/` 常绿**:任何改动按 `Tools/README.md` §5 的"最小集"跑针对性检查;
|
||||||
|
改数据层必跑 `UnitTests`,改绘图/面板必跑 `-run smoke -run panels`(必要时 `-mode compare`)。
|
||||||
|
2. **性能类改动一律先量后改**:用 `-run perf` 取同一会话的前后数字;
|
||||||
|
绝对耗时受机器状态影响,比较时用**同一次运行内的相对值**。
|
||||||
|
3. **如果将来真要压体积**(优先级从高到低):
|
||||||
|
* 替换 `std::to_wstring` / `std::to_string`(≈80 处)→ 可整条去掉 gdtoa(≈33 KB),
|
||||||
|
验收标准已就绪(`stdio_mingw_vs_ucrt_formats`);注意其中若干处实参是浮点。
|
||||||
|
* 图标帧继续瘦身(观感优先,**不建议**)。
|
||||||
|
4. **如果将来要重写 `EdgePanelLayoutFor`**:先 `-run panels -mode record`,
|
||||||
|
重写后 `-mode compare`;只要有一处像素/几何差异就说明改坏了。
|
||||||
|
5. **不要重复这两次实验**:`EdgeScanFor` 哈希化(+5.6 KB / −5 μs 不划算)、
|
||||||
|
仅切 `__USE_MINGW_ANSI_STDIO`(0 B)。数字见 §11.1 与 `Code_Review_Progress.md` 第 34 轮。
|
||||||
@@ -10368,3 +10368,64 @@ release/debug **零警告**;Release `TargetCheck.exe` **890,880 B**(< 1 MB
|
|||||||
288 信号时每帧约 16 万次比较(~0.3ms 量级)。可改哈希索引,但收益有限、风险不小。
|
288 信号时每帧约 16 万次比较(~0.3ms 量级)。可改哈希索引,但收益有限、风险不小。
|
||||||
* `SceneContentHash` 里 6 段逐面板混入循环未合并(同上:无法验证"仅失效条件不变")。
|
* `SceneContentHash` 里 6 段逐面板混入循环未合并(同上:无法验证"仅失效条件不变")。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2026-11-04 · 第 34 轮(`Code_Review` 分支):验证工具入库 + §10 建议的实测结论
|
||||||
|
|
||||||
|
> 计划 `Documents/Code_Review_Plan.md` §六,逐节点 `Documents/Code_Review_Progress.md` 第 34 轮,
|
||||||
|
> 结论 `Documents/Code_Review_Report.md` §11。工具说明见 `Tools/README.md`(改工具必须同步它)。
|
||||||
|
|
||||||
|
### 1. ⭐ `Tools/` 已入库:验证能力变成"仓库资产"
|
||||||
|
|
||||||
|
* `.gitignore`:删掉整目录忽略的 `Tools/`,改为 `Tools/**/Build/`;
|
||||||
|
**工具产物与临时文件都不得入库**(截图/基线/报告写 `Temp/tools/`)。
|
||||||
|
* `Tools/Makefile`:`make` 全部、`make UnitTests`、`make OffscreenChecks`、`make clean`、`make help`。
|
||||||
|
工具链接本体 `../Build/Release/*.o`(排除 `Main.o`)⇒ **改完本体必须先 `make release`**。
|
||||||
|
* `Tools/UnitTests`:219 条断言 / 18 用例(解析逐位一致、格式化、公式、变换、滑动平均、公式缓存)。
|
||||||
|
* `Tools/OffscreenChecks`:`smoke` / `frames` / `panels` / `edgestats` / `perf`,共 71 条断言。
|
||||||
|
窗口 `SW_HIDE` + `Application::HandleMouseMove()` 合成鼠标 + 读后备缓冲区;**不占用用户屏幕/输入**。
|
||||||
|
|
||||||
|
### 2. ⚠️ 两个必须记住的构建陷阱
|
||||||
|
|
||||||
|
1. **工具源码不能加 `-flto`**:本体目标文件是 LTO 字节码,链接时必须
|
||||||
|
`-flto -flto-partition=one`;但把**工具源码**也交给 LTRANS 会让 GCC 15.2.0 在
|
||||||
|
`choose_baseaddr`(i386.cc:7447)**ICE**。⇒ 只在链接参数里带 LTO。
|
||||||
|
2. **`Tools/Makefile` 必须 `.DEFAULT_GOAL := all`**:用 `$(foreach)` 生成的目标会先于 `all`
|
||||||
|
出现,make 会把第一个显式目标当默认目标 ⇒ 否则直接 `make` 只构建第一个工具。
|
||||||
|
|
||||||
|
### 3. ⛔ `EdgeScanFor` 状态查找哈希化:**实测后回退**(别再做一次)
|
||||||
|
|
||||||
|
* 改法(已回退):`vector` → `deque`(指针稳定)+ `unordered_multimap<keyHash, EdgeScanState*>`,
|
||||||
|
桶内仍逐字段比对;新建/淘汰/定向作废/清空四处同步索引。功能与像素均验证通过。
|
||||||
|
* **数字**:198 个状态整轮查询 `0.097 → 0.073 us/query`(一轮省 ≈4.8 μs);
|
||||||
|
产物 **788,480 → 794,112 B(+5,632 B)**。
|
||||||
|
* **结论**:体积换 5 μs/帧不成立,已回退。
|
||||||
|
* ⚠️ **同时修正第 33 轮报告的量级错误**:那里估"288 信号时 ≈0.3 ms/帧",
|
||||||
|
实测 198 状态整轮只有 **≈20 μs/帧**(差一个数量级)。**不要再按 0.3ms 去优化这条路径。**
|
||||||
|
|
||||||
|
### 4. ⛔ 仅切 `__USE_MINGW_ANSI_STDIO=0`:**0 字节收益**(别再做一次)
|
||||||
|
|
||||||
|
* 实测:加 `-D__USE_MINGW_ANSI_STDIO=0` 全量重编后产物**仍是 788,480 B**,
|
||||||
|
map 里 `mingw_wpformat.o` + gdtoa 依旧在。
|
||||||
|
* 原因(3 行最小程序确认):**预编译 libstdc++ 自己引用 `__mingw_swprintf`** ——
|
||||||
|
只要还调用 `std::to_wstring` / `std::to_string`(全仓约 80 处,若干处实参是浮点),
|
||||||
|
gdtoa 必然被链入。真要省这 33 KB 必须替换全部 80 处,风险与收益不匹配。
|
||||||
|
* 该实验已回退,`Makefile` 里留了说明。**副产物**:`stdio_mingw_vs_ucrt_formats`
|
||||||
|
(13.6 万次 `%.*f`/`%.6g`/`%g`/`%.4e`/`%.2f %ls`/`%llu`/`%lld`/`%02X`/`%.2f%%` 逐字符比对,
|
||||||
|
0 差异)——将来真做迁移就是验收标准。
|
||||||
|
|
||||||
|
### 5. ⏸ `EdgePanelLayoutFor` 表驱动化:不是性能项,不重写
|
||||||
|
|
||||||
|
* 实测 **1.9~2.5 μs/次**(2000 轮 × 7 种面板),每帧约 14 次调用 ≈ 30 μs ⇒ 不是热点。
|
||||||
|
* 第 33 轮已消除"按种类分派"的重复(`LanePanelEnabled`/`LanePanelStateOf`/`LanePanelScrollOf`),
|
||||||
|
剩余 957 行的重复是**几何算式本身**,无法表驱动。
|
||||||
|
* 这类重写若要动,必须先 `-run panels -mode record`,改完 `-mode compare`(几何 + 像素哈希逐面板比对)。
|
||||||
|
|
||||||
|
### 6. 本轮验证口径
|
||||||
|
|
||||||
|
* `make release` 全量:零 error / 零 warning;产物 **788,480 B**。
|
||||||
|
* `make -C Tools`:零 error / 零 warning。
|
||||||
|
* `UnitTests.exe` → `checks=219 failed=0`;`OffscreenChecks.exe -all` → 5/5 检查 PASS、`checks=71 failed=0`。
|
||||||
|
* 单元测试写错过的 5 处"我以为的语义"(都已按产品语义修正):
|
||||||
|
`9.999999999999999`(>15 位有效数字允许 1 ULP)、`1..2` 合法、`FormulaIsIdentity` 不去空格、
|
||||||
|
非法公式 `EvaluateFormula` **原样返回采样值**(不是 NaN)。
|
||||||
@@ -209,6 +209,18 @@ endif
|
|||||||
# * ⚠️ 全关(`-fno-unwind-tables`)会链接失败(历史踩坑),**不要**打开。
|
# * ⚠️ 全关(`-fno-unwind-tables`)会链接失败(历史踩坑),**不要**打开。
|
||||||
RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables
|
RELEASE_ONLY_OPT = -fno-asynchronous-unwind-tables
|
||||||
|
|
||||||
|
# ---- 曾评估并放弃:把 `swprintf` 交给系统 UCRT(`-D__USE_MINGW_ANSI_STDIO=0`)----
|
||||||
|
# 动机:MinGW 的 ANSI stdio(`mingw_wpformat.o`)会链入 gdtoa 整套
|
||||||
|
#(`strtodg`/`gdtoa`/`gethex`/`hexnan`),map 实测 `.text` 合计 ≈33 KB。
|
||||||
|
# 实测结论(2026-11-04):**加这个宏体积一个字节都不减**。
|
||||||
|
# 原因:预编译的 libstdc++ 自己就引用 `__mingw_swprintf` ——
|
||||||
|
# 只要工程里还调用 `std::to_wstring` / `std::to_string`(全仓约 80 处),
|
||||||
|
# 链接器就必须把 MinGW 的 printf 引擎拉进来(用 3 行最小程序 `std::to_wstring(5)` 实测)。
|
||||||
|
# ⇒ 要真省下这 33 KB,得把 80 处 `std::to_*string` 全换成自写整数格式化,
|
||||||
|
# 其中若干处实参是浮点(语义会变),风险与收益不匹配。
|
||||||
|
# 保留证据:`Tools/UnitTests` 的 `stdio_mingw_vs_ucrt_formats`(13.6 万次调用逐字符比对,0 差异)
|
||||||
|
# 证明两套 printf 在这些格式上**完全等价**,将来若真要做迁移,那条测试就是验收标准。
|
||||||
|
|
||||||
# 丢弃未被引用的函数/数据(非 LTO 时还需要配合 -ffunction-sections 才有粒度)。
|
# 丢弃未被引用的函数/数据(非 LTO 时还需要配合 -ffunction-sections 才有粒度)。
|
||||||
GC_SECTIONS = -Wl,--gc-sections
|
GC_SECTIONS = -Wl,--gc-sections
|
||||||
|
|
||||||
|
|||||||
@@ -491,7 +491,29 @@ namespace jdi
|
|||||||
{
|
{
|
||||||
return CheckFailed();
|
return CheckFailed();
|
||||||
}
|
}
|
||||||
const SignalData& signal = group->signals[0];
|
|
||||||
|
// 挑一条"最有起伏"的信号:常量信号(只有 1 个不同取值)上任何阈值都得不到边沿,
|
||||||
|
// 断言会退化成"0 == 0"的空转(MF4 的第一条信号就是常量)。
|
||||||
|
const SignalData* chosen = nullptr;
|
||||||
|
double chosenSpan = -1.0;
|
||||||
|
for (const SignalData& candidate : group->signals)
|
||||||
|
{
|
||||||
|
double low = 0.0;
|
||||||
|
double high = 1.0;
|
||||||
|
FiniteRange(candidate.values, low, high);
|
||||||
|
const double span = high - low;
|
||||||
|
if (candidate.values.size() >= 4 && span > chosenSpan)
|
||||||
|
{
|
||||||
|
chosen = &candidate;
|
||||||
|
chosenSpan = span;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(chosen != nullptr && chosenSpan > 0.0, "found a non-constant signal");
|
||||||
|
if (chosen == nullptr || !(chosenSpan > 0.0))
|
||||||
|
{
|
||||||
|
return CheckFailed();
|
||||||
|
}
|
||||||
|
const SignalData& signal = *chosen;
|
||||||
const TimeAxis& axis = group->AxisOf(signal);
|
const TimeAxis& axis = group->AxisOf(signal);
|
||||||
const SignalStyle& style = signal.style;
|
const SignalStyle& style = signal.style;
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -124,11 +124,12 @@ SUMMARY checks=<n> failed=<m> cases=<k> result=<PASS|FAIL>
|
|||||||
| `filter_moving_average_equivalence` | 1 阶 ≡ 单次、2 阶 ≡ 链式、常数信号不变 | 均值滤波多阶 |
|
| `filter_moving_average_equivalence` | 1 阶 ≡ 单次、2 阶 ≡ 链式、常数信号不变 | 均值滤波多阶 |
|
||||||
| `dataset_group_access` | `AddGroup` / `GroupAt` / `Style(ref)` 读写 | 数据层入口 |
|
| `dataset_group_access` | `AddGroup` / `GroupAt` / `Style(ref)` 读写 | 数据层入口 |
|
||||||
| `dataset_formula_transform_cache` | 值域 + 逐分组极值 vs 逐点参考**逐位相同**;命中同指针;样式变化失效 | 第 32 轮核心缓存的正确性 |
|
| `dataset_formula_transform_cache` | 值域 + 逐分组极值 vs 逐点参考**逐位相同**;命中同指针;样式变化失效 | 第 32 轮核心缓存的正确性 |
|
||||||
|
| `stdio_mingw_vs_ucrt_formats` | MinGW ANSI stdio 与系统 UCRT 的 printf 在**本程序用到的全部格式**上逐字符比对(13.6 万次调用) | 评估“把 `swprintf` 交给 UCRT”是否可行(当前 0 差异);将来真做迁移就是验收标准 |
|
||||||
|
|
||||||
### 3.2 验收标准
|
### 3.2 验收标准
|
||||||
|
|
||||||
* **零失败**(`result=PASS`)才算通过;
|
* **零失败**(`result=PASS`)才算通过;
|
||||||
* 断言数量只增不减(当前 **218** 项):删断言必须说明理由;
|
* 断言数量只增不减(当前 **219** 项 / 18 个用例):删断言必须说明理由;
|
||||||
* 任何"为了让它过"而放宽的断言,必须在代码注释里写清依据。
|
* 任何"为了让它过"而放宽的断言,必须在代码注释里写清依据。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -13,12 +13,17 @@
|
|||||||
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstdio>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <cwchar>
|
||||||
#include <random>
|
#include <random>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <wchar.h>
|
||||||
|
|
||||||
#include "UnitTestFramework.hpp"
|
#include "UnitTestFramework.hpp"
|
||||||
#include "Utf8Util.hpp"
|
#include "Utf8Util.hpp"
|
||||||
|
|
||||||
@@ -304,3 +309,128 @@ JDI_TEST(ticks_nice_step_and_build_ticks)
|
|||||||
JDI_CHECK_NEAR(ticks.back(), 10.0, 0.0);
|
JDI_CHECK_NEAR(ticks.back(), 10.0, 0.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief MinGW ANSI stdio 与 UCRT printf 在**本程序实际使用的格式**上是否逐字符一致。
|
||||||
|
*
|
||||||
|
* 用途:评估"把 `swprintf` 让给系统 UCRT 实现(`__USE_MINGW_ANSI_STDIO=0`)"
|
||||||
|
* 这条体积优化是否可行 —— 若成立可以省掉整个 `mingw_wpformat` + `gdtoa` 链(≈33KB)。
|
||||||
|
* 判据极其严格:**逐字符相同**,一个字符的差异都不接受(显示文本是产品语义)。
|
||||||
|
*/
|
||||||
|
JDI_TEST(stdio_mingw_vs_ucrt_formats)
|
||||||
|
{
|
||||||
|
std::mt19937 rng(20261107u);
|
||||||
|
std::uniform_real_distribution<double> distribution(-1.0e7, 1.0e7);
|
||||||
|
std::uniform_real_distribution<double> magnitude(-40.0, 40.0);
|
||||||
|
|
||||||
|
int mismatches = 0;
|
||||||
|
int compared = 0;
|
||||||
|
static const int decimalsList[] = {0, 1, 2, 3, 4, 6, 8, 12};
|
||||||
|
|
||||||
|
for (int iteration = 0; iteration < 8000; ++iteration)
|
||||||
|
{
|
||||||
|
double value = distribution(rng);
|
||||||
|
// 覆盖极小 / 极大 / 整数量级,以及 FormatDecimal 会遇到的量级
|
||||||
|
if ((iteration % 7) == 0)
|
||||||
|
{
|
||||||
|
value = std::pow(10.0, magnitude(rng)) * ((iteration % 2 == 0) ? 1.0 : -1.0);
|
||||||
|
}
|
||||||
|
if ((iteration % 11) == 0)
|
||||||
|
{
|
||||||
|
value = static_cast<double>(iteration);
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t ours[128] = {};
|
||||||
|
wchar_t theirs[128] = {};
|
||||||
|
|
||||||
|
for (const int decimals : decimalsList)
|
||||||
|
{
|
||||||
|
swprintf(ours, 128, L"%.*f", decimals, value);
|
||||||
|
_snwprintf(theirs, 128, L"%.*f", decimals, value);
|
||||||
|
++compared;
|
||||||
|
if (wcscmp(ours, theirs) != 0)
|
||||||
|
{
|
||||||
|
if (mismatches < 5)
|
||||||
|
{
|
||||||
|
std::printf(" [INFO] %%.%df value=%.17g ours=\"%ls\" ucrt=\"%ls\"\n", decimals, value, ours,
|
||||||
|
theirs);
|
||||||
|
}
|
||||||
|
++mismatches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct WideFormatProbe
|
||||||
|
{
|
||||||
|
const wchar_t* format;
|
||||||
|
};
|
||||||
|
static const WideFormatProbe probes[] = {{L"%.6g"}, {L"%.4e"}, {L"%.2f"}, {L"%g"}};
|
||||||
|
for (const WideFormatProbe& probe : probes)
|
||||||
|
{
|
||||||
|
swprintf(ours, 128, probe.format, value);
|
||||||
|
_snwprintf(theirs, 128, probe.format, value);
|
||||||
|
++compared;
|
||||||
|
if (wcscmp(ours, theirs) != 0)
|
||||||
|
{
|
||||||
|
if (mismatches < 5)
|
||||||
|
{
|
||||||
|
std::printf(" [INFO] \"%ls\" value=%.17g ours=\"%ls\" ucrt=\"%ls\"\n", probe.format, value,
|
||||||
|
ours, theirs);
|
||||||
|
}
|
||||||
|
++mismatches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非浮点格式(颜色十六进制 / 计数 / 百分比尾缀)
|
||||||
|
const unsigned long long counter = static_cast<unsigned long long>(iteration) * 7919ull;
|
||||||
|
swprintf(ours, 128, L"%llu", counter);
|
||||||
|
_snwprintf(theirs, 128, L"%llu", counter);
|
||||||
|
++compared;
|
||||||
|
if (wcscmp(ours, theirs) != 0)
|
||||||
|
{
|
||||||
|
++mismatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
swprintf(ours, 128, L"%02X%02X%02X", iteration & 0xFF, (iteration >> 3) & 0xFF, (iteration >> 6) & 0xFF);
|
||||||
|
_snwprintf(theirs, 128, L"%02X%02X%02X", iteration & 0xFF, (iteration >> 3) & 0xFF,
|
||||||
|
(iteration >> 6) & 0xFF);
|
||||||
|
++compared;
|
||||||
|
if (wcscmp(ours, theirs) != 0)
|
||||||
|
{
|
||||||
|
++mismatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
swprintf(ours, 128, L"%.2f%%", value / 1000.0);
|
||||||
|
_snwprintf(theirs, 128, L"%.2f%%", value / 1000.0);
|
||||||
|
++compared;
|
||||||
|
if (wcscmp(ours, theirs) != 0)
|
||||||
|
{
|
||||||
|
++mismatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `%ls`:`FormatByteSize` 的 `%.2f %ls` 依赖"宽格式串里的 %ls = 宽字符串"。
|
||||||
|
// ⚠️ MSVC/UCRT 的宽 printf 里 `%s` 也表示**宽**字符串,与 MinGW ANSI(`%s`=窄)相反,
|
||||||
|
// 因此全仓禁止在宽格式串里写 `%s`(已有注释约束),这里顺带把 `%ls` 也钉住。
|
||||||
|
swprintf(ours, 128, L"%.2f %ls", value / 1024.0, L"MB");
|
||||||
|
_snwprintf(theirs, 128, L"%.2f %ls", value / 1024.0, L"MB");
|
||||||
|
++compared;
|
||||||
|
if (wcscmp(ours, theirs) != 0)
|
||||||
|
{
|
||||||
|
if (mismatches < 5)
|
||||||
|
{
|
||||||
|
std::printf(" [INFO] \"%%.2f %%ls\" ours=\"%ls\" ucrt=\"%ls\"\n", ours, theirs);
|
||||||
|
}
|
||||||
|
++mismatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
swprintf(ours, 128, L"%lld", static_cast<long long>(iteration) * -7919ll);
|
||||||
|
_snwprintf(theirs, 128, L"%lld", static_cast<long long>(iteration) * -7919ll);
|
||||||
|
++compared;
|
||||||
|
if (wcscmp(ours, theirs) != 0)
|
||||||
|
{
|
||||||
|
++mismatches;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::printf(" [INFO] compared %d format calls, %d mismatches\n", compared, mismatches);
|
||||||
|
JDI_CHECK_EQ(mismatches, 0);
|
||||||
|
}
|
||||||
Reference in new issue
Block a user