Release 产物 1.74MB → 796KB(去掉 <filesystem> 与 zlib)+ .mat 加载进度不再停住

一、体积膨胀的真因(用 -Wl,-Map 逐对象/逐节对比上一提交定位,只有两个)
1) <filesystem>(致命,约 +900KB):MatH5::Open 用 std::filesystem::file_size 取文件大小,
   MinGW 的 libstdc++ 因此链入 fs_path/fs_dir/fs_ops/c++locale/codecvt/collate_members/
   cow-wstring-inst/cxx11-wlocale-inst/fstream-inst/cp-demangle 整套 locale/iostream 机器
   (归档成员 415→538);还把 64KB 静态表从 .bss 挤成 .data(1.8KB→73.8KB)。
   修法:改用 _wfopen_s + _fseeki64/_ftelli64 取大小。
2) -lz(约 46KB):只需要"解开 HDF5 分块"。修法:MatH5 内置紧凑 inflate
   (RFC1950/1951:固定/动态 Huffman、存储块、长度-距离回抄),Makefile 与探针脚本移除 -lz。
顺带把 UploadHaloLayer 里 64KB 的预乘 alpha 查表换成 (v*a+128)*257>>16(无除法、无表)。

二、压到 800KB 以下的两项无损措施
1) 图标资源无损重编码:ico 里 64/48/32/16 四帧原为未压缩 32bpp DIB(合计 32KB),
   用自写工具(GDI+ 编码)改为 PNG 帧(ICO 的 PNG 条目 Vista+ 全支持,本文件本就有 256 PNG 帧):
   43,334 → 17,404 字节。像素逐点校验:差异 ≤85 像素、最大分量差 ≤3(编码取整),肉眼不可辨;
   并从**真实产物** LoadImageW 逐尺寸(16/32/48/64)验证可加载。
2) RELEASE_OPT 由 -Os 改为 -Oz(省约 6KB);实测动画帧 p50 7.73ms(-Os) vs 7.77ms(-Oz),
   无可辨识差异。
放弃的手段:-ffunction-sections/-fdata-sections + --gc-sections(反而 +9KB)、LTO(工具链 ICE)、
-fno-asynchronous-unwind-tables(链接失败)、-fno-exceptions(会破坏 catch (bad_alloc))。
结果:Build/Release/Target.exe = 795,648 字节。

三、.mat 加载进度不更新(已加载大小 / 总大小 / 百分比都停在 0)
真因:LoadMatSourceGroups 只在结束时回调一次 ratio=1.0,且从不设置 hasBytes;
读 42MB 文件与解码 19 个样本流(解压+拷贝,占大头)期间进度恒为 0。
修法:
- MatHdf5::Open 新增 OpenProgress(bytesRead,totalBytes),按 4MB 分块读取并回报;
- MatLoader 用 ProgressReporter 把进度拆成 0.00–0.55 读文件(hasBytes=true)与
  0.55–0.98 解码样本流(每解完一个流推进 done/total),末段 1.0;
- 取消与出错分开:取消时清空 error,不弹错误框。
实测:回调 29 次、字节数变化 11 次、比例单调、终值 1.000、bytes=42,386,344/42,386,344
(= 文件大小);Application 侧断言 m_loadingBytesRead == m_loadingTotalBytes == 文件大小。

四、验证(全部离屏,不占用鼠标/键盘/屏幕)
- matcheck 21/21(新增进度断言)、matui 15/15(新增图标 + 加载字节断言);
- 回归 verify 49/49、panelanim 158/158、gridanim 93/93、hoverdrag 20/20、lanescroll 23/23、
  datacheck 69/69、edgecheck 46/46、errcheck 19/19、halocheck 16/16、panflash 19/19、
  panelstutter 8/8、stress 6/6;
- make release / make debug 零警告。

文档:Documents/agent_mem.md 追加本轮小节(含 5 条新规则:MinGW 下 <filesystem> 是体积炸弹、
链一整条库前先算账、膨胀要看节表而非 .o 大小、图标可无损重编码并用产物校验、
长解析必须按阶段回报进度且 hasBytes 只在有字节时置真)。
This commit is contained in:
DeepSeek-V4.1-Flash committed 2026-10-19 02:37:52 +08:00
1 parent 5b24dacee0
commit f44f8021e3
8 files changed
+623 -46

No files matched your search

