fix: AecDbX3dPart 记住"已尝试建过",避免 Hide 构件被反复重建

现象:开图时 X3dPart 的 EnsureCreated 命中率只有 10.1%,1914 次重建里
721 次产出 NULL(38%),单个对象最多被重建 544 次。

根因:EnsureCreated 的命中条件是 !m_bNeedCreate && m_pSolid != NULL,
而 Rebuild() 里对 Hide 构件是"故意不建几何、直接 return true"——
m_pSolid 按设计就是 NULL,于是每次访问都重新走一遍 Rebuild,永远不可能命中。
子项全为空的构件同理。

修复:
- 新增 m_bCreated 标记"已经尝试建过"(无论是否产出实体)
- EnsureCreated 命中条件改为 !m_bNeedCreate && (m_bCreated || m_pSolid != NULL)
- Rebuild 入口置 m_bCreated = true;所有设 m_bNeedCreate = true 的 setter
  同时置 m_bCreated = false,保证参数变化后仍会重建
- 重建后仍无实体时打一条日志(限前 5 条,带 m_sName),方便定位是哪些构件

实测(big1.dwg / R230 Debug):
  X3dPart 命中率      10.1% -> 41.4%
  重建次数            1914  -> 1248(-666)
  rebuild_null         721  -> 55
  单对象最多重建       544  -> <=3
  开图墙钟             93 s -> 93 s(无变化)

注意:这 666 次空转没有带来可测的提速,因为 Rebuild 里 Hide 的早退分支在
布尔运算之前,每次只有几微秒。所以这是正确性/整洁度修复,不是性能修复;
但消除了"个别构件被重试几百次"这种病态行为,也暴露了"哪些构件建不出实体"。

顺带新增诊断(Debug + AEC_PERF_COUNTER=1 才生效,Release 无开销):
- SectionPerf::NoteEnsure():按 tag 统计"延迟建几何"的命中/重建,
  并给出对象维度直方图与重建次数最高的对象
- AecDbX3dPart::Rebuild / AcDbX3dSolid::EnsureCreated / AecDbGuardBar::Draw3d 打点
- tools/run_acad_perf.ps1 增加 -CpuTrace:用 xperf 采 CPU 采样并 merge 成 etl,
  可直接喂 tools/perf_diagsession.py(VS 的 VSDiagnostics 在这台机器上会卡住,
  wpr -stop 有 COM 线程模式 bug,故用 xperf)

验证:R230 Debug 编译通过并部署,自动开图取数;截面缓存与回写仍为 0/正常。
This commit is contained in:
company-desktop committed 2026-10-28 22:51:01 +08:00
1 parent 56c89c5fa4
commit facf14699b
7 files changed
+154 -3

No files matched your search

