一次开图要做 5641 次截面恢复(22.4 s),其中大量内容重复,
而每次恢复都是"写临时 SAT 文件 + 从盘 acisIn + 删文件",单次 2.7~5.5 ms,
还要过一次杀软。本提交加一级母体缓存,命中直接 clone 返回。
新增 CoreLibrary/SectionRegionCache.{h,cpp},三个通道按 key 的正确性强度分开:
- kParam:纯参数 hash,用于 AecDbEwFitting::GetSection 的两个重载
(key = dD/dt/HLR 标志 或 W/H/dt/HLR 标志,同参数必同几何)
- kAttr:类名 + spec XData(排除编号,并跳过 Id/点/向量/矩阵/二进制类型),
只给 AecDbEwPipe 用。AecDbEwPipeBase 是很多类的基类,其余派生类实例间的
区别就是截面,不满足"同数据必同截面",所以一律走内容 hash
- kText:SAT 文本内容 hash,用于 RegionAcisInDwgInFields 的其它类
与 CoreTools::StringToRegion 的两个重载
约定:母体由缓存持有、永不外泄,Acquire/Put 都返回 clone,调用方拥有返回值;
母体不入库;每条通道 4096 条上限(AEC_SECTION_CACHE_MAX 可调),超出按 FIFO 淘汰;
AEC_SECTION_CACHE=0 可整体关闭做 A/B;卸载时 ClearAll() 释放,避免 DLL 析构顺序问题。
关键发现:SAT 文本第 2 行带存盘时间戳
16 Autodesk AutoCAD 19 ASM 219.0.0.5600 NT 24 Thu Oct 22 10:19:20 2026
同一几何存盘时间不同,整份文本就不同(采样里两份 2596 字节的文本只差
10:19:20 与 10:19:16),这正是内容 hash 命中率只有 54% 的原因,与截面无关。
因此新增 HashSatText():算 hash 时跳过第 2 行,仅当该行含 "Autodesk" 时才跳过。
计数器也改用同一口径,避免两套统计互相矛盾。
实测(big1.dwg / AutoCAD 2020 / R230 Debug,AEC_SECTION_CACHE 关 vs 开):
开图墙钟 108~113 s -> 98 s
截面恢复总耗时 22.4 s -> 9.87 s(-56%)
StringToRegion 14.05 s -> 7.73 s
RegionAcisIn 8.38 s -> 2.14 s
临时文件 6773 次/9.34MB -> 2875 次/3.61MB
命中率 text 65.5% / attr 84.1% / param 88.8%
clone 开销 命中 58us、未命中 73us(合计 0.37 s,可忽略)
顺带:
- tools/run_acad_perf.ps1 增加 -CacheOff 开关,用于同一构建上的 A/B 对比
- SectionPerfCounter 增加 AppendReport(),让缓存统计也写进同一份报告文件
- 诊断口子 AEC_SAT_SAMPLE=n 可导出未命中的 SAT 原文,用于定位这类差异
验证:R230 Debug 编译通过并部署;用脚本自动开图各跑一次(缓存关/开),
数据已写入 pipe-section-region-performance-plan.md。
尚未做:R190 编译(本机缺 v100 工具集)、Release 构建的收益复测、
开图期间 1140 次 RegionAcisout 回写(与本缓存无关,需单独立项)。
87 lines
2.7 KiB
C++
87 lines
2.7 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%ec_section_perf.txt(供其它模块复用同一份报告)
|
||
void AppendReport(const char* pszText);
|
||
|
||
// 清零
|
||
void Reset();
|
||
}
|
||
|
||
#endif // SECTION_PERF_COUNTER_H
|