diag: 统计回写的实体类,定位到 AecDbEwPipeBase 基类实例 #103
No files matched your search
@@ -1,6 +1,7 @@
|
||||
#include "stdafx.h"
|
||||
#include <sstream>
|
||||
#include "AecDbCurveBase.h"
|
||||
#include "PerfTraceMark.h"
|
||||
#include "AcGiViewDraw.h"
|
||||
#include "AcGiExplodeDraw.h"
|
||||
#include "simpleproperty.h"
|
||||
@@ -306,6 +307,7 @@ Acad::ErrorStatus AecDbCurveBase::subTransformBy(const AcGeMatrix3d& xform)
|
||||
|
||||
Acad::ErrorStatus AecDbCurveBase::subGetTransformedCopy(const AcGeMatrix3d& xform,AcDbEntity*& pEnt) const
|
||||
{
|
||||
PerfTraceMark("AecDbCurveBase::subGetTransformedCopy");
|
||||
assertReadEnabled();
|
||||
AcDbEntity *pCloned = (AcDbEntity *)clone();
|
||||
pCloned->setLayer(layerId());//
|
||||
@@ -324,6 +326,7 @@ Acad::ErrorStatus AecDbCurveBase::subDeepClone( AcDbObject* pOwner,
|
||||
AcDbIdMapping& idMap,
|
||||
Adesk::Boolean isPrimary) const
|
||||
{
|
||||
PerfTraceMark("AecDbCurveBase::subDeepClone");
|
||||
// 具体实现与 AecDbEntityBase 共用 AecDbEntityCommon::base_DeepClone,
|
||||
// 这里只保留 AcDbObject 虚函数这一侧的入口。
|
||||
return AecDbEntityCommon::base_DeepClone(pOwner, pClonedObject, idMap, isPrimary);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "stdafx.h"
|
||||
#include "AecDbEntityBase.h"
|
||||
#include "PerfTraceMark.h"
|
||||
#include "AcGiViewDraw.h"
|
||||
#include "AcGiExplodeDraw.h"
|
||||
#include "simpleproperty.h"
|
||||
@@ -280,6 +281,7 @@ Acad::ErrorStatus AecDbEntityBase::subDeepClone( AcDbObject* pOwner,
|
||||
AcDbIdMapping& idMap,
|
||||
Adesk::Boolean isPrimary) const
|
||||
{
|
||||
PerfTraceMark("AecDbEntityBase::subDeepClone");
|
||||
return AecDbEntityCommon::base_DeepClone(pOwner, pClonedObject, idMap, isPrimary);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "stdafx.h"
|
||||
#include "AecDbEntityCommon.h"
|
||||
#include "PerfTraceMark.h"
|
||||
#include "AecDbObjXDataMap.h"
|
||||
#include "AcGiViewDraw.h"
|
||||
#include "dbobjptr.h"
|
||||
@@ -1234,6 +1235,7 @@ Acad::ErrorStatus AecDbEntityCommon::base_DeepClone( AcDbObject* pOwner,
|
||||
AcDbIdMapping& idMap,
|
||||
Adesk::Boolean isPrimary) const
|
||||
{
|
||||
PerfTraceMark("AecDbEntityCommon::base_DeepClone");
|
||||
// You should always pass back pClonedObject == NULL
|
||||
// if, for any reason, you do not actually clone it
|
||||
// during this call. The caller should pass it in
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "stdafx.h"
|
||||
//#include "Resource.h"
|
||||
#include "KTCadUtility.h"
|
||||
#include "PerfTraceMark.h"
|
||||
#include <dbidmap.h>
|
||||
#include <acutil.h>
|
||||
#include "Kingtouch.h"
|
||||
@@ -427,6 +428,7 @@ Acad::ErrorStatus CKTCadUtility::AddToCurrentSpace(AcDbEntity* pEnt, AcDbObjectI
|
||||
// 在相同Database中,拷贝实体, 仅适合简单实体, 且复制对象没有加入数据库
|
||||
Acad::ErrorStatus CKTCadUtility::CloneObject(AcDbObject*& pObjDest, const AcDbObject* pObjSrc)
|
||||
{
|
||||
PerfTraceMark("CKTCadUtility::CloneObject");
|
||||
ASSERT(pObjSrc != NULL);
|
||||
|
||||
BOOL bCreate = FALSE;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// PerfTraceMark.h —— 极轻量的"某函数被调用过"打点,用于在没有调试器时定位调用路径
|
||||
//
|
||||
// 用法:在候选函数入口写 PerfTraceMark("函数名"),
|
||||
// 每次进程内该 tag 只记录第一次,追加到 %TEMP%\aec_writeback_trace.txt。
|
||||
// 只在 Debug(NDEBUG 未定义)且环境变量 AEC_WRITEBACK_TRACE=1 时生效,发布版本零开销。
|
||||
//
|
||||
// 为什么不用 SectionPerf:AecRxBase.arx 不链接 CoreLibrary(静态库只被 CoreCommand 链接),
|
||||
// 所以 AecRxBase 的文件里拿不到 SectionPerf 的符号。本头文件是 inline 的,哪个模块都能用。
|
||||
|
||||
#ifndef PERF_TRACE_MARK_H
|
||||
#define PERF_TRACE_MARK_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
inline void PerfTraceMark(const char* pszTag)
|
||||
{
|
||||
#ifndef NDEBUG
|
||||
static bool s_bInit = false;
|
||||
static bool s_bOn = false;
|
||||
static std::set<std::string> s_seen;
|
||||
|
||||
if (!s_bInit)
|
||||
{
|
||||
s_bInit = true;
|
||||
const char* psz = ::getenv("AEC_WRITEBACK_TRACE");
|
||||
s_bOn = (psz != NULL && psz[0] != '0');
|
||||
}
|
||||
if (!s_bOn || pszTag == NULL)
|
||||
return;
|
||||
|
||||
const std::string sTag(pszTag);
|
||||
if (s_seen.find(sTag) != s_seen.end())
|
||||
return;
|
||||
s_seen.insert(sTag);
|
||||
|
||||
char szTmp[MAX_PATH + 1];
|
||||
::memset(szTmp, 0, sizeof(szTmp));
|
||||
if (::GetTempPathA(MAX_PATH, szTmp) == 0)
|
||||
return;
|
||||
std::string sPath(szTmp);
|
||||
sPath += "aec_writeback_trace.txt";
|
||||
|
||||
FILE* fp = NULL;
|
||||
if (fopen_s(&fp, sPath.c_str(), "ab") == 0 && fp != NULL)
|
||||
{
|
||||
fprintf(fp, "[mark] %s\n", pszTag);
|
||||
fclose(fp);
|
||||
}
|
||||
#else
|
||||
(void)pszTag;
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // PERF_TRACE_MARK_H
|
||||
@@ -322,46 +322,60 @@ pClone->dwgIn(&filer); // → dwgInFields → acisIn 读回来
|
||||
(`AecDbX3dPartEntity.cpp:757` 直接 `clone()`),但**尚未证实它就是本次的调用者**,
|
||||
需要用 PDB 解析上面三个 RVA 才能钉死。
|
||||
|
||||
**哪个类在回写**(按实体类统计):
|
||||
**哪个类在回写**:
|
||||
|
||||
```
|
||||
---- write-backs by entity class ----
|
||||
1140 AecDbEwPipeBase
|
||||
```
|
||||
|
||||
全部 1140 次都是 **`AecDbEwPipeBase` 基类实例**(不是 `AecDbEwPipe`,也不是任何具名子类)。
|
||||
它与属性 key 统计里 `AecDbEwPipeBase` 1172 次几乎一一对应 →
|
||||
图纸里有约 1172 个基类实例,绘制期每个被深拷贝一次。
|
||||
(这些实例由 `AecDbAssembleWell.cpp` 的装配井代码创建,多为矩形截面段。)
|
||||
全部 1140 次都是 **`AecDbEwPipeBase` 基类实例**(不是 `AecDbEwPipe`,也不是具名子类),
|
||||
与属性 key 统计里 `AecDbEwPipeBase` 的 1172 次几乎一一对应。
|
||||
这些实例由 `AecDbAssembleWell.cpp` 的装配井代码创建,多为矩形截面段。
|
||||
|
||||
**完整调用链**(文件:行):
|
||||
**完整调用链(已用 PDB 解符号确认)**
|
||||
|
||||
前期用 `AEC_WRITEBACK_TRACE=1` 抓到栈,但帧里的 RVA 当时没解析,先后误判成
|
||||
`AecDbCurveBase::subGetTransformedCopy` 与 `AecDbEntityCommon::base_DeepClone`——
|
||||
两者都已被证伪(断点不命中、`PerfTraceMark` 打点也没触发)。
|
||||
用 `tools/resolve_rva.ps1`(dbghelp + 本地 PDB)解析后,真实链路是:
|
||||
|
||||
| 层次 | 位置 |
|
||||
| --- | --- |
|
||||
| 视口绘制 | `AecDbEntityCommon::base_ViewportDraw` → `AecDbCurveBase::subViewportDraw` |
|
||||
| 取变换副本 | `AecDbCurveBase::subGetTransformedCopy`(`AecRxBase/AecDbCurveBase.cpp:307`) |
|
||||
| ↑ 其中的 `clone()` | 触发深拷贝 |
|
||||
| 深拷贝实现 | `AecDbEntityCommon::base_DeepClone`(`AecRxBase/AecDbEntityCommon.cpp:1232`,`dwgOut` 在 :1265) |
|
||||
| Z 型楼梯绘制 | `AecDbZLineStair::Draw3d` → `DrawSteep3d` → **`DrawGuarBar3d`** |
|
||||
| 拷贝管道实体 | `AecDbEwPipeBase::copyFrom`(`CoreLibrary/AecDbEwPipeBase.cpp:1605`) |
|
||||
| ↑ 调用 | `CopyObject(*pBase,*this)`(同文件 `:1627`) |
|
||||
| **CopyObject 模板** | **`inc/AecDbEntityCommon.h:331`**:`AcDbDeepCloneFiler` + `src.dwgOutFields()` + `dest.dwgInFields()` |
|
||||
| 写字段 | `AecDbEwPipeBase::dwgOutFields`(`CoreLibrary/AecDbEwPipeBase.cpp:1440`) |
|
||||
| **写截面** | **`AecDbEwPipeBase::RegionAcisoutDwgOutFields`(`CoreLibrary/AecDbEwPipeBase.cpp:1275`)** |
|
||||
| **写截面** | **`AecDbEwPipeBase::RegionAcisoutDwgOutFields`(同文件 `:1275`)** |
|
||||
|
||||
即:`getTransformedCopy` → `clone()` → 全实体序列化 → 截面走 `acisOut` 写临时 SAT 文件。
|
||||
即:**楼梯画护栏时会 `copyFrom` 一份管道实体,而 `copyFrom` 用"整实体序列化"来拷字段,
|
||||
于是截面被写成一个临时 SAT 文件再读回来。**
|
||||
|
||||
注意 `AecDbEwPipeBase::copyFrom`(:1605)里复制截面用的是
|
||||
`m_pRegion->copyFrom(...)`(内存级 ACIS 拷贝),比"写文件再读回"便宜得多。
|
||||
所以最局部的修法可能是:给管系实体加一个 `subGetTransformedCopy` 重载,
|
||||
用 `isA()->create()` + `copyFrom()` 代替 `clone()`,绕开整条序列化链路。
|
||||
**但 clone 与 copyFrom 的语义并不完全等价,需要先核对**(扩展字典、XData、数据库驻留属性等)。
|
||||
**关键点:那次序列化是多余的。** `copyFrom` 在 `CopyObject` 之后又把所有字段用内存级方式
|
||||
拷了一遍:
|
||||
|
||||
可选修法(都需要动 `base_DeepClone` 的契约,属于结构性改动,建议单独立项):
|
||||
|
||||
| 方案 | 做法 | 风险 |
|
||||
| 字段 | `CopyObject`(序列化往返) | `copyFrom` 后面手工做的 |
|
||||
| --- | --- | --- |
|
||||
| A | 用 `pFiler->isKindOf(AcDbDeepCloneFiler::desc())` 识别深拷贝,`dwgOutFields` 只写占位符不落盘;克隆完成后由 `base_DeepClone` 用 `m_pRegion->clone()` 在内存里补 | 要给 `AecDbEntityCommon` 加一个"重数据拷贝"的虚钩子,影响面大 |
|
||||
| B | 缓存里同时保存每份 SAT 的原文,`dwgOutFields` 直接写缓存文本,省掉 `acisOut` | 若 `m_pRegion` 在 restore 之后被就地修改过,写出去的是旧几何——必须先审计全库对 `m_pRegion` 的写操作 |
|
||||
| C | 维持现状 | 每次开图多 ~2.4 s(占 98 s 的 2.4%) |
|
||||
| 曲线 | 序列化 | `m_pBaseCurve->clone()` / `copyFrom()` |
|
||||
| 截面 | **acisOut → 临时文件 → acisIn** | `m_pRegion->clone()` / `copyFrom()` |
|
||||
| spec XData | 序列化 | `m_mapSpecXData.CopyFrom()` |
|
||||
| 用户界面数据 | 序列化 | `m_arUserInfs.append()` |
|
||||
| 标志位 / 图层 / 线型 / Breaks | 序列化 | 逐项赋值 |
|
||||
|
||||
**A 与 B 都需要先做代码审计**,不适合和本缓存改动混在一起。
|
||||
`WriteUserInterface` 只写 `m_arUserInfs`,同样已被手工拷贝。
|
||||
所以最小改动是:**把 `copyFrom` 里那句 `CopyObject(*pBase,*this)` 去掉**,
|
||||
再补上源对象为 NULL 时清空目标的分支(目前这个"清空"是 `CopyObject` 顺带完成的)。
|
||||
|
||||
预计收益:省掉 1140 次 `acisOut`(2.38 s)及其对应的读回;
|
||||
`copyFrom` 本身也从"全实体序列化"变成纯内存拷贝。
|
||||
|
||||
风险:`copyFrom` 是所有管系实体(管道/管件/井…)的公共拷贝路径,涉及复制粘贴、块内拷贝等,
|
||||
必须实测验证(复制粘贴后截面、XData、GUID、图层线型是否都正确)。
|
||||
|
||||
> 诊断手法记录:`AEC_WRITEBACK_TRACE=1` + `SectionPerf::TraceCallStack()` 出栈,
|
||||
> 再用 `tools/resolve_rva.ps1` 把"模块+RVA"解析成函数名。
|
||||
> 单靠栈不解析符号很容易误判(本次就误判了两次)。
|
||||
|
||||
### Phase 2:临时目录治理(可选,止血)
|
||||
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
# resolve_rva.ps1 - resolve module+RVA to function names using dbghelp + local PDB
|
||||
# usage: powershell -NoProfile -ExecutionPolicy Bypass -File resolve_rva.ps1 `
|
||||
# -Module "C:\...\CoreCommand.arx" -Rva 0x58f051,0x7ddcef,0x7e09d0,0x7e1300
|
||||
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$Module,
|
||||
[Parameter(Mandatory=$true)][string[]]$Rva,
|
||||
[string]$SymbolPath = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$mod = (Resolve-Path $Module).Path
|
||||
$dir = Split-Path $mod -Parent
|
||||
if (-not $SymbolPath) { $SymbolPath = $dir }
|
||||
|
||||
$cs = @'
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
public static class Dbg {
|
||||
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi)]
|
||||
public struct SYMBOL_INFO {
|
||||
public uint SizeOfStruct;
|
||||
public uint TypeIndex;
|
||||
public ulong Reserved1;
|
||||
public ulong Reserved2;
|
||||
public uint Index;
|
||||
public uint Size;
|
||||
public ulong ModBase;
|
||||
public uint Flags;
|
||||
public ulong Value;
|
||||
public ulong Address;
|
||||
public uint Register;
|
||||
public uint Scope;
|
||||
public uint Tag;
|
||||
public uint NameLen;
|
||||
public uint MaxNameLen;
|
||||
[MarshalAs(UnmanagedType.ByValArray, SizeConst=1024)]
|
||||
public byte[] Name;
|
||||
}
|
||||
|
||||
[DllImport("dbghelp.dll", SetLastError=true)]
|
||||
public static extern bool SymInitialize(IntPtr hProcess, string UserSearchPath, bool fInvadeProcess);
|
||||
[DllImport("dbghelp.dll", SetLastError=true)]
|
||||
public static extern ulong SymLoadModuleEx(IntPtr hProcess, IntPtr hFile, string ImageName, string ModuleName,
|
||||
ulong BaseOfDll, uint DllSize, IntPtr Data, uint Flags);
|
||||
[DllImport("dbghelp.dll", SetLastError=true)]
|
||||
public static extern bool SymFromAddr(IntPtr hProcess, ulong Address, out ulong Displacement, ref SYMBOL_INFO Symbol);
|
||||
[DllImport("dbghelp.dll", SetLastError=true)]
|
||||
public static extern bool SymCleanup(IntPtr hProcess);
|
||||
[DllImport("dbghelp.dll")]
|
||||
public static extern uint SymSetOptions(uint SymOptions);
|
||||
|
||||
public const uint SYMOPT_UNDNAME = 0x00000002;
|
||||
public const uint SYMOPT_LOAD_LINES = 0x00000010;
|
||||
public const uint SYMOPT_DEBUG = 0x80000000;
|
||||
}
|
||||
'@
|
||||
Add-Type -TypeDefinition $cs -Language CSharp | Out-Null
|
||||
|
||||
[Dbg]::SymSetOptions([Dbg]::SYMOPT_UNDNAME) | Out-Null
|
||||
$proc = [System.Diagnostics.Process]::GetCurrentProcess()
|
||||
$h = $proc.Handle
|
||||
if (-not [Dbg]::SymInitialize($h, $SymbolPath, $false)) {
|
||||
Write-Output ("SymInitialize failed: " + [ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error()).Message)
|
||||
exit 1
|
||||
}
|
||||
|
||||
$base = [Dbg]::SymLoadModuleEx($h, [IntPtr]::Zero, $mod, $null, 0, 0, [IntPtr]::Zero, 0)
|
||||
if ($base -eq 0) {
|
||||
Write-Output ("SymLoadModuleEx failed: " + [ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error()).Message)
|
||||
[Dbg]::SymCleanup($h) | Out-Null
|
||||
exit 1
|
||||
}
|
||||
Write-Output ("module : " + $mod)
|
||||
Write-Output ("base : 0x{0:x}" -f $base)
|
||||
Write-Output ("sympath : " + $SymbolPath)
|
||||
Write-Output ""
|
||||
|
||||
foreach ($r in (($Rva -join ',').Split(',') | Where-Object { $_ -ne '' })) {
|
||||
$off = [Convert]::ToUInt64($r, 16)
|
||||
$addr = $base + $off
|
||||
$sym = New-Object Dbg+SYMBOL_INFO
|
||||
$sym.SizeOfStruct = 88
|
||||
$sym.MaxNameLen = 1024
|
||||
$sym.Name = New-Object byte[] 1024
|
||||
[uint64]$disp = 0
|
||||
if ([Dbg]::SymFromAddr($h, $addr, [ref]$disp, [ref]$sym)) {
|
||||
$name = [Text.Encoding]::ASCII.GetString($sym.Name, 0, [int]$sym.NameLen)
|
||||
Write-Output ("0x{0:x} {1}+0x{2:x} {3}" -f $off, $name, $disp, "")
|
||||
} else {
|
||||
Write-Output ("0x{0:x} <no symbol> (err {1})" -f $off, [Runtime.InteropServices.Marshal]::GetLastWin32Error())
|
||||
}
|
||||
}
|
||||
|
||||
[Dbg]::SymCleanup($h) | Out-Null
|
||||
Reference in new issue
Block a user