@@ -2,6 +2,7 @@
#include <math.h>
#include "MFCDbDwgFiler.h"
#include "AcDbX3dSolid.h"
#include "SectionPerfCounter.h"
#include "AecDbEarthWorm.h"
#include "AecDbPortRoom.h"
#include "AecDb3dRoadAxisLine.h"
@@ -4620,7 +4621,12 @@ Acad::ErrorStatus AcDbX3dSolid::ReCreate()
void AcDbX3dSolid::EnsureCreated() const
{
if (!m_bNeedCreate)
{
SectionPerf::NoteEnsure("X3dSolid", this, false);
return;
}
SectionPerf::NoteEnsure("X3dSolid", this, true);
//先清标志再重建,避免 ReCreate 内部再次触发 EnsureCreated 造成递归
m_bNeedCreate = false;
@@ -1,5 +1,6 @@
#include "stdafx.h"
#include "AecDbGuardBar.h"
#include "SectionPerfCounter.h"
#include "KTSetting.h"
#include "KTCADUtilityEx.h"
#include "dbgrip.h"
@@ -188,6 +189,7 @@ Acad::ErrorStatus AecDbGuardBar::SubAddDimPoints(AcDbGripDataPtrArray& grips,con
AcGeVector3d vtDirX;
AcGePoint3d ptGe[4],ptMid;
SectionPerf::NoteEnsure("GuardBar::Draw3d", this, true);
GuardBar bar;
GetGuardBarData(bar);
@@ -786,6 +788,7 @@ AcDbEntity* AecDbGuardBar::DrawPipe(AcGiDraw &dc,AcDbCurve* pBaseCln1,AcDbRegion
Acad::ErrorStatus AecDbGuardBar::Draw3d(AcGiDraw &dc) const
{
SectionPerf::NoteEnsure("GuardBar::Draw3d.entry", this, false);
//护栏的样子参照 PDMS 的样子
assertReadEnabled();
@@ -823,6 +826,7 @@ Acad::ErrorStatus AecDbGuardBar::Draw3d(AcGiDraw &dc) const
if (Has3DBuffer())
{
SectionPerf::NoteEnsure("GuardBar::Draw3d", this, false);
//Clean3DBuffer();
AcDbEntity* pEnt = NULL;
const AcDbVoidPtrArray &ar3D = Get3DBuffer();
@@ -1,5 +1,6 @@
#include "stdafx.h"
#include "AecDbX3dPart.h"
#include "SectionPerfCounter.h"
#include "AcDbX3dSolid.h"
#include "CoreTools.h"
#include "KTSetting.h"
@@ -49,6 +50,7 @@ AecDbX3dPart::AecDbX3dPart(void)
m_pSolidDraw = NULL;
m_pSolid = NULL;
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_sName = _T("");
m_sGUID = IDCreate();
m_bActive = false;
@@ -62,6 +64,7 @@ AecDbX3dPart::AecDbX3dPart(const AecDbX3dPart& pPart)
m_pSolidDraw = NULL;
m_pSolid = NULL;
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_sName = pPart.m_sName;
m_sGUID = pPart.m_sGUID;
@@ -153,6 +156,7 @@ void AecDbX3dPart::ReCreateGUID()
void AecDbX3dPart::AddX3dSolid(AcDbX3dSolid* pXSolid,AcDb::BoolOperType bType /*AcDb::kBoolUnite*/,bool bForHouseSub /*= false*/)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
if (bType == AcDb::kBoolUnite)
{
m_arX3DSolidPtrsUnite.append(pXSolid);
@@ -173,6 +177,7 @@ void AecDbX3dPart::AddX3dSolid(AcDbX3dSolid* pXSolid,AcDb::BoolOperType bType /
void AecDbX3dPart::SetOrgPoint(const AcGePoint3d& ptOrg)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_ptOrg = ptOrg;
}
@@ -472,6 +477,7 @@ bool AecDbX3dPart::GetActiveInf(CPoint& ptRC) const
void AecDbX3dPart::RemoveAll3dSolidRec()
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
AcDbX3dSolid* pXSolid = NULL;
for (int i = 0;i < m_arX3DSolidPtrsUnite.length();i++)
{
@@ -509,6 +515,7 @@ void AecDbX3dPart::RemoveAll3dSolidRec()
void AecDbX3dPart::RemoveAt(int nIndex,AcDb::BoolOperType bType/* = AcDb::kBoolUnite*/,bool bForHouseSub/* = false*/)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
AcDbX3dSolid* pXSolid = NULL;
if (bForHouseSub)
{
@@ -588,6 +595,7 @@ AcDbX3dSolid* AecDbX3dPart::GetX3dSolid(int nIndex,AcDb::BoolOperType bType/* =
bool AecDbX3dPart::SetAxis(int nIndex,const AcGeVector3d& vtAxis)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
if (nIndex >= 0 && nIndex < MAX_AXISROTATS)
{
m_arVtAxis.setAt(nIndex,vtAxis);
@@ -615,6 +623,7 @@ void AecDbX3dPart::SetHideShow( )
void AecDbX3dPart::SetHide(bool bHide)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_mapXData.SetAt(_T("Hide"),bHide ? 1 : 0);
}
@@ -631,6 +640,7 @@ bool AecDbX3dPart::IsHide() const
void AecDbX3dPart::PeelOffTheSkin(int nPos)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_nPeelOff = nPos;
if (m_pSolidDraw != NULL)
@@ -648,12 +658,14 @@ bool AecDbX3dPart::IsPeelOffTheSkin() const
void AecDbX3dPart::SetActive(bool bActive)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_bActive = bActive;
}
void AecDbX3dPart::AddMove(const AcGeVector3d& vtMove)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_vtMove += vtMove;
if (m_pSolidDraw != NULL)
@@ -666,6 +678,7 @@ void AecDbX3dPart::AddMove(const AcGeVector3d& vtMove)
void AecDbX3dPart::AddMove(const AcGeMatrix3d& xMirror)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_vtMove += xMirror.translation();
if (m_pSolidDraw != NULL)
@@ -683,6 +696,7 @@ AcGeVector3d AecDbX3dPart::GetMove() const
void AecDbX3dPart::ResetMove()
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_vtMove = AcGeVector3d::kIdentity;
if (m_pSolidDraw != NULL)
@@ -695,6 +709,7 @@ void AecDbX3dPart::ResetMove()
void AecDbX3dPart::AddRotate(int nAxisIndex,double dRot)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
if (nAxisIndex >= 0 && nAxisIndex <= MAX_AXISROTATS )
{
m_dAxisRotats[nAxisIndex] += dRot;
@@ -719,6 +734,7 @@ double AecDbX3dPart::GetRotate(int nAxisIndex) const
void AecDbX3dPart::ResetRotate(int nAxisIndex)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
if (nAxisIndex >= 0 && nAxisIndex < MAX_AXISROTATS)
{
m_dAxisRotats[nAxisIndex] = 0.0;
@@ -781,6 +797,7 @@ void AecDbX3dPart::Transformby(AcGeVector3d& vt) const
void AecDbX3dPart::SetBoolOperFor(int nOper)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
m_nBoolOper = nOper;
}
@@ -808,6 +825,10 @@ bool AecDbX3dPart::Create()
bool AecDbX3dPart::Rebuild()
{
// 已尝试过:即使产出 NULL(Hide 构件、子项全空)也不再反复重试;
// 参数变化时 setter 会把 m_bCreated 置回 false
m_bCreated = true;
//先清标志再重建,避免构建过程中再次触发 EnsureCreated 造成递归
m_bNeedCreate = false;
@@ -928,10 +949,28 @@ bool AecDbX3dPart::Rebuild()
bool AecDbX3dPart::EnsureCreated() const
{
if (!m_bNeedCreate && m_pSolid != NULL)
// 命中条件必须包含"已经尝试建过":Hide 构件与子项为空的构件,
// m_pSolid 按设计就是 NULL,否则会每次访问都重走一遍 Rebuild
if (!m_bNeedCreate && (m_bCreated || m_pSolid != NULL))
{
SectionPerf::NoteEnsure("X3dPart", this, false);
return true;
}
return const_cast<AecDbX3dPart*>(this)->Rebuild();
SectionPerf::NoteEnsure("X3dPart", this, true);
const bool bOk = const_cast<AecDbX3dPart*>(this)->Rebuild();
SectionPerf::NoteEnsure(m_pSolid != NULL ? "X3dPart.rebuild_ok" : "X3dPart.rebuild_null", this, false);
if (m_pSolid == NULL)
{
// 只打前几条,方便定位是哪些构件建不出实体
static int s_nLogged = 0;
if (s_nLogged < 5)
{
++s_nLogged;
acutPrintf(_T("\n[X3dPart] \"%s\" 重建后无实体(隐藏或子项为空),不再重试。"), (LPCTSTR)m_sName);
}
}
return bOk;
}
int AecDbX3dPart::GetForHouseSubtract(AcDbVoidPtrArray& arSolidPtrs) const
@@ -1298,6 +1337,7 @@ Acad::ErrorStatus AecDbX3dPart::dwgOutFields(AcDbDwgFiler* pFiler) const
Acad::ErrorStatus AecDbX3dPart::CopyFrom(AecDbX3dPart* pSrcPart)
{
m_bNeedCreate = true;
m_bCreated = false; // 参数变了,允许下次重建
if (pSrcPart == NULL)
return Acad::eInvalidInput;
@@ -36,6 +36,17 @@ namespace
std::set<unsigned long long> seen;
};
// 每个"延迟建几何"缓存点的统计
struct EnsureStat
{
EnsureStat() : nTotal(0), nHits(0), nRebuilds(0) {}
unsigned long long nTotal;
unsigned long long nHits;
unsigned long long nRebuilds;
std::map<const void*, int> builds; // 对象 -> 被建次数
};
struct Stats
{
Stats()
@@ -66,6 +77,9 @@ namespace
// 属性 key:class 名 -> 唯一/命中统计
std::map<std::string, KeyStat> attrStats;
// 延迟建几何:tag -> 统计
std::map<std::string, EnsureStat> ensureStats;
// 回写次数:实体类 -> 次数
std::map<std::string, unsigned long long> writeBackByClass;
};
@@ -274,6 +288,25 @@ namespace SectionPerf
++s.writeBackByClass[key];
}
void NoteEnsure(const char* pszTag, const void* pObj, bool bRebuilt)
{
Stats& s = GetStats();
if (!s.bEnabled)
return;
std::string key(pszTag != NULL ? pszTag : "?");
EnsureStat& st = s.ensureStats[key];
++st.nTotal;
if (bRebuilt)
{
++st.nRebuilds;
++st.builds[pObj];
}
else
{
++st.nHits;
}
}
void Dump()
{
Stats& s = GetStats();
@@ -300,6 +333,40 @@ namespace SectionPerf
s.nTempFiles, s.nTempFileBytes, s.nWriteBacks);
out += szLine;
if (!s.ensureStats.empty())
{
out += "---- lazy-create cache (by tag) ----\n";
out += " tag total hits rebuild hit_rate objects built_once built_2_3 built_4p\n";
for (std::map<std::string, EnsureStat>::const_iterator it = s.ensureStats.begin();
it != s.ensureStats.end(); ++it)
{
const EnsureStat& e = it->second;
int nOnce = 0, n23 = 0, n4p = 0;
for (std::map<const void*, int>::const_iterator b = e.builds.begin(); b != e.builds.end(); ++b)
{
if (b->second <= 1) ++nOnce;
else if (b->second <= 3) ++n23;
else ++n4p;
}
const double rate = e.nTotal ? (100.0 * (double)e.nHits / (double)e.nTotal) : 0.0;
sprintf_s(szLine, sizeof(szLine), " %-12s %-9llu %-7llu %-8llu %7.1f%% %-7llu %-11d %-10d %d\n",
it->first.c_str(), e.nTotal, e.nHits, e.nRebuilds, rate,
(unsigned long long)e.builds.size(), nOnce, n23, n4p);
out += szLine;
// top offenders:被重建次数最多的对象
const void* pWorst = NULL; int nWorst = 0;
for (std::map<const void*, int>::const_iterator b = e.builds.begin(); b != e.builds.end(); ++b)
{
if (b->second > nWorst) { nWorst = b->second; pWorst = b->first; }
}
if (nWorst > 3)
{
sprintf_s(szLine, sizeof(szLine), " worst object %p rebuilt %d times\n", pWorst, nWorst);
out += szLine;
}
}
}
if (!s.writeBackByClass.empty())
{
out += "---- write-backs by entity class ----\n";
@@ -457,5 +524,6 @@ namespace SectionPerf
s.nWriteBacks = 0;
s.attrStats.clear();
s.writeBackByClass.clear();
s.ensureStats.clear();
}
}
+4
View File
@@ -142,6 +142,10 @@ protected:
int m_nPeelOff; //是否需要剖开
AcDb3dSolid* m_pSolid; //原始solid元
mutable bool m_bNeedCreate; //几何待重建,首次使用时再算
// 是否已经"尝试建过"(无论是否产出实体)。用于区分两种情况:
// Hide 构件、或子项全是空 —— m_pSolid 按设计就是 NULL,但不能再反复重试;
// 参数变化时 m_bNeedCreate 会重新置 true,下次访问照常重建。
bool m_bCreated;
mutable AcDb3dSolid* m_pSolidDraw; //临时绘制solid元
mutable SHJson::WValue m_json;
};
@@ -73,6 +73,10 @@ namespace SectionPerf
// 记录一次回写(Region -> SAT 文本)
void NoteWriteBack(const char* pszTag);
// 统计"延迟建几何"的命中情况:同一对象被建了几次。
// pszTag 区分不同缓存点(如 X3dSolid / X3dPart),pObj 是对象指针。
void NoteEnsure(const char* pszTag, const void* pObj, bool bRebuilt);
// 记录一次回写,并按实体类分开统计(谁在开图期回写)
void NoteWriteBackClass(const char* pszClass);