上一版只抓到"绘制期深拷贝"的调用栈,还没回答"是哪个类的实体在回写"。
现在 SectionPerf 增加按类统计的写回计数(NoteWriteBackClass),
在 RegionAcisoutDwgOutFields 里记一次 isA()->name()。
实测(big1.dwg,开图一次):
---- write-backs by entity class ----
1140 AecDbEwPipeBase
全部 1140 次都是 AecDbEwPipeBase 基类实例,不是 AecDbEwPipe 也不是具名子类,
与属性 key 统计里 AecDbEwPipeBase 的 1172 次几乎一一对应——
说明图纸里约有 1172 个基类实例(由 AecDbAssembleWell.cpp 的装配井代码创建,
多为矩形截面段),绘制期每个被深拷贝一次。
完整链路(已写入计划文档):
base_ViewportDraw -> AecDbCurveBase::subGetTransformedCopy
(AecRxBase/AecDbCurveBase.cpp:307 里的 clone())
-> AecDbEntityCommon::base_DeepClone(AecDbEntityCommon.cpp:1232,dwgOut 在 1265)
-> AecDbEwPipeBase::dwgOutFields(AecDbEwPipeBase.cpp:1440)
-> AecDbEwPipeBase::RegionAcisoutDwgOutFields(AecDbEwPipeBase.cpp:1275)
顺带发现:AecDbEwPipeBase::copyFrom(:1605)复制截面用的是 m_pRegion->copyFrom(),
是内存级 ACIS 拷贝,比"写文件再读回"便宜得多。所以最局部的修法可能是给管系实体
加 subGetTransformedCopy 重载,用 isA()->create()+copyFrom() 代替 clone(),
绕开整条序列化链路;但 clone 与 copyFrom 语义不完全等价,需先核对。
本次只记录分析,不改行为。
验证:R230 Debug 编译通过并部署,脚本自动开图一次得到上述分类统计。
93 lines
3.0 KiB
C++
93 lines
3.0 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 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
|