Files
envi-code/SourceCode/Code2026/inc/SectionPerfCounter.h
T
company-desktop 67d6545e6c diag: 增加调用栈抓取,定位开图期 1140 次截面回写的成因
现象:开图期间 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 跑图一次,栈如文档所示。
2026-10-28 20:49:42 +08:00

90 lines
2.9 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);
// 打印 + 写文件
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