优化原生结果编码传输与浏览器缓存,记录八路性能基线
原生结果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。
This commit is contained in:
1 parent
808c484f5b
commit
3bc4be3c06
55 files changed
+20522
-141
No files matched your search
@@ -0,0 +1,156 @@
|
||||
# C 端结果编码与写出优化(2026-09-11)
|
||||
|
||||
针对上一轮 [八路全流程成本评估](八路网页求解全流程成本评估-2026-09-11.md) 中约1.18 s的C结果写出阶段,先比较编码和缓冲方案,再实现并验证真实网页路径。案例固定为 [test-mql-8-corrected.json](../../tests/data/test-mql-8-corrected.json),未修改方程、局部管流求根、积分器、误差限、采样或前端生产代码。
|
||||
|
||||
已采用 **Ryu精确回读编码 + 64 KiB批量写出**。真实八路模型中,C编码写出墙钟中位数 **1.1808 → 0.1638 s,减少86.13%**;网页点击到结果可查看 **8.0100 → 6.9756 s,减少12.91%**,点击到浏览器缓存保存完成 **8.1280 → 7.0841 s,减少12.84%**。CSV下载保存没有观察到改善(1.1127 → 1.1506 s)。数值精度和求解路径保留,完整原生结果逐位一致,16次网页结果/CSV/刷新恢复核验通过。
|
||||
|
||||
上述网页收益是构建缓存命中的三次正式运行组中位数比较。首次编译另列:新依赖会增加冷编译成本,不能把缓存命中收益直接套用到首次运行。
|
||||
|
||||
## 调研与方案选择
|
||||
|
||||
原路径对约179万个结果数字逐值调用 `fprintf("%s%.17g", …)`。CPU时间与墙钟接近,只说明这一段主要在执行代码,仍需实验区分转换和写入成本。保留JSON合同可以继续使用已有Python原始片段传输、浏览器解析、缓存、CSV和结果文件流程。
|
||||
|
||||
候选比较基于作者源码与官方文档,未采用第三方性能宣传作为本项目的加速证据:
|
||||
|
||||
| 候选 | 本轮判断 | 一手依据 |
|
||||
|---|---|---|
|
||||
| 加大stdio缓存,保留 `fprintf %.17g` | 改动小;必须实测是否能减少主要成本 | 当前 `native/runtime/main.c` 与下方重放实验 |
|
||||
| `snprintf %.17g` 到固定块,再 `fwrite` | 仍使用相同浮点转换;可隔离stdio调用方式的收益 | 下方重放实验 |
|
||||
| Ryu binary64 shortest | 采用;C接口、小型固定依赖、无分配转换,能精确回读原浮点值 | [固定版本源码](https://github.com/ulfjack/ryu/blob/4c0618b0e44f7ef027ebae05d2cc7812048f7c8f/ryu/d2s.c)、[作者说明](https://github.com/ulfjack/ryu/tree/4c0618b0e44f7ef027ebae05d2cc7812048f7c8f)、[边界测试](https://github.com/ulfjack/ryu/blob/4c0618b0e44f7ef027ebae05d2cc7812048f7c8f/ryu/tests/d2s_test.cc) |
|
||||
| yyjson 的Schubfach路径 | 可用作后续对照,但完整 `.c/.h` 约756 kB,本轮不引入完整JSON库 | [0.13.0接口](https://github.com/ibireme/yyjson/blob/6447536015f3d600f3d65323b10976103b337ca7/src/yyjson.h#L1715-L1733) |
|
||||
| 独立yy_double / Dragonbox | 前者属于作者基准仓库抽出版;后者官方实现要求C++11,本轮保留C11构建链 | [yy_double](https://github.com/ibireme/c_numconv_benchmark/tree/bdacf3330e202d7ec3ae552419ea5772bae95dac/vendor/yy_double)、[Dragonbox](https://github.com/jk-jeon/dragonbox/blob/beeeef91cf6fef89a4d4ba5e95d47ca64ccb3a44/README.md) |
|
||||
|
||||
Ryu 的最短转换指足以恢复原始binary64的有效数字,不代表完整JSON字符数一定最少。其裸接口也会生成 `NaN`/`Infinity`;这些不是标准JSON数字,因此包装层必须明确拒绝,而非直接输出。相关规则见 [Ryu源码](https://github.com/ulfjack/ryu/blob/4c0618b0e44f7ef027ebae05d2cc7812048f7c8f/ryu/d2s.c)、[RFC 8259 §6](https://www.rfc-editor.org/rfc/rfc8259.html#section-6)。
|
||||
|
||||
本轮固定Ryu提交 `4c0618b0e44f7ef027ebae05d2cc7812048f7c8f`,原样保留相关C/头文件,选择Boost-1.0许可。源码及来源清单位于 [native/encoding/ryu](../../native/encoding/ryu/),头文件位于 [native/include/ryu](../../native/include/ryu/),许可同时随原生构建的 `THIRD_PARTY_NOTICES.txt` 分发。它是项目源代码依赖,不是本地运行环境;未安装新环境或新增Python/前端依赖。
|
||||
|
||||
## 独立编码与写入实验
|
||||
|
||||
将真实八路原生结果的全部series、final和finalState,共 **1,790,486个binary64**,在计时外转为预加载的连续double数组。每个候选预热一次、正式三次,顺序交替,先写真实文件并逐值按64位模式核对,再单独做 `/dev/null` 输出对照。
|
||||
|
||||
计时从 `fopen` 前到 `fclose` 后,包含编码、缓冲设置和写出;不含输入加载、输出投影、JSON静态键名准备或数值复核,也未调用 `fsync`。连续数据重放不包含生产代码的跨行矩阵读取,不能直接将微基准加速当作网页提速。
|
||||
|
||||
| 编码/写入候选 | 真实文件墙钟 / s | 真实文件CPU / s | `/dev/null`墙钟 / s | 输出字节 |
|
||||
|---|---:|---:|---:|---:|
|
||||
| 原 `fprintf %.17g` | 1.121005 | 1.120823 | 1.108461 | 32,725,281 |
|
||||
| `fprintf` + 1 MiB stdio缓存 | 1.121927 | 1.121777 | 1.107086 | 32,725,281 |
|
||||
| `snprintf %.17g` + 64 KiB批量 | 1.148625 | 1.148510 | 1.087661 | 32,725,281 |
|
||||
| 裸Ryu + 64 KiB批量 | 0.100643 | 0.100637 | 0.071845 | 34,387,794 |
|
||||
|
||||
加大stdio缓存没有改善;保留相同浮点转换的 `snprintf` 批量方案反而稍慢。裸Ryu重放墙钟减少91.02%,且 `/dev/null` 中同样大幅加速,证据支持主要成本在浮点转换,而非仅文件写入等待。CPU/墙钟之差不是独立磁盘耗时。
|
||||
|
||||
裸Ryu总是采用科学计数法,重放文件反而增大约5.08%。因此生产包装层进一步比较普通与科学表示长度;下方生产C写出包含该重排和真实stride读取,不能与裸Ryu的0.1006 s混作同一测量。
|
||||
|
||||
原始依据:[replay/summary.json](../../test/c-result-encoding-20260911/replay/summary.json),每次真实文件与计时记录均保留在 `replay/file/`。工具:[benchmark_native_result_encoding.py](../../tests/manual/benchmark_native_result_encoding.py)。
|
||||
|
||||
## 实现
|
||||
|
||||
- [json_numbers.c](../../native/runtime/json_numbers.c) 调用 `d2s_buffered_n`,用固定64 KiB栈缓冲批量写出数组,支持原输出矩阵的stride;每次调用返回前将自身缓冲交给FILE,保留调用者已有的stdio顺序和 `ftell` 边界。
|
||||
- Ryu输出后只进行十进制token重排:普通表示更短时采用普通表示,否则保留科学计数法。例如 `1.2E1 → 12`、`1E-1 → 0.1`;等长时不改。重排没有浮点运算或再次舍入,也不会展开巨大指数。负零固定输出 `-0.0`,普通Python JSON读取也能保留符号。
|
||||
- [main.c](../../native/runtime/main.c) 中 `series/final/finalState` 接入新编码。状态/整数计数/索引元数据、字符串转义、`--probe/--init` 的既有stdio路径保持原方式;不是所有C数字出口都改成Ryu。
|
||||
- 非有限结果数字、短写、`ferror` 或 `fclose` 失败都会阻止新结果索引发布并返回失败。数值数组未写完整时,不能把已写出的文件前缀视作成功结果。活动runner仍要求每次使用新的输出目录。
|
||||
- [build.py](../../app/simulation/native_codegen/build.py) 递归纳入嵌套头文件哈希,Ryu源码、查找表和许可记录都进入构建身份或随构建分发;不会误用优化前缓存。
|
||||
|
||||
JSON的字段、变量键名、列序、采样数和数值精度保留。数字拼写和文件SHA允许变化;不要求与旧 `%.17g` 的文本逐字相同。没有采用降低精度、减少采样、删列或有损压缩。
|
||||
|
||||
## 真实八路网页验证
|
||||
|
||||
输入SHA256为 `670977bef67e62d9c66e8af497bada208bd72a7301be45128d185d47282cf288`;157元件、178条连接、132状态、1784变量及时间列、1002个采样。0~10 s、0.01 s输出、CVODE BDF、`rtol=1e-8`、`max_step=1e30`及各状态atol下限保持原值。
|
||||
|
||||
旧版是本轮修改前从工作区冻结的代码(含此前结果传输/IDB/CSV优化),不是退回Git的旧后处理。旧版与新版各有无插桩组和阶段诊断组;每组预热一次、正式三次,串行运行。没有在正式计时期间安排其他大型构建、仿真或全量数值比对。前端全部使用相同生产资产,不改源码或构建。
|
||||
|
||||
用户端到端加速取同机无插桩两组;C写出细分取阶段诊断两组。页面“结果可查看”为成功完成DOM且按钮恢复可用,“保存完成”为IndexedDB事务提交后恢复指针发布的观察点。下载计时包括Playwright通知和 `saveAs`;本机回环HTTP不能代表远程网络。
|
||||
|
||||
三次正式运行的**中位数(最小~最大)**,单位s。端到端来自无插桩组,C阶段来自独立诊断组;各组依次采集,未声称同序号是交替配对试验。降幅统一为两组中位数之比,小样本、系统调度与温度波动仍影响结果。
|
||||
|
||||
| 指标 | 优化前 | 优化后 | 用时变化 |
|
||||
|---|---:|---:|---:|
|
||||
| C编码写出(墙钟,诊断组) | 1.1808(1.1529~1.2007) | 0.1638(0.1625~0.1671) | -86.13% |
|
||||
| C编码写出(CPU,诊断组) | 1.1804(1.1528~1.1991) | 0.1638(0.1625~0.1671) | -86.12% |
|
||||
| 点击运行 → 结果可查看 | 8.0100(7.9657~8.0463) | 6.9756(6.9228~6.9863) | -12.91% |
|
||||
| 点击运行 → 缓存保存完成(观察点) | 8.1280(8.0499~8.1684) | 7.0841(7.0053~7.0849) | -12.84% |
|
||||
| CSV点击 → 下载保存 | 1.1127(0.9959~1.3711) | 1.1506(0.8617~1.3136) | +3.41% |
|
||||
| 结果文件点击 → 下载保存 | 1.2254(1.1259~1.3546) | 1.2662(0.9707~1.3405) | +3.33% |
|
||||
| 积分求解(无插桩组) | 6.0926(6.0571~6.0992) | 6.0617(6.0598~6.0752) | -0.51% |
|
||||
|
||||
CSV和结果文件导出依然由已有浏览器路径生成,本轮未改。CSV用时波动范围重叠,不能判定加速;文件大小及SHA在全部16次运行间完全一致(31,820,845字节)。积分耗时的少量变化也不归因于编码:求解代码、计数和输出数值保持一致。
|
||||
|
||||

|
||||
|
||||
生产 `series` 字节数 **32,640,796 → 31,609,208(减少3.16%)**,诊断组HTTP响应体中位数 **34,475,145 → 33,442,734字节(减少2.99%)**。HTTP体另含元数据与进度,随时间文本略有波动;完整浏览器导出结果约33.85 MB,因浏览器重新编码而基本不变。
|
||||
|
||||
阶段诊断组的其他主要区间如下(单位ms,中位数):
|
||||
|
||||
| 阶段 | 优化前 | 优化后 | 边界说明 |
|
||||
|---|---:|---:|---|
|
||||
| 前端提交前预处理 | 18.800 | 22.600 | 模型检查、快照/XML生成及提交准备 |
|
||||
| 后端XML校验 | 23.855 | 22.529 | 请求输入验证 |
|
||||
| 网络编译 | 45.417 | 41.611 | 连接/方程编译 |
|
||||
| 生成C | 59.194 | 60.283 | 生成模型代码 |
|
||||
| 构建缓存校验 | 45.505 | 34.094 | 正式运行全部命中 |
|
||||
| 积分求解 | 6113.293 | 5957.849 | 含积分器、RHS/Jacobian、事件和采样 |
|
||||
| 输出投影 | 85.804 | 79.819 | 重算采样点输出,位于编码前 |
|
||||
| C结果编码写出 | 1180.792 | 163.829 | 含fopen、编码、stdio、fclose及索引 |
|
||||
| Python索引结果读取 | 46.351 | 40.434 | 父区间,含字节读取/小元数据解析 |
|
||||
| HTTP结果事件组装编码 | 32.844 | 33.361 | 元数据JSON与原始series片段拼接 |
|
||||
| 后端HTTP全程 | 7720.603 | 6553.295 | 包含上述后端子区间及ASGI发送等待 |
|
||||
| 浏览器流文本解码 | 27.700 | 27.000 | 同步TextDecoder调用累计 |
|
||||
| 浏览器结果JSON解析 | 99.700 | 104.300 | 单次原始JSON.parse |
|
||||
|
||||
C写出占原生 `main` 时间的比例从 **16.00%降至2.64%**;新版积分占 **96.05%**,输出投影约 **1.29%**。这里先计算每次运行的阶段/父区间比例,再取中位数。浏览器解析未见改善;下一步更大的速度空间仍在求解计算,结果侧剩余CPU时间已明显缩小。
|
||||
|
||||
首次运行单列(无插桩组,各一次,构建缓存未命中):
|
||||
|
||||
| 观察值 / s | 优化前 | 优化后 |
|
||||
|---|---:|---:|
|
||||
| 原生构建 | 3.6580 | 4.3720 |
|
||||
| 点击到结果可查看 | 11.6815 | 11.3810 |
|
||||
| 点击到缓存保存完成 | 11.8062 | 11.5025 |
|
||||
|
||||
本次新构建增加约0.714 s,抵消了大部分编码收益;首次页面运行仅缩短约0.30 s。这是单次冷构建观察,未做重复冷编译统计,不能推广为稳定冷启动提速。旧/新无插桩与诊断程序构建身份不同,均单独预热,不混入正式三次。
|
||||
|
||||
## 正确性、边界与限制
|
||||
|
||||
- 数字编码最终 **10项专项测试通过**。覆盖34,254个有限binary64位模式、正负零、极大极小/次正规数、十进制边界与随机值;默认及 `RYU_ONLY_64_BIT_OPS` 两种路径均逐位回读一致。覆盖64 KiB边界、stride、非有限值拒绝、短写/零写、`/dev/full` 与关闭失败。见 [最终测试日志](../../test/c-result-encoding-20260911/writer-final-tests.log)。
|
||||
- 相关后端传输/取消、代码生成、HTTP、CSV与纯C后端 **29项回归通过**。该次同时运行当时9项数字编码测试,共38项;之后补充普通/科学token选择测试并重新运行最终10项编码测试。见 [回归日志](../../test/c-result-encoding-20260911/backend-tests.log)。
|
||||
- 8次诊断原生结果以一份旧版为基准,其余7份全部 **1,790,486个数值逐位一致**,每份含1,044个负零;series、final和finalState均覆盖,不使用容差或抽样。状态和求解计数也相同,仅排除求解墙钟/CPU元数据。见 [native-bit-parity.json](../../test/c-result-encoding-20260911/native-bit-parity.json)。
|
||||
- 16次真实网页完整series/final、CSV全部单元格、下载结果文件和刷新恢复通过;CSV比较 **28,617,120个单元格**,全部CSV SHA一致。网页比较是解析后的数值严格相等,原生64位检查另行补足负零验证。见 [equality.json](../../test/c-result-encoding-20260911/equality.json)。
|
||||
- 原生CLI额外向 `/dev/full` 写出,返回退出码3且未发布结果索引。见 [写失败验证](../../test/c-result-encoding-20260911/write-failure-check/summary.json)。
|
||||
- 所有网页运行均为1002采样、1784变量,nfev=74,265、接受步6,974、拒绝步454、njev=475、nlu=1,656、事件1、启动4。输入、导入导出参数/连接与前端资产哈希一致。
|
||||
- 与本轮冻结源码比对,已有文件只改变C输出main、原生构建头文件扫描、README和许可;物理内核、积分器、代码生成方程、runner及前端生产资产未变。Ryu引入的C/头文件与Boost许可逐文件哈希等于固定上游快照。见 [source-manifest.json](../../test/c-result-encoding-20260911/source-manifest.json)。
|
||||
|
||||
本轮未改变数值求解算法,因此此前Amesim曲线差异结论保持不变。没有新的Amesim同工况CPU/墙钟数据,不作Amesim速度比较。源码兼容性考虑了GCC/MinGW,已在Linux GCC13.3实测默认及纯64位Ryu路径;本轮没有Windows运行实测。
|
||||
|
||||
后端输出期间仍包含输出投影;Python片段整理、传输、浏览器解析和保存也各有成本。父子区间及并行区间不能相加,独立阶段中位数不保证相加等于总耗时中位数。CSV生产实现本轮未修改,其下载用时差异仅记录为观察,不归因于C编码优化。
|
||||
|
||||
## 文件与复现
|
||||
|
||||
- 本报告:[C端结果编码与写出优化-2026-09-11.md](C端结果编码与写出优化-2026-09-11.md)。
|
||||
- 所有原始产物:[test/c-result-encoding-20260911/](../../test/c-result-encoding-20260911/),Git忽略。
|
||||
- 调研上游快照与SHA:[ryu-upstream/manifest.json](../../test/c-result-encoding-20260911/ryu-upstream/manifest.json);优化前冻结源码:[baseline-source/manifest.json](../../test/c-result-encoding-20260911/baseline-source/manifest.json)。
|
||||
- 网页与后端汇总:[summary.json](../../test/c-result-encoding-20260911/summary.json)、[timings.csv](../../test/c-result-encoding-20260911/timings.csv)。
|
||||
- 数字编码测试:[test_native_json_writer.py](../../tests/test_native_json_writer.py);传输与取消:[test_native_result_transport.py](../../tests/test_native_result_transport.py)。
|
||||
|
||||
当前优化版无插桩网页保留在 **http://127.0.0.1:8027/**,可导入同一八路JSON复查。计时数据、下载、截图、临时构建和上游调研快照都在被Git忽略的 `test/` 下;未安装新环境、提交或推送Git。源码Ryu依赖及许可应作为项目实现保留,不属于应忽略的本地运行环境。
|
||||
|
||||
仓库根目录运行。服务与浏览器应分两个终端启动,输出目录选新路径;下方只是新版复测例子,勿与其他仿真/编译并行。旧版重放使用 `baseline-source/tests/manual/backend_stage_profile.py`,显式 `--frontend-dist frontend/dist`;旧版诊断输出必须置于 `baseline-source` 内,以满足构建器的源码相对路径要求。
|
||||
|
||||
```bash
|
||||
.venv/bin/python -m unittest tests.test_native_json_writer 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
|
||||
|
||||
# 终端1:无插桩新版,新的端口和产物目录
|
||||
.venv/bin/python tests/manual/backend_stage_profile.py --plain --port 8029 --output-dir test/c-encoding-recheck/backend
|
||||
|
||||
# 终端2:浏览器沿用已存在的本地运行条件
|
||||
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:8029 --input tests/data/test-mql-8-corrected.json --mode control --runs 3 --output test/c-encoding-recheck/browser
|
||||
```
|
||||
|
||||
诊断组去掉服务的 `--plain`,浏览器使用 `--mode profiled`;请另选新产物目录、端口并串行测试。独立编码实验、计时聚合和完整核对使用:
|
||||
|
||||
```bash
|
||||
.venv/bin/python tests/manual/benchmark_native_result_encoding.py --result-json test/web-cost-20260911/native-compute-profile/control/run-1/result.json --output-dir test/c-encoding-recheck/replay --ryu-root test/c-result-encoding-20260911/ryu-upstream --run --warmups 1 --repeats 3 --dev-null
|
||||
.venv/bin/python tests/manual/summarize_native_encoding.py --root test/c-result-encoding-20260911
|
||||
.venv/bin/python tests/manual/compare_browser_stage_outputs.py test/c-result-encoding-20260911 --native test/web-cost-20260911/native-compute-profile/control/run-1/result.json --group baseline=test/c-result-encoding-20260911/browser-baseline --group baseline-profiled=test/c-result-encoding-20260911/browser-baseline-profiled --group optimized=test/c-result-encoding-20260911/browser-optimized --group optimized-profiled=test/c-result-encoding-20260911/browser-optimized-profiled --output test/c-result-encoding-20260911/equality.json
|
||||
```
|
||||
|
||||
原生逐位工具 [compare_native_result_bits.py](../../tests/manual/compare_native_result_bits.py) 使用 `--baseline 旧结果.json --candidate 新结果.json --output 报告.json`,`--candidate` 可重复。准确输入路径记录于现有 `native-bit-parity.json`。图表由 [plot_encoding.py](../../test/c-result-encoding-20260911/plot_encoding.py) 用系统Python/matplotlib生成,可缩放图为 [overview.svg](assets/2026-09-11/c-result-encoding-20260911-overview.svg)。大结果数值核对安排在全部性能计时结束之后。
|
||||
Reference in new issue
Block a user