docs: 记录 Phase 0 实测结果、AutoCAD 自动化实测环境与属性 key 的取舍 #98
No files matched your search
@@ -32,6 +32,30 @@
|
||||
里面记录了很多"已经决定不做"的理由,别重复踩。
|
||||
- 构建命令与 target 对照见 `CODEBUDDY.md`,或直接用 skill `build-arx`。
|
||||
|
||||
## 自动化 CAD 实测
|
||||
|
||||
可以无人值守跑一次「开图 → 执行命令 → 收结果 → 关进程」。本机环境:
|
||||
|
||||
| 项 | 值 |
|
||||
| --- | --- |
|
||||
| AutoCAD | `c:\Program Files\Autodesk\AutoCAD 2020\acad.exe`(= R23.0,对应 preset **R230**) |
|
||||
| 启动参数 | `/p SHS-ENVI2026`(试验用 profile,会自动加载本仓库的 arx) |
|
||||
| 测试图纸 | `e:\桌面\big1.dwg`(约 8 MB,一次开图 ≈ 108 s) |
|
||||
| 部署目录 | `c:\ShangHuaSoft\Envi3D-2026-M\R230\`(Debug 构建会自动部署到这里) |
|
||||
|
||||
一键脚本:`tools/run_acad_perf.ps1`
|
||||
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File tools\run_acad_perf.ps1
|
||||
```
|
||||
|
||||
它的做法:写一个只含 `ENVIPERFDUMP` 的 `.scr`,用 `/b` 让 AutoCAD 在**图纸载入完成后**执行;
|
||||
报告落 `%TEMP%\aec_section_perf.txt`;收完报告按 PID 结束**本次拉起的** acad,
|
||||
脚本里不写 QUIT,所以不会弹保存对话框、不会回写图纸。
|
||||
|
||||
写这类 `.ps1` 时注意:**Windows PowerShell 5.1 把无 BOM 的 UTF-8 当 ANSI 读**,
|
||||
带中文会直接语法错误——要么保存为 UTF-8 with BOM,要么脚本全用 ASCII(推荐)。
|
||||
|
||||
## 需要用户决策时
|
||||
|
||||
不要把问题打在终端里就停住——先跑 `tools/notify_decision.ps1`(用 `-MessageFile` 传中文),
|
||||
|
||||
@@ -544,7 +544,7 @@ KTDocReactor *g_pDocReactor(NULL);
|
||||
#define ADD_CMD(cmd, func) CoreTools::AddCommand(_T("KTEARTHWORMCORE"), _T(#cmd), _T(#cmd), ACRX_CMD_TRANSPARENT | ACRX_CMD_USEPICKSET, func)
|
||||
#define ADD_CMD_EX(cmd, func, flag) CoreTools::AddCommand(_T("KTEARTHWORMCORE"), _T(#cmd), _T(#cmd), flag, func)
|
||||
|
||||
#if defined(_DEBUG)
|
||||
#ifndef NDEBUG
|
||||
// Phase 0:截面恢复计数器。实现见 CoreLibrary/SectionPerfCounter.cpp
|
||||
void envierpf_dump()
|
||||
{
|
||||
@@ -1081,7 +1081,7 @@ void InitApplication()
|
||||
void pzjcs();
|
||||
ADD_CMD(co_pzjcs, pzjcs);
|
||||
|
||||
#if defined(_DEBUG)
|
||||
#ifndef NDEBUG
|
||||
// Phase 0:截面恢复计数器(仅 Debug,且需启动前设 AEC_PERF_COUNTER=1)
|
||||
ADD_CMD(ENVIPERFDUMP, envierpf_dump);
|
||||
ADD_CMD(ENVIPERFRESET, envierpf_reset);
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace
|
||||
if (!s_stats.bInited)
|
||||
{
|
||||
s_stats.bInited = true;
|
||||
#if defined(_DEBUG)
|
||||
#ifndef NDEBUG
|
||||
const char* psz = ::getenv("AEC_PERF_COUNTER");
|
||||
s_stats.bEnabled = (psz != NULL && psz[0] != '\0' && psz[0] != '0');
|
||||
#else
|
||||
|
||||
@@ -116,12 +116,64 @@ Release 里 `SectionPerf::Enabled()` 恒为 false,调用点只剩一次 bool
|
||||
- 是 → Phase 1 直接做
|
||||
- 否(每个实体文本都不同)→ 停下重新评估(此时缓存收益接近 0)
|
||||
|
||||
#### Phase 0 实测结果(2026-10-28,big1.dwg / AutoCAD 2020 / R230 Debug)
|
||||
|
||||
自动跑图:`tools/run_acad_perf.ps1`(等价于 `acad.exe /p SHS-ENVI2026 /b <dump.scr> big1.dwg`),
|
||||
开图到出报告 **108 s**。
|
||||
|
||||
```
|
||||
RegionTexts=5641 UniqueHashes=2597 SimulatedHits=3044 HitRate=54.0%
|
||||
TempFiles=6773 TempBytes=9,341,125 WriteBacks=1140
|
||||
|
||||
---- timing (by tag) ----
|
||||
count total_ms avg_us max_us tag
|
||||
2559 14620.1 5713.2 320828 CoreTools::StringToRegion
|
||||
3082 8315.3 2698.0 17447 EwPipe::RegionAcisIn
|
||||
1140 2282.1 2001.8 35390 EwPipe::RegionAcisout
|
||||
```
|
||||
|
||||
解读:
|
||||
|
||||
| 结论 | 数值 |
|
||||
| --- | --- |
|
||||
| 一次开图做 SAT 恢复 | **5641 次**(此前基于采样的估算只有 550~1000,低估了 5~10 倍) |
|
||||
| 恢复总耗时 | **22.9 s**(14.6 + 8.3),占 108 s 开图约 21 % |
|
||||
| 单次成本 | 2.7~5.7 ms,实为「写临时文件 + 从盘 `acisIn` + 删」而非内存解析 |
|
||||
| 极端值 | 单次最长 321 ms(`StringToRegion`) |
|
||||
| 唯一截面内容 | 2597 种 → **命中率 54.0 %** |
|
||||
| 临时文件 | 6773 次写入 / 8.9 MB(每次都过杀软) |
|
||||
| 打开时仍在回写 | **1140 次 / 2.3 s**,开图期间不应发生的序列化 |
|
||||
|
||||
**D0 结论:值得做。** 按命中率折算 3044 次命中 × 加权均值 4.07 ms ≈ 省 12.4 s,
|
||||
减去 clone 成本(几十 µs 级,合计约 0.1~0.3 s)→ **净省约 12 s / 108 s ≈ 11 %**,
|
||||
并少约 4000 次临时文件写盘。与基于 trace 的估算(10~14 %)吻合。
|
||||
|
||||
三个待追查项(不阻塞 Phase 1):
|
||||
|
||||
1. **命中率只有 54 %**。2597 种唯一文本对 5641 次恢复,说明同 DN 的 SAT 文本并非完全一致。
|
||||
需确认差异是"真的不同几何"(不同管径/壁厚/异形)还是"带了绝对坐标等无关内容"。
|
||||
前者属性 key 也救不了;后者归一化后命中率有望到 80 %+。
|
||||
2. `StringToRegion` 单次成本是 `RegionAcisIn` 的 2 倍(多一步 `fixACISFormat` 改写文本)。
|
||||
3. **开图期间回写 1140 次 SAT(2.3 s)** 属于纯亏,应单独立项查清来源。
|
||||
|
||||
### Phase 1:截面内容缓存(核心,全平台)
|
||||
|
||||
新增一个缓存模块(建议 `CoreLibrary/SectionRegionCache.h/.cpp`,私有实现放匿名 namespace):
|
||||
|
||||
- **key**:输入 SAT 文本字节的 64 位 hash(自实现 FNV-1a/xxHash,不引第三方),长度参与计算。
|
||||
→ **内容寻址,不需要任何失效逻辑**:样式/参数变了,文本变了,key 自然变。
|
||||
→ 选它的核心理由是**可证明正确**:字节相同即几何相同,不存在"属性漏项导致复用错几何"的风险。
|
||||
- **关于"用管子属性做 key"**(业务上更自然的思路,实测命中率可能高于内容 hash):
|
||||
属性相同的管子截面必然相同,但**不能单独当 key**——DWG 里存的 SAT 文本可能来自旧版本、
|
||||
旧代码路径或用户手工编辑过的异形截面,这些不是属性区分的;只要有一项影响截面的属性漏进
|
||||
key,就会静默复用错误几何。若要做,用两级:
|
||||
```
|
||||
L1 内容 hash → region (可证明正确,当前实测 54%)
|
||||
L2 属性 key → textHash (加速索引;命中后仍比对 textHash,不一致按 miss 处理)
|
||||
```
|
||||
对几 KB 文本做一次 hash 只需几微秒,这个校验几乎免费,但把"属性漏项"从静默错误
|
||||
降级为缓存不命中。属性 key 还需随样式表(DN→尺寸)版本变化失效,内容 hash 不需要。
|
||||
落地前应先加一路"属性 key 命中率"计数,与内容 hash 并行比较再决定是否值得引入。
|
||||
- **value**:**母体** `AcDbRegion*`(heap 对象,不入库,永不外泄)。
|
||||
- **API**:
|
||||
- `AcDbRegion* SectionRegionCache::Acquire(const char* psz, Adesk::UInt32 nLen)` —— 命中返回 `clone()`,未命中返回 NULL
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
# run_acad_perf.ps1 - automatically open a drawing in AutoCAD, run ENVIPERFDUMP,
|
||||
# collect the SectionPerf report, then close only the process we started.
|
||||
#
|
||||
# Usage (from repo root):
|
||||
# powershell -NoProfile -ExecutionPolicy Bypass -File tools\run_acad_perf.ps1
|
||||
# powershell ... -Dwg "E:\path\to\big1.dwg" -TimeoutSec 900
|
||||
#
|
||||
# Notes:
|
||||
# - The dump command is executed through a /b script, which AutoCAD runs AFTER the
|
||||
# drawing has loaded. The script contains only ENVIPERFDUMP (no QUIT) so that a
|
||||
# "save changes?" dialog can never block it; the process is killed by PID instead,
|
||||
# so the drawing is never written back.
|
||||
# - AEC_PERF_COUNTER=1 is set for the child process only.
|
||||
# - Only processes started by this run are terminated: an acad.exe that was already
|
||||
# running before launch is left alone.
|
||||
# - Must be started from a Debug build that contains the SectionPerf probes
|
||||
# (see pipe-section-region-performance-plan.md, Phase 0).
|
||||
#
|
||||
# Keep this file ASCII-only: Windows PowerShell 5.1 reads BOM-less files as ANSI.
|
||||
|
||||
param(
|
||||
[string]$Acad = 'C:\Program Files\Autodesk\AutoCAD 2020\acad.exe',
|
||||
[string]$Profile = 'SHS-ENVI2026',
|
||||
[string]$Dwg = 'E:\big1.dwg',
|
||||
[int] $TimeoutSec = 900
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
$report = Join-Path $env:TEMP 'aec_section_perf.txt'
|
||||
$script = Join-Path $env:TEMP 'aec_perf_dump.scr'
|
||||
|
||||
if (Test-Path $report) { Remove-Item $report -Force }
|
||||
Set-Content -Path $script -Value 'ENVIPERFDUMP' -Encoding ASCII
|
||||
|
||||
Write-Output ("acad : " + $Acad)
|
||||
Write-Output ("profile : " + $Profile)
|
||||
Write-Output ("dwg : " + $Dwg)
|
||||
Write-Output ("report : " + $report)
|
||||
|
||||
$existing = @(Get-Process acad -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id)
|
||||
|
||||
$env:AEC_PERF_COUNTER = '1'
|
||||
$acadArgs = @('/p', $Profile, '/b', $script, $Dwg)
|
||||
$sw = [System.Diagnostics.Stopwatch]::StartNew()
|
||||
$p = Start-Process -FilePath $Acad -ArgumentList $acadArgs -PassThru
|
||||
Write-Output ("launched pid=" + $p.Id)
|
||||
|
||||
$got = $false
|
||||
while ($sw.Elapsed.TotalSeconds -lt $TimeoutSec) {
|
||||
Start-Sleep -Seconds 5
|
||||
if (Test-Path $report) {
|
||||
$sz1 = (Get-Item $report).Length
|
||||
Start-Sleep -Seconds 3
|
||||
$sz2 = (Get-Item $report).Length
|
||||
if ($sz1 -gt 0 -and $sz1 -eq $sz2) { $got = $true; break }
|
||||
}
|
||||
$t = [int]$sw.Elapsed.TotalSeconds
|
||||
if ($t % 60 -lt 5) {
|
||||
$alive = [bool](Get-Process -Id $p.Id -ErrorAction SilentlyContinue)
|
||||
Write-Output (" ... " + $t + "s running=" + $alive)
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output ""
|
||||
if ($got) {
|
||||
Write-Output ("[OK] report ready after " + [int]$sw.Elapsed.TotalSeconds + " s (approx. wall time to open)")
|
||||
} else {
|
||||
Write-Output ("[TIMEOUT] no report within " + $TimeoutSec + " s")
|
||||
$alive = Get-Process -Id $p.Id -ErrorAction SilentlyContinue
|
||||
if ($alive) { Write-Output (" still running, window title: '" + $alive.MainWindowTitle + "'") }
|
||||
else { Write-Output " process exited (command missing? build without probes?)" }
|
||||
}
|
||||
|
||||
$after = @(Get-Process acad -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Id)
|
||||
foreach ($id in $after) {
|
||||
if ($existing -notcontains $id) {
|
||||
try { Stop-Process -Id $id -Force -ErrorAction Stop; Write-Output ("killed pid=" + $id) }
|
||||
catch { Write-Output ("kill failed pid=" + $id + ": " + $_.Exception.Message) }
|
||||
}
|
||||
}
|
||||
|
||||
if ($got) {
|
||||
Write-Output ""
|
||||
Write-Output "======== report ========"
|
||||
Get-Content -Path $report -Encoding UTF8
|
||||
}
|
||||
Reference in new issue
Block a user