+92 -3
View File
@@ -143,10 +143,44 @@ namespace jdi
source.blocks.push_back(std::move(block));
}
/** @brief 加载进度上报器(`.mat` 解析拆成“读文件”与“解码每个样本流”两段)。 */
struct ProgressReporter
{
const LoadProgress* sink = nullptr; /**< 目标回调(可空)。 */
double low = 0.0; /**< 本阶段占的进度区间下界。 */
double high = 1.0; /**< 本阶段占的进度区间上界。 */
std::uint64_t read = 0; /**< 已读字节。 */
std::uint64_t total = 0; /**< 总字节。 */
/** @brief 上报一个 0-1 的阶段内进度(向宿主传递字节数与百分比)。 */
bool Report(double fraction) const
{
if (sink == nullptr || !(*sink))
{
return true;
}
if (fraction < 0.0)
{
fraction = 0.0;
}
if (fraction > 1.0)
{
fraction = 1.0;
}
LoadProgressInfo info;
info.ratio = low + (high - low) * fraction;
// 总字节已知时一并上报(遮罩层据此显示“已加载 / 总大小”)。
info.hasBytes = (total > 0);
info.bytesRead = (total > 0) ? read : 0;
info.totalBytes = total;
return (*sink)(info);
}
};
/** @brief SDI(`SimulationData.Dataset`)结构的分组读取。 */
bool LoadSdi(const MatHdf5& file, std::uint64_t refs, std::uint64_t sigstream,
const std::wstring& stem, std::vector<LoadedSourceGroup>& groups,
std::wstring& error)
std::wstring& error, const ProgressReporter& reporter)
{
// 1) 枚举 `#refs#` 里的全部元素。
std::map<std::uint64_t, SdiElement> elements;
@@ -229,6 +263,16 @@ namespace jdi
std::map<std::wstring, std::size_t> groupIndex;
std::vector<std::vector<double>> streams; // 流编号 → 样本
std::vector<bool> streamCached;
// 进度:`#sigstream#` 的总流数,用来把"解码样本流"这段映射成阶段内 0-1。
std::size_t streamTotal = 0;
for (const auto& member : file.Members(sigstream))
{
if (!member.first.empty() && member.first[0] != '#')
{
++streamTotal;
}
}
std::size_t streamDone = 0;
const auto streamFor = [&](int index) -> const std::vector<double>*
{
if (index < 0)
@@ -249,6 +293,14 @@ namespace jdi
streams[static_cast<std::size_t>(index)].clear();
}
streamCached[static_cast<std::size_t>(index)] = true;
// ⚠️ 必须在这里回报进度:读一个 42MB 的 `.mat` 时,"解码样本流"才是真正的
// 耗时大头(每个流都要解压 + 拷贝);不报进度的话界面会停在原地等
//(用户 2026-10-19 报告:加载 `.mat` 时已加载大小与百分比都不更新)。
++streamDone;
reporter.Report(streamTotal > 0
? static_cast<double>(streamDone) /
static_cast<double>(streamTotal)
: 1.0);
}
return streams[static_cast<std::size_t>(index)].empty()
? nullptr
@@ -421,17 +473,51 @@ namespace jdi
error.clear();
groups.clear();
MatHdf5 file;
if (!file.Open(filePath, error))
// ------------------------------------------------------------------
// 进度拆成三段(用户 2026-10-19 报告:加载 `.mat` 时已加载大小与百分比都不更新,
// 界面只是原地等待):
// 0.00 – 0.55 读文件字节(遮罩层显示"已加载 / 总大小")
// 0.55 – 0.98 解码样本流(每个流解码后推进,含解压与拷贝)
// 0.98 – 1.00 收尾
// 读文件阶段把 `hasBytes` 置真;之后字节数已满,只推进百分比。
// ------------------------------------------------------------------
ProgressReporter readReporter;
readReporter.sink = &progress;
readReporter.low = 0.0;
readReporter.high = 0.55;
const MatHdf5::OpenProgress openProgress =
[&readReporter](std::uint64_t bytesRead, std::uint64_t totalBytes) -> bool
{
readReporter.read = bytesRead;
readReporter.total = totalBytes;
return readReporter.Report(totalBytes > 0 ? static_cast<double>(bytesRead) /
static_cast<double>(totalBytes)
: 0.0);
};
if (!file.Open(filePath, error, openProgress))
{
return false; // 用户取消时 `error` 为空
}
ProgressReporter parseReporter;
parseReporter.sink = &progress;
parseReporter.low = 0.55;
parseReporter.high = 0.98;
parseReporter.read = readReporter.total;
parseReporter.total = 0; // 字节已读完:本阶段只报百分比
if (!parseReporter.Report(0.0))
{
error.clear();
return false;
}
const std::wstring stem = StemOf(filePath);
const std::uint64_t refs = file.Member(file.Root(), "#refs#");
const std::uint64_t sigstream = file.Member(file.Root(), "#sigstream#");
if (refs != 0 && sigstream != 0)
{
if (!LoadSdi(file, refs, sigstream, stem, groups, error))
if (!LoadSdi(file, refs, sigstream, stem, groups, error, parseReporter))
{
return false;
}
@@ -461,6 +547,9 @@ namespace jdi
{
LoadProgressInfo info;
info.ratio = 1.0;
info.hasBytes = (readReporter.total > 0);
info.bytesRead = readReporter.total;
info.totalBytes = readReporter.total;
progress(info);
}
return true;