diff --git a/SourceCode/Code2026/CoreLibrary/AcDbX3dSolid.cpp b/SourceCode/Code2026/CoreLibrary/AcDbX3dSolid.cpp index 5b181d4..5aebd55 100644 --- a/SourceCode/Code2026/CoreLibrary/AcDbX3dSolid.cpp +++ b/SourceCode/Code2026/CoreLibrary/AcDbX3dSolid.cpp @@ -2,6 +2,7 @@ #include #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; diff --git a/SourceCode/Code2026/CoreLibrary/AecDbGuardBar.cpp b/SourceCode/Code2026/CoreLibrary/AecDbGuardBar.cpp index 50f90c5..283c64f 100644 --- a/SourceCode/Code2026/CoreLibrary/AecDbGuardBar.cpp +++ b/SourceCode/Code2026/CoreLibrary/AecDbGuardBar.cpp @@ -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(); diff --git a/SourceCode/Code2026/CoreLibrary/AecDbX3dPart.cpp b/SourceCode/Code2026/CoreLibrary/AecDbX3dPart.cpp index 5958799..2197be9 100644 --- a/SourceCode/Code2026/CoreLibrary/AecDbX3dPart.cpp +++ b/SourceCode/Code2026/CoreLibrary/AecDbX3dPart.cpp @@ -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(this)->Rebuild(); + SectionPerf::NoteEnsure("X3dPart", this, true); + const bool bOk = const_cast(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; diff --git a/SourceCode/Code2026/CoreLibrary/SectionPerfCounter.cpp b/SourceCode/Code2026/CoreLibrary/SectionPerfCounter.cpp index 7f5c4b2..0eddee2 100644 --- a/SourceCode/Code2026/CoreLibrary/SectionPerfCounter.cpp +++ b/SourceCode/Code2026/CoreLibrary/SectionPerfCounter.cpp @@ -36,6 +36,17 @@ namespace std::set seen; }; + // 每个"延迟建几何"缓存点的统计 + struct EnsureStat + { + EnsureStat() : nTotal(0), nHits(0), nRebuilds(0) {} + + unsigned long long nTotal; + unsigned long long nHits; + unsigned long long nRebuilds; + std::map builds; // 对象 -> 被建次数 + }; + struct Stats { Stats() @@ -66,6 +77,9 @@ namespace // 属性 key:class 名 -> 唯一/命中统计 std::map attrStats; + // 延迟建几何:tag -> 统计 + std::map ensureStats; + // 回写次数:实体类 -> 次数 std::map 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::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_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_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(); } } diff --git a/SourceCode/Code2026/inc/AecDbX3dPart.h b/SourceCode/Code2026/inc/AecDbX3dPart.h index f32b774..6f3c771 100644 --- a/SourceCode/Code2026/inc/AecDbX3dPart.h +++ b/SourceCode/Code2026/inc/AecDbX3dPart.h @@ -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; }; diff --git a/SourceCode/Code2026/inc/SectionPerfCounter.h b/SourceCode/Code2026/inc/SectionPerfCounter.h index e27603f..654cf63 100644 --- a/SourceCode/Code2026/inc/SectionPerfCounter.h +++ b/SourceCode/Code2026/inc/SectionPerfCounter.h @@ -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); diff --git a/tools/run_acad_perf.ps1 b/tools/run_acad_perf.ps1 index 3135eab..f41f3f4 100644 --- a/tools/run_acad_perf.ps1 +++ b/tools/run_acad_perf.ps1 @@ -23,7 +23,8 @@ param( [string]$Profile = 'SHS-ENVI2026', [string]$Dwg = 'E:\big1.dwg', [int] $TimeoutSec = 900, - [switch]$CacheOff + [switch]$CacheOff, + [string]$CpuTrace = '' ) $ErrorActionPreference = 'Continue' @@ -41,6 +42,18 @@ Write-Output ("report : " + $report) $existing = @(Get-Process acad -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id) +$xperf = 'C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\xperf.exe' +$rawEtl = '' +if ($CpuTrace) { + if (-not (Test-Path $xperf)) { Write-Output "xperf.exe not found"; exit 1 } + $rawEtl = Join-Path $env:TEMP 'aec_cpu_raw.etl' + Remove-Item $rawEtl -Force -ErrorAction SilentlyContinue + Remove-Item $CpuTrace -Force -ErrorAction SilentlyContinue + & $xperf -on PROC_THREAD+LOADER+PROFILE -stackwalk Profile -BufferSize 1024 -f "$rawEtl" | Out-Null + Start-Sleep -Seconds 2 + Write-Output ("cpu trace: collecting -> " + $CpuTrace) +} + $env:AEC_PERF_COUNTER = '1' if ($CacheOff) { $env:AEC_SECTION_CACHE = '0' } else { $env:AEC_SECTION_CACHE = '1' } Write-Output ("cache : " + $(if ($CacheOff) { 'OFF (A/B baseline)' } else { 'ON' })) @@ -75,6 +88,18 @@ if ($got) { else { Write-Output " process exited (command missing? build without probes?)" } } +if ($CpuTrace) { + Write-Output "stopping cpu trace ..." + & $xperf -stop | Out-Null + Start-Sleep -Seconds 2 + & $xperf -merge "$rawEtl" "$CpuTrace" | Out-Null + if (Test-Path $CpuTrace) { + Write-Output ("[cpu trace] " + $CpuTrace + " (" + [math]::Round((Get-Item $CpuTrace).Length / 1MB, 1) + " MB)") + } else { + Write-Output "[cpu trace] merge failed" + } +} + $after = @(Get-Process acad -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id) foreach ($id in $after) { if ($existing -notcontains $id) {