Merge pull request 'docs: 记录性能部分 4.2~4.6 的结论' (#56) from company-desktop-computer/docs/ewequipment-perf-decisions into main
Reviewed-on: #56
This commit was merged in pull request #56.
This commit is contained in:
commit
5a07b11e42
1 file changed
+72
-33
@@ -498,14 +498,14 @@ for (...) {
|
||||
|
||||
成本主要在 3:N 个单元 = N 次造体 + (N−1) 次布尔,与 `AecDbX3dPart` 的结论一致。
|
||||
|
||||
### 4.2 P0:拖拽时缓存不清理,且每帧全量重建
|
||||
### 4.2 P0:拖拽时缓存不清理,且每帧全量重建(已修复,PR #53)
|
||||
|
||||
```cpp
|
||||
if (!dc.IsDragging()) // :3036
|
||||
if (!dc.IsDragging()) // 改前
|
||||
{
|
||||
if (Has3DBuffer()) { 画缓存; return; }
|
||||
}
|
||||
Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
|
||||
Draw3dSolid(&dc, nColor); // 拖拽时每帧都走到这里
|
||||
```
|
||||
|
||||
而 `Add3DBuffer`(`AecDbEntityCommon.cpp:146`)只是 `append`,**不清理已有内容**。于是拖拽过程中:
|
||||
@@ -514,44 +514,81 @@ Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
|
||||
- 每帧往 3D 缓存里**再追加** N 个
|
||||
- `Draw3DBuffers` 把缓存里所有实体都画一遍
|
||||
|
||||
第 k 帧要造 N 个体、画 k×N 个体。内存随帧数线性涨、帧率随帧数线性掉,拖一次长距离夹点就能看到明显卡顿和内存上冲。
|
||||
第 k 帧要造 N 个体、画 k×N 个体。内存随帧数线性涨、帧率随帧数线性掉。
|
||||
|
||||
这是当前最值得修的性能问题。可选做法(按改动量排序):
|
||||
**2026-10-25 已修复(PR #53)**:去掉 `!dc.IsDragging()` 判断,拖拽时也直接走 3D 缓存。原先写这个判断的原因是「解开后崩溃」,实际测试并不崩溃——崩溃来自 4.2 的缓存无限追加,而不是拖拽本身。缓存长度不再随帧数增长,`Draw3dSolid` 拖拽期间一次都不调用。
|
||||
|
||||
1. 进入拖拽前 `Clean3DBuffer()`,拖拽结束后再清一次 —— 一行改动,先止住缓存膨胀
|
||||
2. 拖拽时画包围盒/线框(用 `GetInfFaceInf` 那套参数直接算 bbox),不造 ACIS
|
||||
3. 拖拽时只对缓存里的 solid 施加增量矩阵(`transformBy(delta)`),不重建
|
||||
原计划的另外两个做法(拖拽时画线框、增量 `transformBy`)不需要了。
|
||||
|
||||
### 4.3 P1:2D 符号绘制触发 3D 布尔
|
||||
### 4.3 P1:2D 符号绘制触发 3D 布尔(暂缓)
|
||||
|
||||
11 处 `Draw*Vlv2d` 里调 `GetBodyBox(ext)`(`:5020` `:5189` `:5357` `:5512` `:5675` `:6307` `:6492` `:6654` `:6816` `:6996` `:7147`),而 `GetBodyBox:4205` 在没有 3D 缓存时会 `Draw3d` —— 也就是**为了画一个 2D 阀门手柄的方向,把所有单元造一遍体再做布尔**。
|
||||
11 处 `Draw*Vlv2d` 里调 `GetBodyBox(ext)`(`:5020` `:5189` `:5357` `:5512` `:5675` `:6307` `:6492` `:6654` `:6816` `:6996` `:7147`),而 `GetBodyBox` 在没有 3D 缓存时会 `Draw3d` —— 为了画一个 2D 阀门手柄的方向,把所有单元造一遍体再做布尔。
|
||||
|
||||
`Draw2d` 本来就是轻量路径(平面符号),这条依赖把它变成了重路径。设备数量多、缩放频繁时开销明显。
|
||||
**2026-10-25 核实:原文的「缩放频繁时开销明显」写错了。** `Draw2d` 开头 `if (Has2DBuffer())`(`:2545`)就直接画缓存并返回,根本走不到 `Draw*Vlv2d`。缩放、平移、重生成都不会清缓存,只有改参数(`clearGeoBuffer`)、图层变化等才会。
|
||||
|
||||
建议:给 `XPrimData` 增加一个按参数解析计算 bbox 的函数(box/cylinder/cone/nozz 这几种占绝大多数,参数都在 `m_pData` 里,`GetInfFaceInf` 已经在做类似的事),2D 路径只算参数 bbox,不碰 ACIS。
|
||||
所以准确的触发条件是:**2D 缓存失效后的第一次绘制**,且此时 3D 缓存也为空(平面图场景下 3D 缓存一直是空的)。是一次性开销,不是每次绘制的持续开销。
|
||||
|
||||
### 4.4 P1:`GetInterface` 每次 `acedGetVar` + 临时实体
|
||||
结论:暂缓。真要优化,收益只在「改完参数后立刻看平面图」这一下,不值得为此把 25 个 `Draw*2d` 的取尺寸逻辑改成参数 bbox。
|
||||
|
||||
- `:3237` 和 `:3300` 各调一次 `acedGetVar("INSUNITS")`,而 `GetInterface` 在绘制、夹点、osnap、剖切里共 67 处调用,很多在循环内。`INSUNITS` 是数据库级常量,可以缓存(`CoreTools::IsMetersUnit()` 已有类似封装)
|
||||
- `GetInfFaceInf` 每个面调两次 `GetTransformbyXform`(`:12160` `:12225`),每次都要 `new`/构造临时 `AcDbLine` / `AcDbPoint` 再走 `TransformEntity`。一个 6 面 box = 12 个临时实体;夹点显示时每个管嘴都要算
|
||||
- 建议:把"单元 → 世界矩阵"提成一个函数,一次算好,`GetInfFaceInf` / `GetTransformbyXform` 都复用它
|
||||
### 4.4 P1:`GetInterface` 每次 `acedGetVar` + 临时实体(只做矩阵复用)
|
||||
|
||||
### 4.5 P1:`GetDraw2dables` 每次建 `*U` 块 + 跑 HLR
|
||||
- `acedGetVar("INSUNITS")` 每个重载各调一次。**2026-10-25 决定不缓存**:`acedGetVar` 读的是数据库级变量,开销很小,而且当前图纸单位可以在会话中途变化(切换图纸、`INSUNITS` 被改),缓存反而要处理失效
|
||||
- `GetInfFaceInf` 每个面调两次 `GetTransformbyXform`,每次构造一个临时 `AcDbLine` / `AcDbPoint` 再走 `TransformEntity`。一个 6 面 box = 12 个临时实体;夹点显示时每个管嘴都要算
|
||||
|
||||
`:2909` 调 `CKTCadUtility::CreateBlockTableRecord(ar, ptIns, NULL, ...)`,`pszBlk == NULL` 时会在当前图纸里**新建一个匿名块表记录**(`KTCadUtility.cpp:537`)。也就是说一次"取 2D 图形"的查询操作会往 DWG 里写数据:块表增长、undo 记录、文件变大。
|
||||
**「单元 → 世界矩阵」是什么**:`TransformEntity`(`:1552`)对实体做的是一串矩阵变换,顺序固定:
|
||||
|
||||
同时 `OrthoProjected3DSolid` 走的是完整 HLR(`HlrCommands.cpp:214`),每个 solid 一次。
|
||||
```cpp
|
||||
rotation(m_ptRotation.x, kXAxis) // 绕 X 轴转
|
||||
rotation(m_ptRotation.y, kYAxis) // 绕 Y 轴转
|
||||
rotation(m_ptRotation.z, kZAxis) // 绕 Z 轴转
|
||||
translation(m_vtMove) // 平移到单元自身的偏移
|
||||
m_arMatrix[i] // 逐项累乘附加矩阵
|
||||
rotation(按 vtDir 先绕 Z 再绕法向) // 转到插入方向
|
||||
translation(ptIns) // 平移到插入点
|
||||
rotation(90°, kXAxis, ptIns) // 竖放时再翻一次
|
||||
```
|
||||
|
||||
导出场景可以接受,但若被绘制路径复用就不合适。建议:
|
||||
这串变换只依赖 `(pData, ptIns, vtDir, bVert)`,与具体是什么实体无关。现在为了变换「一个点」或「一个方向」,要先把它们塞进一个临时实体、跑完这一串 `transformBy`、再把结果读出来——矩阵算了一遍又一遍。
|
||||
|
||||
- 只读查询走"投影曲线直接返回",不建块
|
||||
- 需要块时由调用方决定,函数只负责给实体
|
||||
所谓「提成世界矩阵」,就是把这串矩阵按同样顺序 `postMult` 合成一个 `AcGeMatrix3d M`,之后直接 `ptGe.transformBy(M)` / `vtDir.transformBy(M)`。`GetInfFaceInf` 里 12 次临时实体可以全部去掉,只剩 12 次向量乘法。
|
||||
|
||||
### 4.6 P1:开图路径
|
||||
这条**尚未实现**,等 4.6 一起评估。
|
||||
|
||||
`dwgInFields:1791` 无条件 `subDMesh2NewSolid()`,对每个 `kSubDMesh` 单元做一次 `ParseRevitFamily::parseJson`(`:3142`)。Revit 族转来的设备,这一步是开图耗时的大头之一,而且结果只用于显示。
|
||||
### 4.5 P1:`GetDraw2dables` 每次建 `*U` 块 + 跑 HLR(暂缓)
|
||||
|
||||
可以照 `AecDbX3dPart` 的做法改成惰性:开图只记标志,首次绘制/取几何时再解析。前提是先修 2.4(现在的删除逻辑会把数组搞错)。
|
||||
调 `CKTCadUtility::CreateBlockTableRecord(ar, ptIns, NULL, ...)`,`pszBlk == NULL` 时会在当前图纸里**新建一个匿名块表记录**(`KTCadUtility.cpp:537`)。也就是说一次"取 2D 图形"的查询操作会往 DWG 里写数据:块表增长、undo 记录、文件变大。同时 `OrthoProjected3DSolid` 走完整 HLR(`HlrCommands.cpp:214`),每个 solid 一次。
|
||||
|
||||
**2026-10-25 核实**:
|
||||
|
||||
- 剖切功能已经不再调用它(可以确认)
|
||||
- 但它是 `AecDbEntityCommon` 的虚函数,`CoreCommand/KTEarthWormCmd.cpp` 里有十几处直接调用(`:6776` `:7884` `:12377` 等),`AecDbEntityCommon.cpp:1137` 的 `entitySet` 路径也在用,用途不止剖切
|
||||
|
||||
所以「剖切不用了」不等于「可以改语义」。暂不动,等确认其余调用方各自的期望。
|
||||
|
||||
### 4.6 P1:开图路径(待评估,未实施)
|
||||
|
||||
`dwgInFields` 里无条件 `subDMesh2NewSolid()`,对每个 `kSubDMesh` 单元做一次 `ParseRevitFamily::parseJson`(`parseFamilyForRevit.cpp:1234`:RapidJSON 解析 + 遍历生成 `NewSolidBase`)。Revit 族转来的设备,这一步是开图耗时的大头之一。
|
||||
|
||||
**2026-10-25 评估结论:不能照 `AecDbX3dPart` 的方式惰性化。** 两者的性质不同:
|
||||
|
||||
| | `AecDbX3dPart` | `AecDbEwEquipment` |
|
||||
| --- | --- | --- |
|
||||
| 懒加载的对象 | `m_pSolid`,布尔运算的**派生结果** | `m_arSubPrimPtrs`,**持久化数据本体** |
|
||||
| 延迟的代价 | 首次绘制时重算一次 | 对象在解析前处于「半初始化」状态 |
|
||||
| 数组内容 | 不变 | `kSubDMesh` 被替换成若干新单元,**长度和顺序都变** |
|
||||
|
||||
具体风险:
|
||||
|
||||
1. `m_arSubPrimPtrs` 在本文件有 **99 处引用、约 40 个读函数**(`GetInterface` ×2、`GetInterfaceCount`、`Draw3dSolid`、`ImpGetGripPoints`、`GetSubPrimObj*`、`ActiveInterface`、`NozzhasFlage`、13 个 `Draw*2d`、`PrimPorjectToPlan`、`ToJson` …),每一处都要在开头调 `EnsureSubDMeshParsed()`,漏一个就是「少管嘴 / 少几何 / 夹点错位」
|
||||
2. **索引语义会变**:`GetSubPrimObj(n)`、`SetActiveSubPrimObj(n)`、`RemoveSubPrimObj(n)` 的下标在解析前后指向不同单元。用户如果在解析前拿到下标,解析后再用,就删错/选错
|
||||
3. `copyFrom` / `ByJson` 拷的是「当前数组」,源对象没解析时目标也没解析,需要在拷贝时正确传递标志
|
||||
4. 收益不确定:`AcDb` 开图后可能立刻调 `subGetGeomExtents`,那样惰性化等于没省
|
||||
|
||||
**建议的替代路径**(按优先级):
|
||||
|
||||
1. 先量:在真实图纸上统计「开图总耗时」与 `subDMesh2NewSolid` 的累计耗时。占比不到 20% 就不值得动
|
||||
2. 若确实是大头,优先做「解析结果缓存」:以 `m_pData` 内容的 hash 为 key 缓存解析出的 `XPrimData` 数组,第二次遇到同一个族直接深拷贝。语义完全不变,风险最低
|
||||
3. 最后才考虑惰性化,且必须把所有读入口一次改全
|
||||
|
||||
### 4.7 度量建议
|
||||
|
||||
@@ -608,13 +645,15 @@ Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
|
||||
|
||||
### 5.3 第三批:性能(先量后改)
|
||||
|
||||
| 优先级 | 改动 | 预期 |
|
||||
| --- | --- | --- |
|
||||
| 高 | 拖拽时清缓存 + 简化显示(4.2) | 拖拽帧率、内存峰值 |
|
||||
| 高 | 2D 路径改用参数 bbox,不触发 3D(4.3) | 2D 重生成、缩放 |
|
||||
| 中 | `INSUNITS` 缓存 + 单元矩阵复用(4.4) | 夹点、osnap |
|
||||
| 中 | `GetDraw2dables` 不建块(4.5) | 导出、剖切、DWG 体积 |
|
||||
| 低 | `subDMesh2NewSolid` 惰性化(4.6) | 开图 |
|
||||
| 优先级 | 改动 | 预期 | 状态 |
|
||||
| --- | --- | --- | --- |
|
||||
| 高 | 拖拽时走缓存,不重建(4.2) | 拖拽帧率、内存峰值 | 已完成(PR #53) |
|
||||
| 高 | 2D 路径改用参数 bbox,不触发 3D(4.3) | 一次性开销,收益有限 | 暂缓 |
|
||||
| 中 | 单元矩阵复用,去掉临时实体(4.4) | 夹点、osnap | 待做 |
|
||||
| 中 | `GetDraw2dables` 不建块(4.5) | 导出、DWG 体积 | 暂缓,调用方未确认 |
|
||||
| 低 | `subDMesh2NewSolid` 惰性化(4.6) | 开图 | 待量,不建议按原方案做 |
|
||||
|
||||
`INSUNITS` 缓存(4.4 的第一条)已决定不做。
|
||||
|
||||
### 5.4 需要先确认的事
|
||||
|
||||
@@ -668,4 +707,4 @@ Draw3dSolid(&dc, nColor); // :3062 拖拽时每帧都走到这里
|
||||
2. ~~第二批(`m_Combins` 存盘清空)~~ —— 已完成(PR #48),见 3.1、5.2
|
||||
3. ~~第一批 6~11(逻辑)~~ —— 已完成,范围扩到 3.2~3.14 共 10 项,见 5.1
|
||||
4. ~~确认 5.4 剩下的问题~~ —— 3.7 已改(见 3.7),3.12 确认是故意的、不改,3.6 暂缓
|
||||
5. 第三批按 4.7 度量结果排序,一项一个 PR
|
||||
5. 第三批:4.2 已完成(PR #53);4.4 的矩阵复用待做;4.3 / 4.5 / 4.6 按 4.x 的结论暂缓或待量
|
||||
Reference in new issue
Block a user