一次开图要做 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 回写(与本缓存无关,需单独立项)。
66 lines
2.6 KiB
C++
66 lines
2.6 KiB
C++
// SectionRegionCache.h —— 截面(Region)母体缓存
|
||
//
|
||
// 背景(见 pipe-section-region-performance-plan.md):
|
||
// 一次开图实测要做 5641 次截面恢复(22.9 s),而其中大量内容重复。
|
||
// 恢复一次 = 写临时 SAT 文件 + 从盘 acisIn + 删文件 + 杀软扫描,单次 2.7~5.5 ms。
|
||
//
|
||
// 三个通道,按"key 的正确性强度"分开用:
|
||
// kParam —— 纯参数函数(如 AecDbEwFitting::GetSection(dD, dt)),同参数必同几何,最强
|
||
// kAttr —— 实体属性 key(仅用于"同数据必然同截面"的类,目前只有 AecDbEwPipe)
|
||
// kText —— SAT 文本内容 hash,对所有类都成立(字节相同即几何相同)
|
||
//
|
||
// 约定:
|
||
// - 缓存持有"母体",永不外泄;Acquire/Put 都返回 clone,调用方拥有返回值。
|
||
// - 母体不入库、不参与绘制,只在缓存里待着。
|
||
// - 每次访问都 clone(一次区域深拷贝,远低于一次文件往返)。
|
||
// - 环境变量 AEC_SECTION_CACHE=0 可整体关闭(用于 A/B 对比)。
|
||
|
||
#ifndef SECTION_REGION_CACHE_H
|
||
#define SECTION_REGION_CACHE_H
|
||
|
||
#include <stddef.h>
|
||
|
||
class AcDbRegion;
|
||
class AecDbObjXDataMap;
|
||
|
||
namespace SectionRegionCache
|
||
{
|
||
enum Channel
|
||
{
|
||
kText = 0, // SAT 文本内容 hash
|
||
kAttr = 1, // 实体属性 hash(仅管道)
|
||
kParam = 2, // 纯参数 hash(管件 GetSection)
|
||
kChannelCount = 3
|
||
};
|
||
|
||
// 默认开启;AEC_SECTION_CACHE=0 关闭
|
||
bool Enabled();
|
||
|
||
// FNV-1a 64 位
|
||
unsigned long long HashBytes(const void* pData, size_t nLen);
|
||
|
||
// SAT 文本哈希:跳过 ACIS 头里的时间戳行。
|
||
// 实测同一几何存盘时间不同会让整份文本不同,直接用内容 hash 会几乎全部判成"唯一"。
|
||
unsigned long long HashSatText(const void* pData, size_t nLen);
|
||
|
||
// 属性 key:类名 + 全部 spec XData,排除编号("No."),
|
||
// 并跳过 Id / 点 / 向量 / 矩阵 / 二进制类型(与截面无关或随实体变化)。
|
||
unsigned long long BuildSpecAttrHash(const AecDbObjXDataMap& map, const TCHAR* pszClass);
|
||
|
||
// 命中返回 clone;未命中返回 NULL
|
||
// pSampleData/nSampleLen 仅在诊断(AEC_SAT_SAMPLE)时用于导出未命中的原文,可传 NULL
|
||
AcDbRegion* Acquire(Channel ch, unsigned long long nKey,
|
||
const void* pSampleData = NULL, size_t nSampleLen = 0);
|
||
|
||
// 接管 pMaster 所有权并返回它的 clone(未命中后的入库路径)
|
||
AcDbRegion* Put(Channel ch, unsigned long long nKey, AcDbRegion* pMaster);
|
||
|
||
// 打印各通道命中率与条目数
|
||
void Dump();
|
||
|
||
// 清空并释放全部母体(卸载时调用,避免 DLL 析构顺序问题)
|
||
void ClearAll();
|
||
}
|
||
|
||
#endif // SECTION_REGION_CACHE_H
|