Files
envi-code/SourceCode/Code2026/inc/SectionPerfCounter.h
T
company-desktop facf14699b 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/正常。
2026-10-28 22:51:01 +08:00

97 lines
3.2 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SectionPerfCounter.h —— Phase 0 计数器:截面(Region)恢复路径的实测探针
//
// 目的(见 pipe-section-region-performance-plan.md 的 Phase 0):
// 1. 每次截面恢复到底多贵(µs),一次开图总共多少次、多少时间;
// 2. 同一份 SAT 文本被重复恢复多少次(唯一 hash 数、模拟命中率)——
// 这个数字直接决定 Phase 1 截面缓存能省多少;
// 3. 临时文件写入次数与累计字节(杀软扫描影响面);
// 4. 放回写(RegionToString / RegionAcisoutDwgOutFields)的次数。
//
// 使用方式:
// set AEC_PERF_COUNTER=1 必须在启动 CAD 前设好(进程内只读一次)
// ENVIPERFDUMP 在 CAD 命令行打印汇总,同时追加到 %TEMP%\aec_section_perf.txt
// ENVIPERFRESET 清零
//
// 只在 _DEBUG 下生效:Release 里 Enabled() 恒为 false,调用点只剩一次 bool 判断。
// 不改变任何几何逻辑与存盘格式。
#ifndef SECTION_PERF_COUNTER_H
#define SECTION_PERF_COUNTER_H
#include <stddef.h>
#include <string>
namespace SectionPerf
{
// 环境变量 AEC_PERF_COUNTER 是否开启,且当前是 Debug 构建
bool Enabled();
// 计时作用域:函数入口构造,离开时累加耗时与次数
class Scope
{
public:
explicit Scope(const char* pszTag);
~Scope();
private:
Scope(const Scope&);
Scope& operator=(const Scope&);
const char* m_pszTag;
long long m_nStart;
bool m_bActive;
};
// 记录一次"待恢复的 SAT 文本":内部按内容算 64 位 hash,
// 统计唯一 hash 数与被重复命中的次数(模拟命中率)
void NoteRegionText(const char* pszTxt, size_t nLen);
// 属性 key:用"实体数据"而不是 SAT 文本做 key 时的模拟命中统计。
// 调用方把参与 key 的字段逐个 Add 进来,析构时统一记一次。
// 用途:比较"内容 hash"与"属性 key"两种缓存键的命中率,再决定 Phase 1 用哪个。
class AttrKey
{
public:
// pszClass 一般是 isA()->name(),用于隔离不同实体类
explicit AttrKey(const TCHAR* pszClass);
~AttrKey();
void Add(const void* pData, size_t nLen);
private:
AttrKey(const AttrKey&);
AttrKey& operator=(const AttrKey&);
unsigned long long m_nHash;
std::string m_sClass;
bool m_bActive;
};
// 记录一次临时文件写入
void NoteTempFile(size_t nBytes);
// 记录一次回写(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);
// 打印 + 写文件
void Dump();
// 只追加文本到 %TEMP%\aec_section_perf.txt(供其它模块复用同一份报告)
void AppendReport(const char* pszText);
// 抓一次调用栈写进报告,用于定位"谁在开图期回写实体"这类问题
void TraceCallStack(const char* pszTag, int nSkip = 1);
// 清零
void Reset();
}
#endif // SECTION_PERF_COUNTER_H