性能
“快”由两个数字定义:按键后的首帧在 100 毫秒内显示,UI 线程上没有任何阻塞超过 50 毫秒。该技能中的每项改动都以测量开始、以测量结束,从而确认改动是否真的有效。针对具体症状的修复方案位于 recipes.md。涉及视频、音频、摄像头、Direct3D 或大型图像管线时,还应阅读 gpu-media.md。
循环流程
reproduce -> measure(Release 构建)-> find the line -> fix -> measure again -> commit with the numbers
复现:写下确切步骤,或在 agent CLI / 调试桥(test-windows-app 技能)中添加一个探测命令,执行慢操作并记录每一步的耗时。
测量 Release 构建:./build.ps1 默认构建 Release;-Dev 构建 Debug,会关闭 JIT 优化并指向错误的代码。
定位代码行:按症状选择工具:
症状 / 工具
应用冻结、“Not responding”:dotnet-stack report -p <pid>,然后读取 UI 线程的堆栈(即位于 Microsoft.UI.Xaml 中的那个)。
操作缓慢:dotnet-trace collect -p <pid> --duration 00:00:00:10,然后 dotnet-trace report <file> topN -n 30。
输入或拖动时的短暂冻结:应用内的 UI 线程看门狗(recipes.md 的“Measuring”一节),或使用 XAMLAppResponsiveness 配置的 WPR。
滚动或动画时卡顿:WPR -start GPU -start DesktopComposition,在 WPA 中打开。
内存增长:dotnet-counters monitor -p <pid> --counters System.Runtime,然后 dotnet-gcdump collect -p <pid>。
风扇、电池:空闲时用 dotnet-counters 观察 CPU 使用率;查找定时器和轮询。
启动缓慢:从 Process.Start 到首次应答的时间(recipes.md 的“Startup”一节)。
.NET 工具只需安装一次:dotnet tool install --global dotnet-trace(以及 dotnet-counters、dotnet-stack、dotnet-gcdump)。安装前需先询问。WPR 随 Windows 提供;WPA 随 Windows Performance Toolkit 提供。WPR 需要管理员终端。
尽量以文本方式阅读跟踪记录:参见 recipes.md 的“Measuring”一节。读取后删除所有 .nettrace、.gcdump 和 .etl 文件。它们包含进程命令行、文件路径和内存内容。切勿提交或分享。
使用对应方案修复。
用相同步骤再次测量。在提交信息中写明前后对比:“Picker opens in 40 ms, was 600 ms”。
解决大多数问题的规则
UI 线程只做 UI。文件、网络、数据库、图像解码、跨进程 Win32 调用和音频都放到别处。UI 线程上等待另一个进程的调用(向挂起窗口 SendMessage、调用其他应用的 COM)会让应用在该进程挂起期间一直冻结。
先显示,后处理。让窗口或面板在按键的同一帧内显示到屏幕上,然后再加载。绝不让 UI 等待工作完成。
让 GPU 合成。XAML 通过合成器渲染。大型图像只放入 Image 一次;指针移动只改变小元素的 transform。绝不因指针事件而重绘全屏位图。
按显示尺寸解码。使用 BitmapImage.DecodePixelWidth,或带 BitmapTransform 的 BitmapDecoder,按路径和尺寸缓存。
不要给必须快速的东西加动画。搜索结果和切换器在原地更新。
虚拟化长列表,延迟隐藏 UI。列表使用 ListView 或 ItemsRepeater,大多数人不会打开的面板使用 x:Load="False"。
没有理由就不轮询。屏幕上不需要时停止定时器。
Windows 与 .NET 说明
XAML 布局、合成器、Direct2D(Win2D)、Direct3D、Media Foundation 和 Windows.Media.Ocr 已经使用 GPU 或硬件模块。把工作交给它们,而不是在 C# 中逐像素循环。
只有当测量表明 XAML、合成或 Win2D 太慢时,才编写 Direct3D 或计算着色器。大多数应用永远不需要。
在采集、处理和编码之间,帧保持为 IDirect3DSurface 或 D3D11 纹理。转换为 SoftwareBitmap 再转回会经过 CPU 内存产生复制开销。
对于启动时间,NativeAOT 优于其他所有方案:tools/publish.ps1 -Aot。在模板上测得,从冷启动到主窗口:自包含 JIT 约 420 毫秒,NativeAOT 约 170 毫秒。ReadyToRun 仅提升约 10%。托盘工具、启动器和面板使用 NativeAOT;distribute 技能包含配置(MSVC 链接器)和裁剪警告修复。
完成标准:慢操作有测量到的前后对比,后值满足开头的两个数字,且提交信息中写明两者。