现象:开图时 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/正常。
116 lines
4.5 KiB
PowerShell
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
|
|
}
|