原生结果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。
19 KiB
C 端结果编码与写出优化(2026-09-11)
针对上一轮 八路全流程成本评估 中约1.18 s的C结果写出阶段,先比较编码和缓冲方案,再实现并验证真实网页路径。案例固定为 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接口、小型固定依赖、无分配转换,能精确回读原浮点值 | 固定版本源码、作者说明、边界测试 |
| yyjson 的Schubfach路径 | 可用作后续对照,但完整 .c/.h 约756 kB,本轮不引入完整JSON库 |
0.13.0接口 |
| 独立yy_double / Dragonbox | 前者属于作者基准仓库抽出版;后者官方实现要求C++11,本轮保留C11构建链 | yy_double、Dragonbox |
Ryu 的最短转换指足以恢复原始binary64的有效数字,不代表完整JSON字符数一定最少。其裸接口也会生成 NaN/Infinity;这些不是标准JSON数字,因此包装层必须明确拒绝,而非直接输出。相关规则见 Ryu源码、RFC 8259 §6。
本轮固定Ryu提交 4c0618b0e44f7ef027ebae05d2cc7812048f7c8f,原样保留相关C/头文件,选择Boost-1.0许可。源码及来源清单位于 native/encoding/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,每次真实文件与计时记录均保留在 replay/file/。工具:benchmark_native_result_encoding.py。
实现
- 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 中
series/final/finalState接入新编码。状态/整数计数/索引元数据、字符串转义、--probe/--init的既有stdio路径保持原方式;不是所有C数字出口都改成Ryu。 - 非有限结果数字、短写、
ferror或fclose失败都会阻止新结果索引发布并返回失败。数值数组未写完整时,不能把已写出的文件前缀视作成功结果。活动runner仍要求每次使用新的输出目录。 - 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与关闭失败。见 最终测试日志。 - 相关后端传输/取消、代码生成、HTTP、CSV与纯C后端 29项回归通过。该次同时运行当时9项数字编码测试,共38项;之后补充普通/科学token选择测试并重新运行最终10项编码测试。见 回归日志。
- 8次诊断原生结果以一份旧版为基准,其余7份全部 1,790,486个数值逐位一致,每份含1,044个负零;series、final和finalState均覆盖,不使用容差或抽样。状态和求解计数也相同,仅排除求解墙钟/CPU元数据。见 native-bit-parity.json。
- 16次真实网页完整series/final、CSV全部单元格、下载结果文件和刷新恢复通过;CSV比较 28,617,120个单元格,全部CSV SHA一致。网页比较是解析后的数值严格相等,原生64位检查另行补足负零验证。见 equality.json。
- 原生CLI额外向
/dev/full写出,返回退出码3且未发布结果索引。见 写失败验证。 - 所有网页运行均为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。
本轮未改变数值求解算法,因此此前Amesim曲线差异结论保持不变。没有新的Amesim同工况CPU/墙钟数据,不作Amesim速度比较。源码兼容性考虑了GCC/MinGW,已在Linux GCC13.3实测默认及纯64位Ryu路径;本轮没有Windows运行实测。
后端输出期间仍包含输出投影;Python片段整理、传输、浏览器解析和保存也各有成本。父子区间及并行区间不能相加,独立阶段中位数不保证相加等于总耗时中位数。CSV生产实现本轮未修改,其下载用时差异仅记录为观察,不归因于C编码优化。
文件与复现
- 本报告:C端结果编码与写出优化-2026-09-11.md。
- 所有原始产物:test/c-result-encoding-20260911/,Git忽略。
- 调研上游快照与SHA:ryu-upstream/manifest.json;优化前冻结源码:baseline-source/manifest.json。
- 网页与后端汇总:summary.json、timings.csv。
- 数字编码测试:test_native_json_writer.py;传输与取消: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 内,以满足构建器的源码相对路径要求。
.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;请另选新产物目录、端口并串行测试。独立编码实验、计时聚合和完整核对使用:
.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 使用 --baseline 旧结果.json --candidate 新结果.json --output 报告.json,--candidate 可重复。准确输入路径记录于现有 native-bit-parity.json。图表由 plot_encoding.py 用系统Python/matplotlib生成,可缩放图为 overview.svg。大结果数值核对安排在全部性能计时结束之后。
