原生结果series通过字节索引直传,C端使用Ryu精确回读编码和64 KiB批量写出;网页采用Float64缓存和CSV工作线程,减少结果处理与保存等待。 补充八路AME曲线核查、全流程分阶段计时、独立编码基准和复现工具,固定后续优化采用修正八路及rtol=1e-8。C写出1.1808→0.1638 s,点击到可查看8.0100→6.9756 s。 验证:最终10项编码专项、29项相关后端回归通过;8份原生结果逐位一致,16次网页结果/CSV/刷新恢复通过。前端构建及缓存/CSV专项在本轮结果处理工作中通过。环境、原始大结果与临时构建不纳入Git。
159 lines
16 KiB
Markdown
159 lines
16 KiB
Markdown
# 八路结果处理、浏览器保存与 CSV 优化(2026-09-11)
|
||
|
||
本轮针对“点击运行→结果可查看”“点击运行→浏览器保存完成”“CSV 下载保存”三个用户关注的时间点实施优化。基线为 `808c484`,主案例仍是 [修正后的八路工程](../../tests/data/test-mql-8-corrected.json),未退回四路。此前各阶段成本见 [八路评估报告](八路模型计算效率与网页阶段计时-2026-09-11.md)。
|
||
|
||
## 实现
|
||
|
||
### C 数值 JSON 直接进入 HTTP 结果流
|
||
|
||
C 继续生成原有 `result.json`,数值仍使用 `%.17g`,新增可选 `--result-index` 小索引文件。索引通过写文件时的实际字节位置标出完整 `series` 对象,包含版本、边界、文件长度和采样数;主结果成功关闭后才发布索引,定位或文件写入失败返回错误。
|
||
|
||
网页流式请求启用原生片段传输:Python 校验索引版本、整数边界、文件长度与布局,只解析较小的诊断和最终值;采样数组保留为 C 已生成的 JSON 字节。返回时标准 JSON 编码器处理模型元数据、名称、单位和诊断,原始数值片段直接放入 `result.series`,不再次转换为 Python 浮点数组或重新编码这些数组。浏览器仍收到原有 JSON 对象和普通数字数组,没有改成二进制接口。
|
||
|
||
字节内容在临时工作目录删除前已经独立持有,任务保留及重复 GET 查询也可直接返回。默认 Python 调用、同步 HTTP 仿真与独立 CLI 仍提供普通结果对象;取消、失败和部分结果的状态规则保持原合同。原始片段仅来自受控 C 输出,用户模型文字继续经过标准 JSON 转义,不按字符串搜索猜测边界。
|
||
|
||
C 的 JSON 数字格式化成本仍存在;本轮着重省去大数组在 Python 中的解析和再次编码。方程、牛顿/二分算法、积分器、误差限、事件点与采样没有修改。
|
||
|
||
### 浏览器打包保存与旧缓存兼容
|
||
|
||
原保存方式按每个输出列写独立块,八路一次结果有 1785 个数据写入、14 个顺序事务。新方式把短列连成固定容量的 Float64 数据块:完整八路约 7 块,每块最多 2 MiB,加 1 条头记录,在单次原子事务内提交;只有成功提交且仍是最新保存请求时,才更新小型 sessionStorage 指针。
|
||
|
||
复制过程分批让出主线程;换结果会停止旧保存,失败不发布新指针,只清理本页拥有的缓存。新记录带 `packed-f64-v1` 标记,读取通过少量 `getAll` 结果恢复;旧 IndexedDB 按列格式及旧 sessionStorage 格式继续可读。记录缺块、重叠、错误长度或未知布局会明确失败,不返回残缺结果。
|
||
|
||
### CSV 改在工作线程中生成
|
||
|
||
网页不再把全部曲线 JSON 回传给后台 CSV 接口。主线程按最多 1 MiB 的 Float64 数据批次转移给专用 Worker,Worker 按列序生成 CSV Blob;主线程保持可响应,重复点击、切换结果和组件卸载均有任务清理。保留原下载按钮和文件名规则,旧 HTTP CSV 接口仍可单独使用。
|
||
|
||
保留 time 首列、变量元数据顺序、原始单位、全部原始采样、UTF-8 BOM、CRLF 与 CSV 转义。数字使用可精确回读的最短十进制并保留负零;例如整数可能省掉 `.0`,所以 CSV 文本及文件 SHA 会变化,数值必须逐单元精确一致。未降低精度、删列或抽样来缩短用时。
|
||
|
||
## 验证条件
|
||
|
||
- 输入 SHA256:`670977bef67e62d9c66e8af497bada208bd72a7301be45128d185d47282cf288`;157 元件、178 条连接、132 状态、1784 输出变量。
|
||
- 八路 0~10 s、输出间隔 0.01 s,1002 个原始采样(含事件点);CVODE BDF / SUNDIALS 7.4.0,`rtol=1e-8`、`max_step=1e30`,状态绝对误差限保持质量1e-14、内能1e-8、速度/位移1e-12(各自SI单位)。
|
||
- 同一 Linux Xeon Silver 4210R 虚拟机、GCC 13.3.0、现有 Python .venv,正式网页使用 Node 24.18.0 构建和 Chromium 151.0.7922.34;未安装新环境或新增生产依赖。
|
||
- 旧版后台从 Git `808c484` 导出 app/native/schemas 到隔离目录,前端使用上一轮保存的同提交正式资产;优化版使用本轮源码。两者都是真实服务,不注入结果或模拟网络。
|
||
- 依次运行旧版无插桩、优化版无插桩、优化版分阶段诊断三组;每组预热一次、正式三次。正式计时期间没有安排并行求解、大编译或大文件比对;输出比较留到计时结束。
|
||
- 每轮先公开导入、导出并核对相同工程,再计点击运行时间;接着进入结果页、选择温度、CSV 和结果文件下载、刷新恢复。浏览器保存完成采用同一个 session 指针提交观察点,允许保存与查看自然重叠。
|
||
|
||
## 同机重新测得的前后用时
|
||
|
||
下表均取**无插桩组**预热后三次正式运行的中位数,括号为最小~最大值,单位秒。结果可查看以完成状态 DOM 和运行按钮恢复可用为准,另列两帧后的首次绘制机会以便对应此前报告。保存完成由相同 sessionStorage 指针观察,轮询间隔16 ms并含调度延迟;不是提前显示的保存提示。
|
||
|
||
| 指标 | 旧版 / s | 优化后 / s | 中位耗时降低 |
|
||
|---|---:|---:|---:|
|
||
| 点击运行 → 结果可查看(完成 DOM) | 9.8720(9.8700~9.9832) | 8.0341(7.9848~8.0377) | 18.62% |
|
||
| 点击运行 → 完成状态首次绘制机会 | 9.8931(9.8904~10.0018) | 8.0547(8.0017~8.0595) | 18.58% |
|
||
| 点击运行 → 浏览器保存完成观察 | 11.7740(11.6157~12.6169) | 8.1320(8.0843~8.1468) | 30.93% |
|
||
| CSV 点击 → 下载保存 | 5.6103(5.4994~5.7601) | 1.0051(0.9394~1.0077) | 82.08% |
|
||
| 刷新 → 结果首次绘制机会 | 0.6095(0.5541~0.6375) | 0.3768(0.3525~0.3980) | 38.18% |
|
||
|
||
三个目标均有改善:结果可查看节省约 **1.84 s**,结果保存完成节省约 **3.64 s**,CSV 下载保存节省约 **4.61 s**。这些区间重叠,不能相加为一次仿真的节省量。与上轮历史数据略有差异时,以本轮重新运行的旧版对照为计算比例的依据。
|
||
|
||
| 其他观察 | 旧版中位 / s | 优化后中位 / s |
|
||
|---|---:|---:|
|
||
| 工程导入到首次绘制机会 | 0.2325 | 0.2445 |
|
||
| 进入结果页到首次绘制机会 | 0.1825 | 0.1874 |
|
||
| 选择温度到曲线首次绘制机会 | 0.0286 | 0.0288 |
|
||
| 结果文件下载保存 | 1.3575 | 1.0118 |
|
||
| 刷新到结果DOM | 0.4669 | 0.2496 |
|
||
| 结果就绪DOM到保存指针观察 | 1.9040 | 0.0995 |
|
||
|
||
两组 C 纯求解中位数分别为 **6.1083 / 6.1013 s**,进程全程分别为 **8.0204 / 7.4582 s**。每次仍是 RHS 74265、接受6974、拒绝454;本轮收益来自结果处理与保存,而不是改变求解精度或少算输出。结果文件下载不是本轮主要改动,有限样本的时间变化不单独宣称为该功能优化收益。
|
||
|
||
下载完成计时包含 Playwright 通知和 saveAs 的文件系统成本;首个 DOM 后的两帧只是绘制机会,未直接测 GPU。正式页面均经本机回环 HTTP 访问,没有模拟远端网络。分阶段组相比优化版无插桩组有几%波动,其纯求解也从约6.10 s变到约5.94 s;CSV下载完成还有自动化/磁盘调度波动,故加速比例严格来自上面的两组无插桩对照。
|
||
|
||

