Files
envi-code/tools/run_acad_perf.ps1
T
company-desktop facf14699b fix: AecDbX3dPart 记住"已尝试建过",避免 Hide 构件被反复重建
现象:开图时 X3dPart 的 EnsureCreated 命中率只有 10.1%,1914 次重建里
721 次产出 NULL(38%),单个对象最多被重建 544 次。

根因:EnsureCreated 的命中条件是 !m_bNeedCreate && m_pSolid != NULL,
而 Rebuild() 里对 Hide 构件是"故意不建几何、直接 return true"——
m_pSolid 按设计就是 NULL,于是每次访问都重新走一遍 Rebuild,永远不可能命中。
子项全为空的构件同理。

修复:
- 新增 m_bCreated 标记"已经尝试建过"(无论是否产出实体)
- EnsureCreated 命中条件改为 !m_bNeedCreate && (m_bCreated || m_pSolid != NULL)
- Rebuild 入口置 m_bCreated = true;所有设 m_bNeedCreate = true 的 setter
  同时置 m_bCreated = false,保证参数变化后仍会重建
- 重建后仍无实体时打一条日志(限前 5 条,带 m_sName),方便定位是哪些构件

实测(big1.dwg / R230 Debug):
  X3dPart 命中率      10.1% -> 41.4%
  重建次数            1914  -> 1248(-666)
  rebuild_null         721  -> 55
  单对象最多重建       544  -> <=3
  开图墙钟             93 s -> 93 s(无变化)

注意:这 666 次空转没有带来可测的提速,因为 Rebuild 里 Hide 的早退分支在
布尔运算之前,每次只有几微秒。所以这是正确性/整洁度修复,不是性能修复;
但消除了"个别构件被重试几百次"这种病态行为,也暴露了"哪些构件建不出实体"。

顺带新增诊断(Debug + AEC_PERF_COUNTER=1 才生效,Release 无开销):
- SectionPerf::NoteEnsure():按 tag 统计"延迟建几何"的命中/重建,
  并给出对象维度直方图与重建次数最高的对象
- AecDbX3dPart::Rebuild / AcDbX3dSolid::EnsureCreated / AecDbGuardBar::Draw3d 打点
- tools/run_acad_perf.ps1 增加 -CpuTrace:用 xperf 采 CPU 采样并 merge 成 etl,
  可直接喂 tools/perf_diagsession.py(VS 的 VSDiagnostics 在这台机器上会卡住,
  wpr -stop 有 COM 线程模式 bug,故用 xperf)

验证:R230 Debug 编译通过并部署,自动开图取数;截面缓存与回写仍为 0/正常。
2026-10-28 22:51:01 +08:00

116 lines
4.5 KiB
PowerShell

# 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,
[switch]$CacheOff,
[string]$CpuTrace = ''
)
$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)
$xperf = 'C:\Program Files (x86)\Windows Kits\10\Windows Performance Toolkit\xperf.exe'
$rawEtl = ''
if ($CpuTrace) {
if (-not (Test-Path $xperf)) { Write-Output "xperf.exe not found"; exit 1 }
$rawEtl = Join-Path $env:TEMP 'aec_cpu_raw.etl'
Remove-Item $rawEtl -Force -ErrorAction SilentlyContinue
Remove-Item $CpuTrace -Force -ErrorAction SilentlyContinue
& $xperf -on PROC_THREAD+LOADER+PROFILE -stackwalk Profile -BufferSize 1024 -f "$rawEtl" | Out-Null
Start-Sleep -Seconds 2
Write-Output ("cpu trace: collecting -> " + $CpuTrace)
}
$env:AEC_PERF_COUNTER = '1'
if ($CacheOff) { $env:AEC_SECTION_CACHE = '0' } else { $env:AEC_SECTION_CACHE = '1' }
Write-Output ("cache : " + $(if ($CacheOff) { 'OFF (A/B baseline)' } else { 'ON' }))
$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?)" }
}
if ($CpuTrace) {
Write-Output "stopping cpu trace ..."
& $xperf -stop | Out-Null
Start-Sleep -Seconds 2
& $xperf -merge "$rawEtl" "$CpuTrace" | Out-Null
if (Test-Path $CpuTrace) {
Write-Output ("[cpu trace] " + $CpuTrace + " (" + [math]::Round((Get-Item $CpuTrace).Length / 1MB, 1) + " MB)")
} else {
Write-Output "[cpu trace] merge failed"
}
}
$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
}