diag: 统计回写的实体类,定位到 AecDbEwPipeBase 基类实例 #103

Merged
gjm merged 1 commits from company-desktop/company-desktop-computer/diag-writeback-copyfrom AGit into main 2026-10-28 21:30:50 +08:00
Collaborator

上一版只抓到"绘制期深拷贝"的调用栈,还没回答"是哪个类的实体在回写"。
现在 SectionPerf 增加按类统计的写回计数(NoteWriteBackClass),
在 RegionAcisoutDwgOutFields 里记一次 isA()->name()。

实测(big1.dwg,开图一次):

---- write-backs by entity class ----
1140 AecDbEwPipeBase

全部 1140 次都是 AecDbEwPipeBase 基类实例,不是 AecDbEwPipe 也不是具名子类,
与属性 key 统计里 AecDbEwPipeBase 的 1172 次几乎一一对应——
说明图纸里约有 1172 个基类实例(由 AecDbAssembleWell.cpp 的装配井代码创建,
多为矩形截面段),绘制期每个被深拷贝一次。

完整链路(已写入计划文档):
base_ViewportDraw -> AecDbCurveBase::subGetTransformedCopy
(AecRxBase/AecDbCurveBase.cpp:307 里的 clone())
-> AecDbEntityCommon::base_DeepClone(AecDbEntityCommon.cpp:1232,dwgOut 在 1265)
-> AecDbEwPipeBase::dwgOutFields(AecDbEwPipeBase.cpp:1440)
-> AecDbEwPipeBase::RegionAcisoutDwgOutFields(AecDbEwPipeBase.cpp:1275)

顺带发现:AecDbEwPipeBase::copyFrom(:1605)复制截面用的是 m_pRegion->copyFrom(),
是内存级 ACIS 拷贝,比"写文件再读回"便宜得多。所以最局部的修法可能是给管系实体
加 subGetTransformedCopy 重载,用 isA()->create()+copyFrom() 代替 clone(),
绕开整条序列化链路;但 clone 与 copyFrom 语义不完全等价,需先核对。
本次只记录分析,不改行为。

验证:R230 Debug 编译通过并部署,脚本自动开图一次得到上述分类统计。

上一版只抓到"绘制期深拷贝"的调用栈,还没回答"是哪个类的实体在回写"。 现在 SectionPerf 增加按类统计的写回计数(NoteWriteBackClass), 在 RegionAcisoutDwgOutFields 里记一次 isA()->name()。 实测(big1.dwg,开图一次): ---- write-backs by entity class ---- 1140 AecDbEwPipeBase 全部 1140 次都是 AecDbEwPipeBase 基类实例,不是 AecDbEwPipe 也不是具名子类, 与属性 key 统计里 AecDbEwPipeBase 的 1172 次几乎一一对应—— 说明图纸里约有 1172 个基类实例(由 AecDbAssembleWell.cpp 的装配井代码创建, 多为矩形截面段),绘制期每个被深拷贝一次。 完整链路(已写入计划文档): base_ViewportDraw -> AecDbCurveBase::subGetTransformedCopy (AecRxBase/AecDbCurveBase.cpp:307 里的 clone()) -> AecDbEntityCommon::base_DeepClone(AecDbEntityCommon.cpp:1232,dwgOut 在 1265) -> AecDbEwPipeBase::dwgOutFields(AecDbEwPipeBase.cpp:1440) -> AecDbEwPipeBase::RegionAcisoutDwgOutFields(AecDbEwPipeBase.cpp:1275) 顺带发现:AecDbEwPipeBase::copyFrom(:1605)复制截面用的是 m_pRegion->copyFrom(), 是内存级 ACIS 拷贝,比"写文件再读回"便宜得多。所以最局部的修法可能是给管系实体 加 subGetTransformedCopy 重载,用 isA()->create()+copyFrom() 代替 clone(), 绕开整条序列化链路;但 clone 与 copyFrom 语义不完全等价,需先核对。 本次只记录分析,不改行为。 验证:R230 Debug 编译通过并部署,脚本自动开图一次得到上述分类统计。
company-desktop added 1 commit 2026-10-28 21:28:02 +08:00
上一版只给了"模块+RVA"的栈,我据此先后推断成
AecDbCurveBase::subGetTransformedCopy 和 AecDbEntityCommon::base_DeepClone,两者都是错的
(用户在那里下断点不命中;新加的 PerfTraceMark 打点也没有触发)。本次用符号解析定死。

本次改动:
- 新增 inc/PerfTraceMark.h:inline 的"某函数被调用过"打点,Debug 且 AEC_WRITEBACK_TRACE=1
  才生效。AecRxBase.arx 不链接 CoreLibrary(静态库只被 CoreCommand 链接),拿不到 SectionPerf,
  所以做成 header-only。
- 在 5 个候选入口打点:AecDbCurveBase::subGetTransformedCopy / subDeepClone、
  AecDbEntityBase::subDeepClone、AecDbEntityCommon::base_DeepClone、CKTCadUtility::CloneObject。
  跑一次开图,5 个都没触发 —— 这些候选被排除。
- 新增 tools/resolve_rva.ps1:用 dbghelp 的 SymLoadModuleEx / SymFromAddr 配本地 PDB,
  把"模块+RVA"解析成函数名。Windows 没有自带 symbolizer,这个脚本很轻,可反复用。

解析结果(CoreCommand.arx):
  0x58f051  AecDbEwPipeBase::copyFrom+0x41
  0x7e1300  AecDbZLineStair::DrawGuarBar3d+0x2c0
  0x7e09d0  AecDbZLineStair::DrawSteep3d+0xbe0
  0x7ddcef  AecDbZLineStair::Draw3d+0x34f

真实成因:Z 型楼梯画护栏(DrawGuarBar3d)时 copyFrom 一份管道实体,
而 AecDbEwPipeBase::copyFrom 用通用的 CopyObject(inc/AecDbEntityCommon.h:331)
做"整实体 dwgOutFields/dwgInFields 往返"来拷字段,于是截面被写成临时 SAT 文件再读回来。

而且这次序列化是多余的:copyFrom 随后把所有字段又用内存级方式拷了一遍
(曲线 clone/copyFrom、截面 clone/copyFrom、XData CopyFrom、m_arUserInfs append、
标志位/图层/线型/Breaks 逐项赋值),WriteUserInterface 写的也只是 m_arUserInfs。
所以最小修改是把那句 CopyObject 去掉,并补上源为 NULL 时清空目标的分支
(目前这个"清空"是 CopyObject 顺带完成的)。

本次只做定位与记录(分析写进 pipe-section-region-performance-plan.md 并纠正了上一版的误判),
不改 copyFrom 的行为——它影响所有管系实体的复制粘贴/块内拷贝,需要单独验证。
gjm approved these changes 2026-10-28 21:30:46 +08:00
gjm merged commit 439505d65a into main 2026-10-28 21:30:50 +08:00
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: gjm/envi-code#103