在最近的Windows 11 22H2中,新增了一个被称为“效率模式”(Efficiency Mode)的功能,启用后操作系统将自动分配处理器时间片并适当地调整CPU时钟速度,从而提高能源效率/延长电池续航,减少风扇噪音和电源损耗。微软鼓励开发者使用此功能提高进程效能。
如下图所示,CPU的速度越快,性能越强,但功耗却呈指数级增长。这种高功耗会导致笔记本设备电池续航尿崩、温度飙升和更吵的风扇,而并非所有任务都需要高性能。
功能概念
进程和线程关联服务质量(QoS),在Win10 21359 中新增的 EcoQoS 状态对没有显著性能和延迟要求的工作负载非常有效。开发者可调用 API 来将其进程和线程注明为此状态,其余的由 Windows 负责。
适用场景
优先考虑节能的后端服务、程序更新、同步引擎、索引等,或者希望程序在后台工作时不那么过多的去占用计算机性能资源时。
使用方法
通过SetProcessInformation()或SetThreadInformation()函数更改相关进程/线程PROCESS_POWER_THROTTLING_STATE,并将其中ControlMask和StateMask成员配置为PROCESS_POWER_THROTTLING_EXECUTION_SPEED。
特别注意
以上两个API均需要相关句柄拥有PROCESS_SET_INFORMATION权限。
若使用GetCurrentProcess()获得的自身伪句柄可跳过此节,因其默认已拥有相应权限。
若使用CreateProcess()的句柄可跳过此节,因其默认已拥有相应权限。
以下为OpenProcessToken()更改句柄权限的方式。
HANDLE hToken;
BOOL bResult = OpenProcessToken(原始句柄, PROCESS_SET_INFORMATION, &hToken);
若未拥有进程句柄,仅已知进程名称,则可通过拍摄进程快照+遍历比对szExeFile获得进程PID后OpenProcess()直接获得,可参考C++代码如下:
#include <Tlhelp32.h>
/* Unicode版本 */
DWORD GetProcessIdByNameW(LPCWSTR lpFileName) {
// 拍摄进程快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return -1;
}
PROCESSENTRY32 pe32 = { sizeof(pe32) };
BOOL bRet = Process32First(hSnapshot, &pe32);
while (bRet) {
std::wstring wsExeName = pe32.szExeFile;
// find找不到值 返回string::npos
if (wsExeName.find(lpFileName) != wsExeName.npos) {
CloseHandle(hSnapshot);
return pe32.th32ProcessID;
}
bRet = Process32Next(hSnapshot, &pe32);
}
CloseHandle(hSnapshot);
return -1;
}
/* ANSI多字符集版本 */
DWORD GetProcessIdByNameA(LPCSTR lpFileName) {
// 拍摄进程快照
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE) {
return -1;
}
PROCESSENTRY32 pe32 = { sizeof(pe32) };
BOOL bRet = Process32First(hSnapshot, &pe32);
while (bRet) {
if (0 == strcmp(lpFileName, pe32.szExeFile)) {
CloseHandle(hSnapshot);
return pe32.th32ProcessID;
}
bRet = Process32Next(hSnapshot, &pe32);
}
CloseHandle(hSnapshot);
return -1;
}
调用:
HANDLE hProcess = OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetProcessIdByName("进程名.exe"));
使用示例
PROCESS_POWER_THROTTLING_STATE 结构指定电源管理相关限制策略,此结构配合 SetProcessInformation() 函数对目标进程使用。
其中ControlMask成员不作为权重开关,EcoQos具体是否生效由StateMask决定。
【C++】
#include <Windows.h>
BOOL SetProcessEcoQoS(HANDLE hProcess, bool bFlag) {
// 此句柄必须具有 PROCESS_SET_INFORMATION 访问权限
PROCESS_POWER_THROTTLING_STATE PowerThrottling;
RtlZeroMemory(&PowerThrottling, sizeof(PowerThrottling));
PowerThrottling.Version = PROCESS_POWER_THROTTLING_CURRENT_VERSION;
PowerThrottling.ControlMask = bFlag ? PROCESS_POWER_THROTTLING_EXECUTION_SPEED : NULL;
PowerThrottling.StateMask = bFlag ? PROCESS_POWER_THROTTLING_EXECUTION_SPEED : NULL;
if (!SetProcessInformation(hProcess, ProcessPowerThrottling, &PowerThrottling, sizeof(PowerThrottling))) {
return FALSE;
}
if (!SetPriorityClass(hProcess, bFlag ? IDLE_PRIORITY_CLASS : NORMAL_PRIORITY_CLASS)) {
return FALSE;
}
return TRUE;
}
【C#】
// 声明导入函数
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetProcessInformation([In] IntPtr hProcess,
[In] PROCESS_INFORMATION_CLASS ProcessInformationClass, IntPtr ProcessInformation, uint ProcessInformationSize);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool SetPriorityClass(IntPtr handle, uint priorityClass);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern IntPtr OpenProcess(uint processAccess, bool bInheritHandle, uint processId);
private enum PROCESS_INFORMATION_CLASS {
ProcessMemoryPriority,
ProcessMemoryExhaustionInfo,
ProcessAppMemoryInfo,
ProcessInPrivateInfo,
ProcessPowerThrottling,
ProcessReservedValue1,
ProcessTelemetryCoverageInfo,
ProcessProtectionLevelInfo,
ProcessLeapSecondInfo,
ProcessInformationClassMax,
}
//实现
public static bool SetProcessEcoQoS(IntPtr hProcess, bool bFlag) {
// 此结构有三个字段Version,ControlMask 和 StateMask
uint controlMask = 0x1; //非权重开关
uint stateMask = (uint)(bFlag ? 0x1 : 0x0);
uint version = 1;
int szControlBlock = 12; // 三个uint的大小
IntPtr homo = Marshal.AllocHGlobal(szControlBlock);
// Marshal.WriteInt32 将一个32位整数写入一个指定偏移量的非托管内存指针
Marshal.WriteInt32(homo, (int)version); //homo 指向内存块开头
Marshal.WriteInt32(homo + 4, (int)controlMask); // 将 controlMask 值写入第2字段地址,需将 homo 指针加4字节
Marshal.WriteInt32(homo + 8, (int)stateMask); // 将 stateMask 值写入第3个字段地址,需将 homo 指针加8个字节
bool bRet = false;
// 此句柄必须具有 PROCESS_SET_INFORMATION 访问权限
if(!SetProcessInformation(hProcess, PROCESS_INFORMATION_CLASS.ProcessPowerThrottling, homo, (uint)szControlBlock)) {
goto End;
}
if(!SetPriorityClass(hProcess, (uint)(bFlag ? 0x40 : 0x20))) {
goto End;
}
bRet = true;
goto End;
End:
Marshal.FreeHGlobal(homo);
return bRet;
}
在C#版本中可以自建PROCESS_POWER_THROTTLING_STATE,但由于防止代码冗余,此处直接使用了Marshal.WriteInt32()函数进行了指针偏移内存写入,以完成对其(在示例中为homo指针)封装。
写在最后,本文作为新人首发尝试,可能有很多不足,还请多多包涵,祝大家编码愉快!