面向Windows应用开发者的CPU设置入门:优先级・亲和性・P核心/E核心
· 小村 豪 · Windows, Windows应用程序, CPU, 性能, 优先级, 亲和性, P核心, E核心, 节能, EcoQoS
Windows应用程序的性能,并不是单靠代码就能决定的。即使是同一个 .exe,在不同的电脑上运行,体验也可能不同。
在某台电脑上,周期性处理很稳定。
在另一台电脑上,偶尔会延迟。
接AC电源时没问题,但用电池时却莫名其妙变慢。
在任务管理器里提高了优先级,却没有想象中变快。
以为已经导向P核心,处理时间却还是在波动。
反过来,想拉高性能结果,风扇一直转不停,其他应用程序也变得卡顿。
在开发Windows应用程序时,常会遇到这种「光靠代码很难解释的现象」。
这时候该看的,是这4件事。
| 要看的东西 | 简单来说 |
|---|---|
| 优先级 | 让哪个线程先运行 |
| 亲和性 | 允许在哪些CPU上执行 |
| P核心 / E核心 | 这颗CPU偏性能还是偏节能 |
| 节能设置 | CPU会被允许多认真地转 |
而在现代Windows中,还要再加上 EcoQoS 以及任务管理器的 Efficiency mode。
也就是说,Windows应用程序的执行环境,不是单纯的「CPU快还是慢」。
什么时候执行
在哪里执行
用哪种核心执行
CPU目前处于哪种性能状态
操作系统认为这是「重视性能」,还是「节能就好」
这些因素组合起来,才决定了实际的响应性与处理时间。
本文将整理Windows应用程序开发中容易被忽略的优先级、亲和性、P核心/E核心、节能设置之间的关系。
顺带一提,本文中出现的代码,已经以可构建、可运行的示例(处理优先级与亲和性的 C# 库与演示程序、PowerShell 脚本、单元测试)整套开源在 GitHub 上。
windows-app-cpu-priority-affinity-power - komurasoft-blog-samples (GitHub)
1. 先看整体架构
首先,把整体架构画成图大概是这样。
flowchart LR A["应用程序的代码"] --> B["Windows 调度器"] P["优先级<br/>多容易被调度执行"] --> B AF["亲和性 / CPU Sets<br/>允许在哪些CPU上执行"] --> B Q["QoS / EcoQoS<br/>偏重性能或偏重节能"] --> B B --> C["选择要执行的线程"] C --> D["选择要执行的逻辑处理器"] PE["P核心 / E核心<br/>偏性能还是偏节能"] --> D PM["电源模式 / 电源计划 / PPM<br/>频率・Boost・Core Parking"] --> D D --> R["实际的响应性<br/>处理时间<br/>发热<br/>电池消耗<br/>对其他应用程序的影响"]
重要的是,这些因素并不是各自独立的。
提高优先级,确实会更容易被调度执行。
但如果CPU本身被控制成偏节能,就可能没有预期的那么快。
用亲和性限制CPU,线程的迁移或许会减少。
但如果限制后落在偏节能的核心,或是与Core Parking、电源管理不协调,反而可能造成不利。
把负载导向P核心,计算处理或许会变快。
但若把所有东西都导向高性能一侧,发热、风扇噪音、电池消耗、对其他进程的影响都会增加。
Windows应用程序的性能调优,不是单纯「找一个能变快的按钮」。
想让哪个处理变快?
哪个处理慢一点也没关系?
要优先用户操作的响应性,还是后台处理的完成时间?
电池与发热可以容忍到什么程度?
这些都属于设计层面的问题。
2. 优先级决定的是「多容易被调度执行」
在Windows中,当有多个可执行的线程时,调度器要决定「接下来让哪个线程上CPU」。这时发挥作用的就是优先级。
优先级大致上分两个层级决定。
进程的优先级类别
+ 线程的相对优先级
= 线程的基础优先级
以Win32 API来说,进程侧有 SetPriorityClass,线程侧有 SetThreadPriority。
若要用PowerShell查看当前运行进程的优先级,例如可以这样写。
Get-Process -Id $PID | Select-Object Id, ProcessName, PriorityClass
要提高当前PowerShell进程的优先级,可以这样做。
$p = Get-Process -Id $PID
$p.PriorityClass = "AboveNormal"
用C#的话,可以这样写。
using System.Diagnostics;
using var process = Process.GetCurrentProcess();
process.PriorityClass = ProcessPriorityClass.AboveNormal;
这里要注意的是,优先级并不是「让CPU变快的设置」。优先级影响的,是有竞争时该让哪个线程先运行。
它不是提高CPU频率的设置。
也不是选择P核心的设置。
也不是加快I/O的设置。
更不是消除锁等待或网络等待的设置。
因此,「提高了优先级却没有变快」是很常见的事。举例来说,如果变慢的原因是以下这些,光提高优先级并不能从根本上解决。
- 磁盘I/O等待
- 网络等待
- DB响应等待
- 锁竞争
- GC停顿
- UI线程被阻塞
- GPU或驱动程序侧的等待
- 防病毒软件造成的文件扫描
- CPU频率被压低在节能一侧
另外,HIGH_PRIORITY_CLASS 与 REALTIME_PRIORITY_CLASS 不是能随意使用的设置。
长时间以高优先级持续运行的线程,会不容易把CPU时间让给其他线程。
如果只是自己应用程序内部的问题还好,但这有时会恶化整个系统的响应性。
优先级就像药物一样。
在有效的场合确实有效。
但不是用量越多越好。
提高优先级之前要先想的事
在提高优先级之前,有些事要先想清楚。
| 问题 | 该看的东西 |
|---|---|
| 那个处理真的是在等CPU吗 | CPU使用率、ETW、性能分析器 |
| 是不是堵住了UI线程 | UI响应、异步化、队列设计 |
| 提高优先级的范围够短吗 | 临时提高,结束后还原 |
| 会不会打扰到其他应用程序 | 输入、打印、浏览器、常驻软件受到的影响 |
| 在客户环境中会不会被权限或策略挡住 | 管理员权限、执行用户、安全产品 |
对Windows应用程序来说,重要的不是一直用最高优先级运行,而是在必要的时机,只把必要的处理,优先到必要的范围。
3. 亲和性决定的是「允许在哪些CPU上执行」
如果优先级是「多容易被调度执行」,那么亲和性就是「允许在哪些CPU上执行」。
在Windows中,可以对进程或线程指定一组允许使用的逻辑处理器。
以Win32 API来说,对进程有 SetProcessAffinityMask,对线程有 SetThreadAffinityMask。
若要用PowerShell查看进程的亲和性,例如可以这样写。
Get-Process -Id $PID | Select-Object Id, ProcessName, ProcessorAffinity
为了验证目的,把当前的PowerShell进程限制在前4个逻辑处理器,可以这样写。
$p = Get-Process -Id $PID
$p.ProcessorAffinity = [IntPtr]0xF
0xF 用二进制表示是 1111。
也就是说,代表允许使用逻辑处理器0~3。
不过,这仅仅是验证用的示例。
限制亲和性,有时能改善CPU缓存的局部性,也能减少周期性处理的抖动。
但另一方面,这也会把原本Windows能疏导到空闲CPU的线程,关进一个狭窄的空间里。
尤其在以下这类环境中,要特别注意。
- P核心/E核心混合存在
- SMT/超线程导致逻辑处理器与物理核心的对应不直观
- NUMA架构
- 超过64个逻辑处理器而涉及Processor Group
- 启用了Core Parking
- OEM或BIOS的电源控制很强势
- 运行于虚拟化环境中
亲和性是对系统强力施加「只能用这些CPU执行」限制的设置。
限制可以是稳定化的工具。
但用错了,就会堵死系统的退路。
现在还有CPU Sets
传统的亲和性掩码,是相当强的限制。
而Windows还有一个叫 CPU Sets 的机制。
CPU Sets是让应用程序端能以更柔性的方式,把CPU的期望传达给系统的API。
依Microsoft的说明,CPU Sets被定位为一种与操作系统电源管理兼容的「soft」亲和性指定方式。
究竟是想严格固定CPU?
还是希望在与操作系统的电源管理、调度协调的前提下,大致把执行位置导向某处?
这个区别很重要。
现代的Windows应用程序,不该只靠传统的 SetProcessAffinityMask 解决所有问题,而必须把CPU Sets与QoS也一起纳入考量。
4. P核心/E核心:「CPU也是有个性的」
在近期的CPU中,并非所有核心的性能与功耗都相同。代表性的例子就是P核心与E核心,简单来说,P核心偏性能,E核心偏效率。
| 种类 | 擅长的事 |
|---|---|
| P核心 | 低延迟、高单核性能、繁重的前台处理 |
| E核心 | 节能、后台处理、并行处理的承接方 |
不过,开发者若随意地认定「CPU 0~7是P核心,8~15是E核心」是很危险的。CPU编号的排列,会随CPU型号、BIOS、Windows版本、固件、OEM设置、虚拟化环境而改变。
在Windows端,CPU Sets的信息中有个叫 EfficiencyClass 的概念。
这是在具有异构处理器的系统中,用来表示该CPU Set效率特性的一个值。依Microsoft的文档说明,这个值越高,代表该CPU Set拥有速度更快、但电源效率较低的处理器。
若想妥善运用P核心/E核心,仅看CPU编号是不够的。若要真正深入处理,需要以下观察。
- 用CPU Sets API查看
EfficiencyClass - 用Windows Performance Recorder / Analyzer观察线程的执行CPU
- 用任务管理器的逻辑处理器视图观察趋势
- 在实机上逐一测量处理时间
- 比较AC电源与电池时的差异
- 更换电源模式后比较差异
P核心/E核心不只是硬件规格,而是与Windows的调度器、QoS、节能设置组合后,才决定实际的执行位置。
5. 节能设置:关乎「CPU会被允许多认真地转」
这一点,在实务上相当重要。
优先级是「让哪个线程先运行」。
亲和性是「允许在哪些CPU上执行」。
P核心/E核心是「这颗CPU偏性能还是偏节能」。
而节能设置,关乎的是
CPU究竟被允许在什么样的频率、电力状态下运行
也就是说,会发生这样的事。
提高了优先级。
导向了P核心。
但如果电源设置偏向节能,
CPU或许不会认真地转。
这是相当有Windows特色的一件事。
在Windows 11中,可以从设置应用的「系统 > 电源和电池」选择电源模式。
标注会因环境或版本而不同,但方向大致如下。
| 电源模式 | 方向 |
|---|---|
| 最佳能效 / Best power efficiency | 优先电池与节能 |
| 平衡 / Balanced | 性能与功耗的平衡 |
| 最佳性能 / Best performance | 优先性能 |
此外,传统上还有电源计划:Power Saver、Balanced、High Performance。
Balanced 会依需求调整性能与功耗,High Performance 则是倾向以功耗为代价换取最大性能的设置。
用户能看到的设置看似简单,但背后有Processor Power Management,也就是PPM的设置。
6. P-state、C-state、Boost、EPP
CPU并不是永远以最大频率运行,它有一些用来降低功耗的状态。
| 术语 | 简单来说 |
|---|---|
| P-state | 改变CPU频率与电压的性能状态 |
| C-state | 空闲时停止CPU部分功能的节能状态 |
| Boost | 条件满足时,进入超出额定值的高性能状态的机制 |
| EPP | Energy Performance Preference。偏重性能或偏重节能的倾向 |
P-state是通过改变CPU的频率与电压来降低功耗的机制。
C-state是CPU空闲时停止部分功能,进入更深节能状态的机制。
Windows的电源管理,会利用这些机制在性能与功耗之间取得平衡。
因此,「明明是高优先级却很慢」这种现象是有可能发生的。
线程被优先执行。
但CPU频率偏低。
不容易进入Boost。
EPP偏节能。
Core Parking限制了可用的核心。
电池驱动下整个操作系统偏向节能运行。
在这种情况下,只看优先级找不到原因。
尤其在周期性处理、图像处理、测量仪器控制、视频处理、音频处理、USB摄像头采集、串口通信等场景中,不只是平均处理时间,「偶尔延迟」本身就是个问题。
平均很快。
但1000次里有1次会延迟。
就这一次让缓冲区堵住。
UI卡死。
与设备间的时序偏移。
在这类现象中,光看CPU使用率的平均是不够的。
需要一起检视处理时间的分布、最大值、离群值、电源状态、实际执行的CPU、后台负载。
7. Core Parking:关乎「可用核心的数量」
Windows有一个叫Core Parking的机制。这是一种让未使用的逻辑处理器休息的控制方式。
在使用率低时,把部分核心导向低功耗状态,借此抑制功耗。
依Microsoft的资料说明,CPMinCores 是用来指定在任意时间点,至少要有百分之几的逻辑处理器保持un-parked,也就是可使用状态的设置。
若设为100%,Core Parking算法就会被禁用。
这里会出问题的,是与亲和性的组合。
用亲和性限制了「希望在这群CPU上执行」。
但在电源管理上,那些核心究竟被如何对待,是另一个问题。
在针对Windows Server的资料中也说明,若活跃的线程被强力指定亲和性到NUMA节点内的一部分CPU,有时会与Core Parking的判断不协调。
这在客户端电脑上,作为思路同样值得参考。
亲和性对调度器施加限制。
Core Parking则关乎电源管理「让哪些核心保持可用状态」。
若把这两者分开来想,就会误判原因。
8. EcoQoS / Efficiency Mode:告诉系统「这个处理节能就好」的机制
过去,Windows应用程序的性能调优,主要围绕在提高或降低优先级、改变亲和性这类话题。但现代Windows已经有了另一种思路,那就是QoS。
QoS,Quality of Service,是一种用来表示「这个处理有多重视性能,或有多重视节能」的概念,作用于线程之上。
依Microsoft的文档说明,调度优先级仍然是决定接下来执行哪个线程的主要指标,而QoS则可能影响核心选择与处理器电源管理。
尤其是 EcoQoS,是一种让性能不是最重要的处理,能被导向节能一侧的机制。
用C++的话,例如可以用 SetThreadInformation 与 ThreadPowerThrottling,把当前的线程设为EcoQoS。
#include <windows.h>
void EnableEcoQoSForCurrentThread()
{
THREAD_POWER_THROTTLING_STATE powerThrottling = {};
powerThrottling.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION;
powerThrottling.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
powerThrottling.StateMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
SetThreadInformation(
GetCurrentThread(),
ThreadPowerThrottling,
&powerThrottling,
sizeof(powerThrottling));
}
反过来,若要恢复为性能优先,可以对同一个控制对象将 StateMask 设为0。
void DisableEcoQoSForCurrentThread()
{
THREAD_POWER_THROTTLING_STATE powerThrottling = {};
powerThrottling.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION;
powerThrottling.ControlMask = THREAD_POWER_THROTTLING_EXECUTION_SPEED;
powerThrottling.StateMask = 0;
SetThreadInformation(
GetCurrentThread(),
ThreadPowerThrottling,
&powerThrottling,
sizeof(powerThrottling));
}
EcoQoS并不是一种「什么都设成节能就好」的功能。适合的,是像下面这类处理。
- 后台同步
- 低优先级的索引构建
- 不急的日志汇总
- 与用户操作没有直接关系的缓存更新
- 晚一点结束也无妨的维护处理
另一方面,以下这类处理则应谨慎使用。
- 直接关系到UI操作的处理
- 摄像头采集
- 音频处理
- 关乎控制周期的处理
- 检测设备的判定处理
- 用户正在等待的导出处理
- 需要接近实时性的响应处理
任务管理器的 Efficiency mode 也与这个概念相关。
在Microsoft的Performance Diagnostics博客中说明,启用Efficiency mode后,会把进程的基础优先级降为Low,并将QoS设为EcoQoS。
也就是说,Efficiency mode并不只是单纯的「节能图标」,而是结合低优先级化与EcoQoS,用来保护前台应用程序响应性与电源效率的机制。
从开发者的角度来看,这相当重要。
因为除了「希望这个处理快点结束」之外,也能设计成
「这个处理只要在不打扰用户的范围内运行就好」
的方式,把意图传达给操作系统。
9. 判断流程图
到这里的内容相当复杂,这里整理一份调查Windows应用程序性能或周期抖动时的判断流程。
flowchart TD
A["症状<br/>慢・周期抖动・UI卡死・风扇很吵"] --> B["先记录<br/>处理时间 / 最大值 / CPU使用率 / 电源状态 / AC或电池 / 目标电脑"]
B --> C{"CPU是主要原因吗?"}
C -- "不是 / 不确定" --> D["检查I/O・锁・DB・网络・GC・GPU・驱动程序<br/>在动优先级或亲和性之前,先消除等待时间"]
C -- "是" --> E{"与其他进程的CPU竞争很强吗?"}
E -- "是" --> F["考虑优先级<br/>但要限定范围<br/>避免长期使用High/Realtime"]
E -- "不是" --> G{"是否偏向特定CPU?"}
G -- "是" --> H["检查亲和性 / CPU Sets<br/>是否固定得太多<br/>有没有看P/E核心与NUMA"]
G -- "不是" --> I{"频率・电源状态可疑吗?"}
I -- "是" --> J["检查电源模式 / 电源计划 / PPM<br/>怀疑P-state / EPP / Boost / Core Parking"]
I -- "不是" --> K{"后台处理正在打扰前台吗?"}
K -- "是" --> L["考虑EcoQoS / Efficiency Mode / 降低优先级<br/>把不急的处理导向节能一侧"]
K -- "不是" --> M["重新检视算法・并行度・队列设计・UI线程设计"]
F --> N["一次改一项并进行A/B测量"]
H --> N
J --> N
L --> N
M --> N
N --> O["不只看平均<br/>也要看最大值・离群值・发热・对其他应用程序的影响"]
这个流程中重要的是,不要一开始就乱动设置。先记录。
- 是一直慢,还是偶尔慢
- 是AC电源还是电池
- 电源模式是什么
- 任务管理器里是不是已经进入Efficiency mode
- CPU使用率高不高
- 频率有没有拉高
- 哪个线程在用CPU
- 处理时间的最大值大概是多少
- 有没有其他常驻软件或防病毒软件有关
然后,一次只改一项。
改优先级。
改亲和性。
改电源模式。
加上EcoQoS。
分离后台处理。
加入队列。
从UI线程中拿出来。
同时改多项,就无法知道到底哪个起了作用。
10. 现场常用的检查命令
当Windows应用程序的行为随环境不同而不同时,先让自己能取得状态信息会很方便。
查看CPU信息
Get-CimInstance Win32_Processor |
Select-Object Name, NumberOfCores, NumberOfLogicalProcessors, MaxClockSpeed
查看当前启用的电源计划
powercfg /getactivescheme
查看处理器电源管理的设置
powercfg /q SCHEME_CURRENT SUB_PROCESSOR
输出相当长,但可以查看最小・最大处理器状态、EPP、Boost、与Core Parking相关的设置。
能看到的项目会因环境而异。
生成电源效率诊断报告
在具有管理员权限的终端中执行。
powercfg /energy
经过一段时间的测量后,会输出HTML报告。
可以作为观察驱动程序、设备、计时器分辨率、USB节能、阻止睡眠等问题的入口。
查看目标进程的优先级与亲和性
Get-Process -Name MyApp |
Select-Object Id, ProcessName, PriorityClass, ProcessorAffinity
查看CPU的性能状态
依环境不同,可用的计数器名称会有所变化,但以下这类Performance Counter可作为参考。
Get-Counter '\Processor Information(_Total)\% Processor Performance'
Get-Counter '\Processor Information(_Total)\% Processor Utility'
若要更深入地查看,用Windows Performance Recorder与Windows Performance Analyzer采集ETW会更可靠。
可以一并追踪周期抖动、上下文切换、实际执行的CPU、DPC/ISR、磁盘I/O、CPU频率变化。
11. 在软实时处理中,要整体一起考虑
Windows并不是一般意义上的实时操作系统。但在实务的Windows应用程序中,有时会被要求具备接近实时的特性。
- 以固定周期从USB摄像头获取图像
- 以串口通信与设备交换数据
- 从测量仪器读取数据
- 与PLC或外部设备同步
- 处理音频或视频
- 在一定时间内返回检测结果
- 在不卡死UI的情况下执行繁重的计算
在这类处理中,光靠代码速度是不够的。
需要线程设计。
需要队列设计。
需要日志。
需要超时处理。
需要背压(backpressure)。
需要决定延迟时要丢弃、等待,还是重试。
而在这之上,还需要检视优先级、亲和性、P核心/E核心、节能设置。
举例来说,来想想摄像头采集线程。
这个线程与用户操作相关,也具有周期性。
或许不该设为EcoQoS。
稍微提高优先级或许有价值。
但提高过头,会对UI或其他处理造成不良影响。
固定亲和性或许会更稳定。
但如果固定的位置偏向E核心,反而可能造成反效果。
如果电源模式偏向节能,可能只有在电池模式下才会延迟。
另一方面,来想想旧日志的压缩处理。
这或许是用户不在等待的处理。
那么,就降低优先级。
设为EcoQoS。
在空闲时执行。
只在AC电源时执行。
这样的设计,对整个应用程序反而更友好。
不是把所有处理都变「快」,而是把该变快的处理,与不该打扰别人的处理分开来。
12. 设计的基本方针
在处理这些议题时,有6项基本方针。
1. 不要一开始就固定
一开始不要强力固定优先级或亲和性比较好。
Windows的调度器,多数情况下都做得相当好。
应用程序端加上多余的限制,反而可能让事情变糟。
先正常地做。
测量。
出现问题时,建立假设。
小幅度修改。
再测量。
按这个顺序进行。
2. 优先级要临时使用
高优先级,只用在必要的区间。
比起一直维持高优先级,
在重要处理开始前才提高
结束后就还原
这样的做法更安全。
3. 亲和性放到后面再考虑
亲和性是很强的设置。
在怀疑是周期抖动或CPU迁移造成影响时,有时很有效。
但它不是最先该动的设置。
尤其若客户端有多种机型,随意地固定CPU编号是危险的。
4. P核心/E核心:不是「认定」,而是「观察」
若想妥善运用P核心/E核心,要先在实机上观察。
不要用CPU编号认定,而要用CPU Sets、EfficiencyClass、ETW、实测来确认。
5. 以节能设置为前提进行测试
「开发机是AC电源、高性能设置,所以很快」,光靠这样是不够的。
在客户端环境,以下状况是很常见的。
- 笔记本电脑的电池驱动
- Best power efficiency
- Energy saver
- OEM自家的节能工具
- 企业策略所固定的电源设置
- 因发热而性能下降的轻薄笔记本
- 常驻软件很多的业务终端
性能测试时,至少要分开来看以下这几种条件。
| 条件 | 要看的东西 |
|---|---|
| AC + Best performance | 接近最大性能的状态 |
| AC + Balanced | 标准的业务使用场景 |
| Battery + Balanced | 笔记本电脑的现实情况 |
| Battery + Best power efficiency | 偏节能的下限 |
| 长时间连续运行 | 发热、风扇、热降频 |
| 同时使用其他应用程序 | 与浏览器、Teams、Excel、防病毒软件共存 |
6. 后台处理可考虑使用EcoQoS
与用户体验没有直接关系的处理,有时优先「不打扰」比追求性能更好。
用高性能运行一切的应用程序,或许很快。
但会打扰其他工作。
会让笔记本电脑的风扇转个不停。
会消耗电池。
结果反而变成一个不好用的应用程序。
对Windows应用程序来说,速度以外,「共存的好坏」也是一种质量。
13. 常见的误解
提高优先级就会变快
不一定会变快。
如果原因是CPU竞争,有时确实有效。
但如果原因是I/O等待、锁等待、节能导致的低频率、在E核心上执行等,光靠优先级是不够的。
固定在P核心永远是正解
不永远是正解。
P核心性能高,但发热与功耗也会增加。
而且也可能与其他重要的前台处理发生竞争。
把该导向P核心的处理,与E核心就足够的处理分开来,才是更重要的事。
节能设置是用户的偏好,与应用程序无关
有关系。
同一个应用程序,也会因电源模式、电源计划、EPP、Boost、Core Parking而运行方式不同。
尤其在笔记本电脑上,AC电源与电池的表现会不一样。
Efficiency mode只是单纯变慢
不只是单纯变慢。
它是通过降低优先级与EcoQoS,来保护前台应用程序响应性与电源效率的机制。
对不急的后台处理,反而值得积极考虑使用。
因为是Windows所以不稳定
这也是很粗略的看法。
Windows不是实时操作系统。
但只要理解调度、电源管理、QoS、测量、日志、线程设计,就能让它相当稳定地应用在实务上。
问题不在于「因为是Windows所以没办法」,而在于没有看清楚哪一层发生了什么事。
14. 在实现之前,先设计日志
这类问题有时只会在客户端环境中发生。因此,让应用程序端能留下最低限度的诊断信息,会很有帮助。
例如,可以在诊断日志中输出以下信息。
- 应用程序版本
- Windows版本
- CPU名称
- 逻辑处理器数量
- AC电源还是电池
- 处理时间的平均・最大值・百分位数
- 处理周期的延迟次数
- 目标线程的优先级
- 进程优先级
- 是否设置了亲和性
- 是否设置了EcoQoS
- 启动时的电源计划
- 目标处理超时的次数
当客户端说「偶尔会变慢」时,如果没有任何记录,就只能靠猜测。有了日志,就能建立假设。
只有电池模式时才慢
只有特定电脑才慢
只有刚启动时才慢
30分钟后开始变慢
只在其他应用程序启动时才慢
以固定周期出现离群值
只要能看出这类差异,就更容易判断到底是优先级、亲和性、电源设置、发热,还是其他等待造成的问题。
15. 小村软件式的视角
在Windows应用程序的开发中,光靠漂亮的理论是不够的。
要能在客户的电脑上运行。
要能在现场的终端上运行。
要能与老旧的周边设备共存。
要能在有防病毒软件、打印机、公司策略的环境中运行。
要能在笔记本电脑的节能设置下也不崩溃。
在需要性能的地方,要确实发挥性能。
在不急的处理上,不要打扰用户。
要做到这些,最好不要把Windows单纯视为「黑箱操作系统」。
Windows在考量该如何执行线程。
在考量该用哪个CPU执行。
在电力与性能之间取得平衡。
在处理像P核心/E核心这样的异构核心。
试图区分前台应用程序与后台处理。
开发者不该与它对抗,而应该在必要之处,把意图传达给它。
这个处理让用户在等。
这个处理慢一点也无妨。
这个处理的周期性很重要。
这个处理在后台安静运行就好。
这个处理不能打扰其他处理。
把这样的设计,落实到优先级、亲和性、QoS、节能设置的理解之中。
16. 总结
Windows应用程序的性能,并不是单靠代码就能决定的。
即使提高优先级,若亲和性偏向E核心,可能还是不如预期地运行。
即使导向P核心,若电源模式偏向节能,CPU也可能无法发挥最大性能。
若不考虑CPU Sets与QoS就套用强力亲和性,可能会堵死Windows电源管理与调度器的退路。
若把一切导向高性能一侧,发热、风扇噪音、电池消耗、对其他应用程序的影响都会增加。
在Windows应用程序开发中,不只是速度,还要把响应性、稳定性、功耗、发热、与其他进程的共存都纳入设计。
因此该看的,终究还是这4件事。
优先级
亲和性
P核心 / E核心
节能设置
而在现代Windows中,还要再加上EcoQoS与Efficiency mode。
要担心的事情很多。
但不需要把担心一直放在不安的状态。
把担心,转化为测量。
把担心,转化为日志。
把担心,转化为设计。
把担心,转化为测试条件。
这样一来,Windows就不再是单纯任性的执行环境,而会变成一个相当值得观察、能在现场依赖的平台。
Windows不是实时操作系统。
即便如此,只要理解并设计执行环境,就能做出在现场足以信赖的应用程序。
而这种难度,正是Windows应用程序开发的有趣之处。
参考链接
- 本文的示例代码一整套(C# / PowerShell / 单元测试) - komurasoft-blog-samples (GitHub)
- Scheduling Priorities - Microsoft Learn
- SetProcessAffinityMask function - Microsoft Learn
- SetThreadAffinityMask function - Microsoft Learn
- CPU Sets - Microsoft Learn
- SYSTEM_CPU_SET_INFORMATION structure - Microsoft Learn
- Change the power mode for your Windows PC - Microsoft Support
- Power Policy Settings - Microsoft Learn
- P-states and C-States - Microsoft Learn
- Processor power management options - Microsoft Learn
- CPMinCores - Microsoft Learn
- Quality of Service - Microsoft Learn
- SetThreadInformation function - Microsoft Learn
- Reduce Process Interference with Task Manager Efficiency Mode - Microsoft DevBlogs
相关文章
共享相同标签的最新文章。可以围绕相近的主题进一步加深理解。
把 Windows 的「处理器计划」改成「后台服务」会发生什么 - 整理 quantum、优先级提升、P-Core/E-Core
整理把 Windows 的「处理器计划」切到「后台服务」时,到底改变的是哪一层;从 quantum、前台偏好、优先级提升,一路接到 Windows 11 hybrid CPU 上 QoS 与 P-Core/E-Core 的选择,并区分什么情况真的有效、什么情况其实是 DPC...
如何理解 Windows 的会话隔离 ── Session 0・RDP・多用户同时运行
整理 Windows 应用程序开发者容易混淆的「会话」概念。说明服务无法显示 UI 的 Session 0 隔离原因、RDP 连接时会话的行为、命名对象的会话隔离,以及共享 PC・RDS 环境中常见的设计失误,从实务角度解说。
防止 Windows 应用程序重复启动 ── 命名 Mutex 与重复执行时的窗口前置
整理业务型 Windows 应用程序的常见需求——「不让同一个应用程序启动两次」——如何用命名 Mutex 来实现。涵盖 Global\ 与 Local\ 命名空间差异在 RDP 环境中的陷阱、拥有线程限制与 AbandonedMutexException、在 SetFor...
在 WinForms/WPF 应用中集成 Entra ID 认证 —— MSAL.NET 与 WAM Broker 的实务架构
本文以实务视角整理在 WinForms/WPF 桌面应用中集成 Entra ID(原 Azure AD)认证的步骤:公共客户端的思路、ROPC 被弃用的现状、应用注册、MSAL.NET 的 AcquireTokenSilent 模式、WAM Broker、令牌缓存的持久化,...
业务应用的日期时间与时区 ── 从 DateTime 的陷阱到 UTC 存储原则、测试设计
服务器迁移后时间整整差了 9 个小时、只有海外分支机构的日期会变成前一天——本文从 DateTime 的 Kind 与隐式转换梳理日期时间事故的根本原因。内容涵盖与 DateTimeOffset 的取舍、UTC 存储与 ISO 8601 原则、TimeZoneInfo 与夏...
常见问题
汇总了咨询这一主题时常见的问题。
- CPU优先级要怎么设置?
- 优先级由进程的优先级类别与线程的相对优先级两个层级决定。Win32 API中,进程侧有 SetPriorityClass,线程侧有 SetThreadPriority。若用 PowerShell,可以对 Get-Process 取得的对象的 PriorityClass 属性设置 "AboveNormal" 之类的值;在 C# 中,则是把 Process.GetCurrentProcess() 的 PriorityClass 设为 ProcessPriorityClass.AboveNormal。不过,长期使用 HIGH_PRIORITY_CLASS 或 REALTIME_PRIORITY_CLASS 会让整个系统的响应性变差,应当避免。
- 提高了优先级,应用程序却没有变快,为什么?
- 因为优先级决定的是「有竞争时先让哪个线程运行」,并不是让 CPU 变快的设置。如果变慢的原因是磁盘 I/O 等待、网络等待、锁竞争、GC 停顿、UI 线程被阻塞、防病毒软件扫描,或是节能设置压低了 CPU 频率,提高优先级并不能从根本上解决问题。首先要记录处理时间的分布、电源状态、实际执行的 CPU,才能找出真正的原因。
- 用 SetThreadInformation 设置 EcoQoS 会发生什么?
- 对 SetThreadInformation 指定 ThreadPowerThrottling 与 THREAD_POWER_THROTTLING_EXECUTION_SPEED,就能告诉操作系统把该线程视为 EcoQoS(偏向节能的 QoS)。QoS 可能影响核心选择与处理器电源管理,因此可以把后台同步或不急的日志汇总这类处理,导向不打扰前台应用程序的节能一侧。反之,直接关系到 UI 操作、摄像头采集、控制周期的处理则应谨慎使用。将 StateMask 设为 0 即可恢复为性能优先。
- powercfg 的 CPMinCores 是什么?
- CPMinCores 是 Windows Core Parking 相关的电源设置,用来指定在任意时间点,至少要有百分之几的逻辑处理器保持 un-parked(可使用)状态。若设为 100%,Core Parking 算法就会被禁用。可以用管理员权限执行 powercfg /q SCHEME_CURRENT SUB_PROCESSOR 来查看当前设置;当用亲和性限制 CPU 时,Core Parking 的判断有时会与之冲突,因此两者必须一起看。
作者简介
本文作者的个人简介页面。
Go Komura
小村软件有限公司 代表
以 Windows 软件开发、技术咨询与故障排查为中心,擅长难以复现的故障调查,以及既有资产仍在运行的项目。