From 97826360fb19af1a4943aa8fe30f60edeb885283 Mon Sep 17 00:00:00 2001 From: company-desktop-computer Date: Wed, 28 Oct 2026 23:05:13 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=B1=87=E6=80=BB=E6=89=93=E5=BC=80?= =?UTF-8?q?=E5=A4=A7=E5=9B=BE=E7=BA=B8=E7=9A=84=E6=80=A7=E8=83=BD=E7=BB=93?= =?UTF-8?q?=E8=AE=BA=EF=BC=88=E6=88=AA=E9=9D=A2=E5=AF=B9=E7=94=BB=E4=B9=8B?= =?UTF-8?q?=E5=A4=96=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 把这条线的结论收成一个文档,避免散落在多个 PR 的分析里。 内容: - 优化前后数字:开图墙钟 113 s -> 93 s,截面恢复 22.4 s -> 9.4 s, 开图期回写 1140 -> 0,临时文件 6773 -> 1733 - 剩下时间的归属(优化后 81,046 采样): base_ViewportDraw 之下 74.6%,Parasolid 67.5%, 且 Parasolid 的 93.4% 发生在我们的视口绘制里;我们的 arx 自耗时 <1% - 结论:剩余成本是"构件数量 x 单构件建模成本", 既不是 CAD 自己在拖时间(绘制之外只剩 6.6%),也不是我们的代码慢(<1%)。 一次开图要建 1335 个 AcDbX3dSolid + 1248 个 AecDbX3dPart,计数器验证每个只建一次。 - 还能动的方向按性价比排序:减少要建的几何(LOD)、几何跨会话持久化(Phase 3)、 用基本体替代扫掠、截面再优化(已判定不做,收益 <0.5%) - 诊断方法与踩过的坑:采样算不出次数必须用计数器;加密构建下函数级符号化不可靠 (GuardBar::Draw3d 报 27% 实为误判);VSDiagnostics 本机会卡死、wpr -stop 有 COM bug, 改用 xperf on/stop/merge;RVA 必须用 PDB 解析再下结论;计数器别写进同一份报告文件 验证:纯文档,数据来自 tools/run_acad_perf.ps1 + tools/perf_diagsession.py 的实测。 --- open-drawing-perf-summary.md | 101 +++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 open-drawing-perf-summary.md diff --git a/open-drawing-perf-summary.md b/open-drawing-perf-summary.md new file mode 100644 index 0000000..e11f84a --- /dev/null +++ b/open-drawing-perf-summary.md @@ -0,0 +1,101 @@ +# 打开大图纸性能优化:结论与现状 + +> 本文是这条线的收尾总结。过程细节见 `pipe-section-region-performance-plan.md`, +> 原始热点表见 `report-cpu-hotspots.md`(第一台机器)与分析脚本 `tools/perf_diagsession.py`。 +> 测试基准:`E:\桌面\big1.dwg`(8 MB,约 1000+ 管道、管件、护栏、楼梯), +> AutoCAD 2020 / R230 Debug 构建,`tools/run_acad_perf.ps1` 自动开图取数。 + +## 1. 优化前后的数字 + +| 阶段 | 开图墙钟 | 截面恢复耗时 | 开图期回写 | 临时文件 | +| --- | ---: | ---: | ---: | ---: | +| 最初 | 113 s | 22.4 s | 1140 次 / 2.38 s | 6773 次 / 9.34 MB | +| + 截面内容缓存(含 SAT 头部时间戳归一化) | 98 s | 9.87 s | 1140 次 | 2694 次 / 3.34 MB | +| + 把管道 clone 延迟到 ToJson | **93 s** | **9.4 s** | **0** | **1733 次 / 2.34 MB** | +| + X3dPart 重试修复 | 93 s | 9.4 s | 0 | 1733 次 / 2.34 MB | + +对应 PR:#97/#98(计数器)、#99(属性 key 统计)、#100(截面缓存)、 +#102/#103(回写定位)、#104(延迟 clone)、#105(内存读取评估)、#106(X3dPart 修复)。 + +三项改动各自解决了什么: + +1. **截面缓存**:一次开图要做 5641 次"SAT 文本 → 临时文件 → acisIn", + 其中大量内容重复。关键发现是 SAT 第 2 行带存盘时间戳, + 导致同几何被判成"唯一"(命中率 54 %)——跳过该行后唯一文本从 2490 降到 1560。 +2. **延迟 clone**:护栏/爬梯绘制时 `clone()` 一份管道实体只为导出 JSON, + 而 clone 走"整实体序列化",把截面写成临时 SAT 文件。改为只存参数、 + `ToJson()` 时按 XData 重建截面后,1140 次回写归零。 +3. **X3dPart 修复**:`EnsureCreated` 的命中条件要求 `m_pSolid != NULL`, + 而 Hide 构件按设计就不建几何 → 每次访问重试。加 `m_bCreated` 后 + 单对象最多重建次数由 544 降到 ≤3。**这一条没有提速**(早退分支在布尔运算之前), + 属于正确性修复。 + +## 2. 剩下的时间花在哪 + +优化后一次开图的 CPU 采样(81,046 采样 ≈ 81 s CPU / 93 s 墙钟): + +| 指标 | 占比 | +| --- | ---: | +| 在 `AecDbEntityCommon::base_ViewportDraw` 之下(我们的实体绘制) | **74.6 %** | +| **Parasolid(ASM\* 全家桶)** | **67.5 %** | +| ↑ 其中发生在我们的视口绘制里 | **93.4 %** | +| ↑ 其中在绘制之外(dwgInFields 等) | 6.6 % | +| 我们自己的 arx 自耗时(叶子帧) | **< 1 %** | +| 截面恢复(`StringToRegion` + `RegionAcisIn` 栈内) | 2.5 % | +| sqlite | ≈ 0 | + +**Parasolid 时间里 93.4 % 是被我们的绘制代码调起来的**,栈里最内层的"我们的帧"分布: + +| 占比 | 最内层我们的函数 | 性质 | +| ---: | --- | --- | +| 29.8 % | `AcGiViewDraw::DrawEntity` | 把已建好的实体/实体交给 CAD 画 | +| 14.5 % | `GiDrawUtility::DrawPipe` | 我们自己的扫掠封装 | +| 6.6 % | `GiDrawUtility::DrawLoft` | 我们自己的放样封装 | +| 1.9 % | `createRegion` | 截面构建 | +| 1.5 % | `AcDbX3dSolid::CreateExtrude` | 拉伸 | +| 1.4 % | `AecDbEwEquipment::Draw3dSolid` | 设备实体 | +| 1.3 % | `NewExtrude::makeSolid` | | +| 1.1 % | `AecDbBDPipeBase::GetBooleanPart3D` | 布尔 | + +## 3. 结论:是"我们让它建的几何太多",不是"CAD 画得慢" + +- **不是"CAD 自己在拖时间"**:只有 6.6 % 的 Parasolid 时间发生在我们的绘制路径之外。 + 换句话说,这张图并没有在加载期乱建几何(截面缓存已经把这块压到 2.5 %)。 +- **也不是"我们的代码慢"**:我们的 arx 自耗时 < 1 %,瓶颈全在被调用的建模内核里。 +- **真正的原因是建模工作量**:一次开图要建 1335 个 `AcDbX3dSolid` + 1248 个 `AecDbX3dPart` + (含布尔、扫掠、放样、蒙皮),**每一个都只建一次**(计数器验证:1335 个 solid 全部 built_once)。 + 这部分成本是"这张图有这么多个三维构件"的必然结果。 + +所以更准确的说法是:**剩余时间既不是 CAD 的锅也不是我们代码的锅, +是"构件数量 × 单构件建模成本",而这两项都由我们的设计决定。** + +## 4. 还能动的地方(按性价比排序) + +| 方向 | 说明 | 代价 / 风险 | +| --- | --- | --- | +| 减少要建的几何 | 视口外/遮挡/仅二维表达时跳过 3D 构建(LOD) | 改变显示行为,要按视图判断,容易出错 | +| 几何跨会话持久化 | 存盘时把建好的实体几何一起存(Phase 3,modeler filer) | 要升版本号 + 兼容矩阵,之前评估后暂缓 | +| 优化 `DrawPipe` / `DrawLoft` | 用更便宜的基本体代替扫掠(直管用圆柱/圆台) | 会改变与 CAD `Extrude` 一致的观感,需实测比对 | +| 截面恢复再优化 | 只剩 7.4 s 纯解析,命中率已接近上限 | 收益 < 0.5 %,**已判定不做**(见计划文档) | + +## 5. 诊断方法与踩过的坑(可复用) + +| 坑 | 结论 | +| --- | --- | +| CPU 采样只能算占比,算不出调用次数 | 一次开图 5641 次截面恢复,采样反推只估到 550~1000(差 5~10 倍)。**次数类结论必须用计数器** | +| 加密/混淆构建下函数级符号化不可靠 | trace 里 `AecDbGuardBar::Draw3d` 显示 27 %,实际计数器证明它 39 次调用全部早退。**函数级排行要用插桩核实** | +| VS 的 `VSDiagnostics.exe` 在本机会卡死 | `/launch:` 依赖 DiagnosticsHub 收集器服务,CAD 起不来 | +| `wpr -stop` 本机报 0x80010106(COM 线程模式) | 改用 `xperf -on … -f raw.etl` / `-stop` / `-merge raw out.etl` | +| 栈里只有"模块+RVA"时别猜 | `tools/resolve_rva.ps1` 用 dbghelp + 本地 PDB 解析,本文件第 5 节的两次误判都是没解析符号就下结论 | +| 计数器别写进同一份报告文件 | 抓栈若写进 `aec_section_perf.txt`,自动化脚本会把"栈写出来了"当成"开图完成",导致提前结束采集 | + +相关工具: + +| 工具 | 用途 | +| --- | --- | +| `tools/run_acad_perf.ps1` | 自动开图 + 执行 `ENVIPERFDUMP` + 收报告;`-CpuTrace` 可同时采 CPU 采样 | +| `tools/perf_diagsession.py` | 解析 `.diagsession` / `.etl`,出模块/函数/线程/调用链报告 | +| `tools/resolve_rva.ps1` | 模块+RVA → 函数名(dbghelp + PDB) | +| `ENVIPERFDUMP` / `ENVIPERFRESET` / `AEC_PERF_COUNTER=1` | 截面恢复、回写、延迟建几何的计数器 | +| `AEC_SECTION_CACHE=0` | 关闭截面缓存做 A/B | +| `AEC_WRITEBACK_TRACE=1` | 首次回写时抓调用栈到 `%TEMP%\aec_writeback_trace.txt` | -- 2.54.0