现象:开图期间 EwPipe::RegionAcisout 被调用 1140 次、耗时 2.38 s, 把几何又序列化回 SAT。之前只知现象不知来源。 新增 SectionPerf::TraceCallStack():用 RtlCaptureStackBackTrace + GetModuleHandleExW 把栈帧解析成 "模块名 + RVA" 并写进报告文件。正式代码里不开启, 只在 AEC_WRITEBACK_TRACE=1 时于 RegionAcisoutDwgOutFields 首次调用处抓一次。 结论(栈已定位): subViewportDraw -> base_ViewportDraw -> ... -> base_DeepClone -> AcDbObject::dwgOut -> AecDbEwPipeBase::dwgOutFields -> RegionAcisout 即:不是保存,而是绘制期的深拷贝。AecDbEntityCommon::base_DeepClone (AecRxBase/AecDbEntityCommon.cpp:1265)用 AcDbDeepCloneFiler filer; GetMe()->dwgOut(&filer); pClone->dwgIn(&filer); 实现拷贝,于是每次克隆实体,截面都要写一个临时 SAT 文件再读回来。 读的那一半现在已被 kText 缓存吃掉,剩下 acisOut 写文件这半(2.38 s)。 顺带修正一个文档缺陷:inc/SectionPerfCounter.h 里 %TEMP%\aec_section_perf.txt 的注释之前被转义吃掉变成了 %TEMP%<BEL>ec_section_perf.txt。 修法(跳过深拷贝期的区域序列化 / 直接写缓存里的 SAT 原文)都需要动 AecDbEntityCommon 的拷贝契约或先审计 m_pRegion 的写操作,风险面不一致, 只把分析与三种方案写进 pipe-section-region-performance-plan.md,本次不改行为。 验证:R230 Debug 编译通过并部署;AEC_WRITEBACK_TRACE=1 跑图一次,栈如文档所示。
90 lines
2.9 KiB
C++
90 lines
2.9 KiB
C++
// 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);
|
||
|
||
// 打印 + 写文件
|
||
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
|