company-desktop
|
6d6f9ddaff
|
diag: 统计回写的实体类,定位到 AecDbEwPipeBase 基类实例
上一版只抓到"绘制期深拷贝"的调用栈,还没回答"是哪个类的实体在回写"。
现在 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 编译通过并部署,脚本自动开图一次得到上述分类统计。
|
2026-10-28 21:06:22 +08:00 |
|
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 |
|
company-desktop
|
f8d10d5a3b
|
perf: 增加截面 Region 缓存,开图恢复耗时降 56%
一次开图要做 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 回写(与本缓存无关,需单独立项)。
|
2026-10-28 20:41:05 +08:00 |
|
company-desktop
|
764e544db4
|
perf: 增加属性 key 命中率统计(Phase 0 补充)
意图:内容 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 自动跑图取数。
|
2026-10-28 20:06:06 +08:00 |
|
company-desktop
|
3f387485fd
|
fix: 计数器改用 NDEBUG 判断构建类型(本仓库把 _DEBUG 剥掉了)
现象:Debug 构建的 R230 里 ENVIPERFDUMP 报"未知命令",计数器也永远不生效。
根因:cmakelists.txt 第 23~24 行会把 /D_DEBUG 从 CMAKE_CXX_FLAGS_DEBUG 里正则剥掉,
所以即使 --config Debug 编译,_DEBUG 也没有定义。于是 Phase 0 里用
`#if defined(_DEBUG)` 包住的命令注册和 Enabled() 判定整块被编掉——
已用二进制验证:修复前 arx 里查不到 ENVIPERFDUMP,修复后能查到(UTF-16 字符串)。
修复:改用 NDEBUG。cmakelists.txt 只对 Release 定义 NDEBUG
(`$<$<CONFIG:Release>:NDEBUG>`),所以 `#ifndef NDEBUG` 正好等价于"Debug 构建"。
- CoreLibrary/SectionPerfCounter.cpp:计数器启用条件
- CoreCommand/EarthWorm.cpp:ENVIPERFDUMP / ENVIPERFRESET 的函数定义与命令注册
验证:R230 Debug 两个 target 重新编译通过,产物已部署;
脚本自动开图后 ENVIPERFDUMP 正常输出(数据已记录到计划文档 Phase 0 实测结果)。
已知风险:`#ifdef _DEBUG` 在本仓库本来就不生效,rxdebug.cpp 等历史代码同样如此,
本次只修自己新增的两处,没有动别处。
|
2026-10-28 19:52:18 +08:00 |
|
company-desktop
|
3afc2dd7c1
|
perf: 增加截面恢复路径的 Phase 0 计数器(Debug 探针)
按 pipe-section-region-performance-plan.md 的 Phase 0 落地。目的不是提速,而是先拿到
三个决定性数字,再判断截面缓存(Phase 1)到底值不值得做:
1) 每次截面恢复多贵(µs),一次开图共多少次;
2) 同一份 SAT 文本被重复恢复多少次(唯一 hash 数 / 模拟命中率)——直接决定缓存收益上限;
3) 临时文件写入次数与字节数(杀软扫描影响面),以及打开图纸时的回写次数。
改动:
- 新增 inc/SectionPerfCounter.h + CoreLibrary/SectionPerfCounter.cpp
- Scope(QPC 计时)+ NoteRegionText(FNV-1a 64 位 hash,统计唯一数与模拟命中)
+ NoteTempFile + NoteWriteBack + Dump/Reset
- 计数器实例放在函数内 static:CoreLibrary 是静态库且只被 CoreCommand 链接,
所以 CoreCommand.arx 里只有一份,两个入口共用
- AecDbEwPipeBase.cpp:RegionAcisInDwgInFields / RegionAcisoutDwgOutFields 接入
- CoreTools.cpp:StringToRegion 两个重载 / RegionToString 接入
- EarthWorm.cpp:注册 ENVIPERFDUMP / ENVIPERFRESET(仅 Debug)
- CoreLibrary/cmakelists.txt 加一个源文件
- 计划文档的 Phase 0 补上实际命令与采集项
开关:启动 CAD 前 set AEC_PERF_COUNTER=1;命令 ENVIPERFDUMP 打印并追加到
%TEMP%\aec_section_perf.txt,ENVIPERFRESET 清零。
Release 下 Enabled() 恒为 false,调用点只剩一次 bool 判断。
不改任何几何逻辑与存盘格式。
验证:R230 Debug 编译通过(CoreLibrary_BAL_REMOTE、CoreCommand_BAL_REMOTE,无新增 warning),
产物已自动部署到 c:\ShangHuaSoft\Envi3D-2026-M\R230\。
R190 未在本机编译(缺 v100 工具集);代码刻意只用 C++98 特性,控制台输出改走 %hs,
避开了 &wstring[0] 这类老 STL 上的写法问题。
尚未做 CAD 实测:需要开图后跑 ENVIPERFDUMP 才看得到数据。
|
2026-10-28 19:31:27 +08:00 |
|