|
||
|
||
可缩放版本:[SVG](assets/2026-09-11/postprocess-20260911-before-after.svg)。图的“result visible”为完成状态DOM,与主表第一行相同。
|
||
|
||
## 优化后的后台阶段
|
||
|
||
分阶段服务使用隔离的 C 主程序时钟和真实函数包装;数值内核逐字节相同。按每个 X-Simulation-Id 关联网页与后台,以下为正式三次的秒数(中位、范围)。
|
||
|
||
| 阶段 | 中位数(最小~最大)/ s |
|
||
|---|---:|
|
||
| XML 校验 | 0.0227(0.0219~0.0232) |
|
||
| 网络编译 | 0.0393(0.0372~0.0425) |
|
||
| C 代码生成 | 0.0557(0.0539~0.0934) |
|
||
| 构建缓存核验(均命中) | 0.0339(0.0327~0.0340) |
|
||
| C 纯积分 | 5.9407(5.9370~6.0256) |
|
||
| C 输出投影 | 0.0799(0.0774~0.0813) |
|
||
| C 原始 JSON 与索引写出 | 1.1643(1.1578~1.1762) |
|
||
| Python 原始结果文件读字节 | 0.0250(0.0107~0.0269) |
|
||
| Python 解析小索引与元数据 | 0.0012(0.0011~0.0015) |
|
||
| 索引读取与片段整理全程(含上两项) | 0.0472(0.0350~0.0486) |
|
||
| 响应接口元数据组装 | 0.0172(0.0168~0.0176) |
|
||
| 响应小元数据 JSON 序列化 | 0.0329(0.0328~0.0329) |
|
||
| 序列化后至 ASGI 最后响应完成 | 0.0747(0.0528~0.1222) |
|
||
| 后台 HTTP 全程(父区间) | 7.5315(7.5174~7.6975) |
|
||
|
||
旧版阶段诊断中,Python 完整结果 JSON 解析约0.551 s、HTTP结果序列化约1.193 s;新路径只解析小元数据约0.0012 s,序列化约0.0329 s。旧阶段分项来自上一轮诊断报告,不用于替代本轮端到端的同机重新比较。C格式化/写出仍约1.16 s,后续还有优化空间。
|
||
|
||
原生片段整理全程约0.0472 s,包含读文件、小JSON解析、校验和series字节持有;不能与其子项重复相加。C main全程约7.1957 s,Python原生执行包装约7.2336 s,worker全程约7.4409 s,也都是包含子阶段的父区间。诊断产物保留另约0.00055 s;服务器send、后台生产和浏览器等待可能重叠。
|
||
|
||
## 浏览器保存与 CSV 的实际细分
|
||
|
||
以下来自优化后的分阶段组,所有时间为观察区间,含相应异步等待与调度。
|
||
|
||
| 观察区间 | 中位数 / s |
|
||
|---|---:|
|
||
| 点击运行至调用fetch | 0.0187 |
|
||
| 最终结果 JSON.parse | 0.0982 |
|
||
| UTF-8 解码总计 | 0.0297 |
|
||
| 结果解析结束至保存提交指针发布 | 0.1186 |
|
||
| IndexedDB 单次事务窗口 | 0.0549 |
|
||
| 完成状态DOM至保存完成观察 | 0.0829 |
|
||
| CSV工作线程start发送至finish发送 | 0.0681 |
|
||
| CSV finish发送至完成消息接收 | 0.3889 |
|
||
| CSV点击至Blob下载锚点 | 0.4893 |
|
||
| 结果文件点击至Blob下载锚点 | 0.5905 |
|
||
|
||
实际八路三次保存均为 **1 个事务、约55 ms**;事务前还需要打包和调度,所以不能把55 ms当成从计算结束到保存完成的全部时间。浏览器仍在同一提交完成后发布恢复指针,没有通过放宽持久化完成标准来提速。
|
||
|
||
CSV三次均未发HTTP请求。按批传输约68 ms;finish发出到完成消息收到约389 ms,含剩余工作线程计算、启动/排队和消息传递,不冒称纯Worker CPU时间。点击至Blob准备好约489 ms,实际下载保存的主结论仍使用无插桩组约1.005 s。
|
||
|
||
新CSV为31,820,845字节,旧版32,346,795字节,减少约1.63%;主要收益是省去全量JSON往返和后台逐单元格式化,并将生成放在工作线程,不能仅归因于文件体积变小。CSV编码采用Float64精确回读方式,数值一致性单独验证。
|
||
|
||
|
||
## 正确性与回归
|
||
|
||
29 项后端相关回归通过,覆盖原生执行、默认同步接口、真实 ASGI 流式响应与重复任务 GET、用户/异常断线取消后的部分结果、心跳、索引损坏与截断文件、JSON 边界转义、独立 C 程序和旧 CSV HTTP 合同。后续补全严格索引版本及 I/O 异常处理后,6 项传输专项再次通过。
|
||
|
||
6 项真实 IndexedDB 专项通过,覆盖完整八路形状的值、负零、空列、旧缓存、缺块/重叠、头记录写入失败、保存竞态和跨页面隔离。6 项 CSV 专项通过,包括 1052929 个值的精确回读、最小子正规数、极大数、负零、列顺序和转义、无 CSV HTTP 请求、重复点击与错误重试。专项合成数据耗时只用于功能诊断,不充当正式八路测量。正式 TypeScript/Vite 构建通过。
|
||
|
||
本轮发现已有 `tests/test_native_codegen.py` 引用的 `tests/fixtures/native-skill-test.xml` 在当前提交缺失,从 `5d5a2e1:tests/data/native-skill-test.xml` 原样恢复到其现行测试路径,保证相关回归可运行。没有放回浏览器输入目录或改变这份测试资料的物理内容。
|
||
|
||
三组共 **12 次**真实八路运行全部完成10 s,无页面错误。每次公开导出工程都与固定输入的全部参数、连接与设置一致;全部 `series`、`final` 与优化前原生基准精确相等。12份CSV合计 **21,462,840 个数值单元**与基准精确相等,各组内部CSV字节稳定;旧新文本差异符合前述编码规则。12次刷新前后的完整结果一致。
|
||
|
||
另外,4份分阶段组C原始结果文件的 `series/final/finalState` 均与旧原生基准逐值相同,证明后处理改动没有改变积分轨迹或事件点。这里只比较数值,不要求运行耗时等诊断字段在不同运行间相同。证据:[全量网页/CSV核对](../../test/postprocess-20260911/equality.json)、[C原始状态核对](../../test/postprocess-20260911/native-parity.json)。
|
||
|
||
## Amesim 范围
|
||
|
||
本轮处理优化不改变计算结果,沿用 [当前八路 AME 归档核查](test-mql-8当前AME归档与完整曲线核查-2026-09-11.md) 的差异记录。完整数值逐值不变后,原有早期压力/温度差及约0.9834 s碰撞力尖峰也会保留;不能把后处理提速解释为八路物理曲线验收通过。
|
||
|
||
当前没有可信 Amesim CPU/墙钟记录,也没有可调用的 Amesim 安装,速度对比仍跳过。后续继续使用修正八路,在相同输入、精度与完整采样下核查正确性与用时。
|
||
|
||
## 文件与复现
|
||
|
||
代码入口为 [原生片段传输](../../app/simulation/native_codegen/transport.py)、[C 结果索引](../../native/runtime/main.c)、[浏览器保存](../../frontend/src/resultPersistence.ts)、[CSV 导出任务](../../frontend/src/resultCsvExport.ts) 和 [CSV Worker](../../frontend/src/resultCsv.worker.ts)。
|
||
|
||
输入快照、隔离旧版、构建、运行结果和截图位于 `test/postprocess-20260911/`;环境仍在 `.venv/native/`。这些大型运行产物和环境均在 Git 忽略范围,本轮未提交或推送 Git。
|
||
|
||
- [三组逐次与汇总JSON](../../test/postprocess-20260911/summary.json)、[各阶段CSV](../../test/postprocess-20260911/timings.csv)
|
||
- [旧版网页记录](../../test/postprocess-20260911/browser-baseline/summary.json)、[优化版网页记录](../../test/postprocess-20260911/browser-optimized/summary.json)、[优化版分阶段记录](../../test/postprocess-20260911/browser-profiled/summary.json);每次截图、输入、结果与刷新文件保存在对应组子目录,后台逐请求阶段位于 `backend-profiled/requests/<simulationId>/stages.json`。
|
||
- [源码与输入清单](../../test/postprocess-20260911/source-manifest.json)
|
||
- [后端回归](../../test/postprocess-20260911/backend-tests.log)、[传输专项复测](../../test/postprocess-20260911/backend-transport-final.log)、[正式前端构建](../../test/postprocess-20260911/frontend-build.log)
|
||
- 计时脚本 [backend_stage_profile.py](../../tests/manual/backend_stage_profile.py)、[browser_stage_profile.mjs](../../tests/manual/browser_stage_profile.mjs);数值复核 [compare_browser_stage_outputs.py](../../tests/manual/compare_browser_stage_outputs.py)。
|
||
|
||
```bash
|
||
# 后端相关回归
|
||
.venv/bin/python -m unittest tests.test_native_result_transport tests.test_native_codegen \
|
||
tests.test_generic_system_xml_simulation tests.test_result_csv_export tests.test_native_only_backend -v
|
||
# 当前正式网页的独立服务与计时:两个终端分别运行,输出目录必须为新目录
|
||
.venv/bin/python tests/manual/backend_stage_profile.py --plain \
|
||
--output-dir test/new-results-service --port 8021
|
||
LD_LIBRARY_PATH="$PWD/.venv/native/browser-libs/usr/lib/x86_64-linux-gnu${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" \
|
||
.tools/node-v24.18.0-linux-x64/bin/node tests/manual/browser_stage_profile.mjs \
|
||
--url http://127.0.0.1:8021 --mode control --runs 3 --output test/new-results-browser
|
||
```
|