意图:内容 hash 只看到 54% 命中率,但业务上"同数据的管子/管件截面必然相同",
所以要用实体属性做 key 再量一次,决定 Phase 1 到底用哪种缓存键。
新增 SectionPerf::AttrKey(RAII):
- 构造时按类名(isA()->name())初始化 hash 与统计桶,析构时记一次
- Add() 增量喂字节,调用方决定哪些字段参与 key
- 统计按实体类分开(total / unique / hits / hit_rate)
接入 AecDbEwPipeBase::RegionAcisInDwgInFields:key = 类名 + 全部 spec XData,
但排除编号("No.",每根管子唯一)并跳过 Id / 点 / 向量 / 矩阵 / 二进制类型
(与截面无关或随实体变化)。只在 AEC_PERF_COUNTER=1 时构建,Release 无开销。
实测(big1.dwg,AutoCAD 2020 / R230 Debug,113 s 开图):
属性 key 合计 3082 次 / 2672 命中 = 86.7%
AecDbEwPipe 1088 次 173 唯一 84.1%
AecDbEwPipeBase 1172 次 3 唯一 99.7%
(其余类 43%~99%)
内容 hash 54.0%
结论:属性 key 显著优于内容 hash,但只覆盖 3082/5641 次恢复
(另 2559 次走 CoreTools::StringToRegion,拿不到 spec 数据,只能用内容 hash)。
混合策略预计省 14.9 s / 22.6 s 恢复开销,约占 113 s 开图的 13%。
数据已写入 pipe-section-region-performance-plan.md,并标注一个未验证点:
属性 key 命中时两份 SAT 文本是否真的相同(决定 Phase 1 是否需要 textHash 兜底校验)。
验证:R230 Debug 编译通过并部署,用 tools/run_acad_perf.ps1 自动跑图取数。
84 lines
2.6 KiB
C++
84 lines
2.6 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();
|
||
|
||
// 清零
|
||
void Reset();
|
||
}
|
||
|
||
#endif // SECTION_PERF_COUNTER_H
|