简体   繁体   English

以编程方式控制 Windows 10 的“电源模式”

[英]Control Windows 10's "Power Mode" programmatically

Background背景

Hi.你好。 I have an SB2 (Surface Book 2), and I'm one of the unlucky users who are facing the infamous 0.4GHz throttling problem that is plaguing many of the SB2 machines.我有一个 SB2(Surface Book 2),我是面临臭名昭著的 0.4GHz 节流问题的不幸用户之一,这个问题困扰着许多 SB2 机器。 The problem is that the SB2 suddenly, and very frequently depending on the ambient temperature, throttles heavily from a boost of 4GHz to 0.4GHz and hangs in there for a minute or two (this causes a severe slowup of the whole laptop).问题是 SB2 突然并且非常频繁地取决于环境温度,从 4GHz 的提升到 0.4GHz 大幅节流并在那里停留一两分钟(这会导致整个笔记本电脑严重减速)。 This is extremely frustrating and almost makes the machine unusable for even the simplest of workloads.这非常令人沮丧,几乎使机器无法用于即使是最简单的工作负载。

Microsoft apparently stated that it fixed the problem in the October 2019 update, but I and several other users are still facing it.微软显然表示它在 2019 年 10 月的更新中解决了这个问题,但我和其他几个用户仍然面临这个问题。 I'm very positive my machine is up to date, and I even manually installed all the latest Surface Book 2 firmware updates.我非常肯定我的机器是最新的,我什至手动安装了所有最新的 Surface Book 2 固件更新。

Here's a capture of the CPU state when the problem is happening:这是问题发生时 CPU state 的捕获: 节流图像

As you can see, the temperature of the unit itself isn't high at all, but CPU is throttling at 0.4GHz exactly.如您所见,设备本身的温度一点也不高,但 CPU 的频率恰好在 0.4GHz。

More links about this: 1 2有关此的更多链接: 1 2

Workarounds解决方法

I tried pretty much EVERYTHING.我几乎尝试了一切。 Undervolting until freezing screens, disabling BD PROCHOT, disabling power throttling in GPE, messing up with the registry, tuning several CPU/GPU settings.降低电压直到冻结屏幕,禁用 BD PROCHOT,禁用 GPE 中的电源节流,搞乱注册表,调整几个 CPU/GPU 设置。 Nothing worked.没有任何效果。

You can do only 2 things when the throttling starts:节流开始时你只能做两件事:

  1. Wait for it to finish (usually takes a minute or two).等待它完成(通常需要一两分钟)。
  2. Change the Power Mode in windows 10 .更改windows 中的电源模式 10 . It doesn't even matter if you're changing it from "Best performance" to "Best battery life", what matters is that you change it.将其从“最佳性能”更改为“最佳电池寿命”甚至都没有关系,重要的是您进行了更改。 As soon as you do, throttling completely stops in a couple seconds.只要你这样做,节流就会在几秒钟内完全停止。 This is the only manual solution that worked.这是唯一有效的手动解决方案。

Question

In practice, changing this slider each 10 seconds no matter how heavy the workload is, indefinitely lead to a smooth experience without throttling.在实践中,无论工作量有多大,每 10 秒更改一次 slider,就能无限期地带来流畅的体验,而不会节流。 Of course, this isn't a feasible workaround by hand.当然,这不是手动可行的解决方法。

In theory, I thought that if I could find a way to control this mode programmatically, I might be able to wish this problem goodbye by switching power modes every 10 seconds or so.理论上,我想如果我能找到一种方法来以编程方式控制此模式,我也许可以通过每 10 秒左右切换一次电源模式来告别这个问题。

I don't mind if it's in win32 (winapi) or a .net thing.我不介意它是在 win32 (winapi) 中还是在 .net 中。 I looked a lot, found this about power management, but it seems there's no interface for setting in win32.找了很多,发现这个是关于电源管理的,但是win32好像没有设置界面。 I could have overlooked it, so here's my question:我本可以忽略它,所以这是我的问题:

Is there any way at all to control the Power Mode in Windows 10 programmatically?有没有办法以编程方式控制 Windows 10 中的电源模式?

OK... I've been wanting command line or programmatic access to adjust the power slider for a while, and I've run across this post multiple times when looking into it.好的......我一直想要命令行或编程访问来调整电源 slider 一段时间,并且在查看它时我已经多次遇到这个帖子。 I'm surprised no one else has bothered to figure it out.我很惊讶没有其他人费心去弄明白。 I worked it out myself today, motivated by the fact that Windows 11 appears to have removed the power slider from the taskbar and you have to go digging into the Settings app to adjust it.我今天自己解决了这个问题,原因是 Windows 11 似乎已经从任务栏中删除了电源 slider 并且您必须 go 深入“设置”应用程序进行调整。

As previously discussed, in the registry key HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes you can find values "ActiveOverlayAcPowerScheme" and "ActiveOverlayDcPowerScheme" which record the current values of the slider for AC power and battery power, respectively.如前所述,在注册表项 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes 中,您可以找到值“ActiveOverlayAcPowerScheme”和“ActiveOverlayDcPowerScheme”,它们分别记录了 slider 的交流电源和电池电源的当前值。 However, changing these values is not sufficient to adjust the power slider or the system's mode of operation.但是,更改这些值不足以调整电源 slider 或系统的操作模式。

Turns out there is an undocumented method in C:\Windows\System32\powrprof.dll called PowerSetActiveOverlayScheme.原来在 C:\Windows\System32\powrprof.dll 中有一个未记录的方法叫做 PowerSetActiveOverlayScheme。 It takes a single parameter.它需要一个参数。 I "guessed" that it would take a GUID in the same manner that PowerSetActiveScheme does, and it seems to work.我“猜测”它会以与 PowerSetActiveScheme 相同的方式获取 GUID,而且它似乎可以工作。

Note — Using an undocumented API is unsupported by Microsoft.注意 — Microsoft 不支持使用未记录的 API。 This method may break in future Windows releases.此方法可能会在未来的 Windows 版本中失效。 It can be used for personal tinkering but I would not suggest using it in any actual production projects.它可以用于个人修补,但我不建议在任何实际生产项目中使用它。

Here is the C# PInvoke signature:这是 C# PInvoke 签名:

[DllImportAttribute("powrprof.dll", EntryPoint = "PowerSetActiveOverlayScheme")]
public static extern uint PowerSetActiveOverlayScheme(Guid OverlaySchemeGuid);

It returns zero on success and non-zero on failure.它在成功时返回零,在失败时返回非零。

Calling it is as simple as:调用它很简单:

PowerSetActiveOverlayScheme(new Guid("ded574b5-45a0-4f42-8737-46345c09c238"));

It has immediate effect.它具有立竿见影的效果。 This particular GUID moved the slider all the way to the right for me and also updated the "ActiveOverlayAcPowerScheme" value in the registry.这个特定的 GUID 将 slider 一直移到我的右边,还更新了注册表中的“ActiveOverlayAcPowerScheme”值。 Using a GUID of all zeros reset the slider to the middle value.使用全零的 GUID 将 slider 重置为中间值。 You can see what GUID options are available by just observing the values that show up in the registry when you set the power slider to different positions.当您将电源 slider 设置到不同位置时,您可以通过观察注册表中显示的值来查看可用的 GUID 选项。

There are two methods that can be used to read the current position of the slider. I'm not sure what the difference between them is, they returned the same value each time in my testing.有两种方法可用于读取 slider 的当前 position。我不确定它们之间的区别是什么,在我的测试中它们每次都返回相同的值。

[DllImportAttribute("powrprof.dll", EntryPoint = "PowerGetActualOverlayScheme")]
public static extern uint PowerGetActualOverlayScheme(out Guid ActualOverlayGuid);

[DllImportAttribute("powrprof.dll", EntryPoint = "PowerGetEffectiveOverlayScheme")]
public static extern uint PowerGetEffectiveOverlayScheme(out Guid EffectiveOverlayGuid);

They also return zero on success and non-zero on failure.它们还会在成功时返回零,在失败时返回非零。 They can be called like...他们可以被称为...

if (PowerGetEffectiveOverlayScheme(out Guid activeScheme) == 0)
{
    Console.WriteLine(activeScheme);
}

There is one more method called "PowerGetOverlaySchemes", which I presume can be used to fetch a list of available GUIDs that could be used.还有一种称为“PowerGetOverlaySchemes”的方法,我认为它可用于获取可使用的可用 GUID 列表。 It appears to take three parameters and I haven't bothered with figuring it out.它似乎需要三个参数,我没有费心去弄明白。

I created a command-line program which can be used to set the power mode, and it can be found at https://github.com/AaronKelley/PowerMode .我创建了一个可用于设置电源模式的命令行程序,可以在https://github.com/AaronKelley/PowerMode找到。

Aaron's answer is awesome work, helped me massively, thank you.亚伦的回答很棒,对我帮助很大,谢谢。

If you're anything like me and如果你和我一样

  1. don't have Visual Studio at the ready to compile his tool for yourself and/or没有准备好为自己和/或编译他的工具的 Visual Studio

  2. don't necessarily want to run an arbitrary executable file off of GitHub (no offence),不一定要运行 GitHub 的任意可执行文件(无罪),

you can use Python (3, in this case) to accomplish the same thing.您可以使用 Python(在本例中为 3)来完成同样的事情。

For completeness' sake, I'll copy over the disclaimer:为了完整起见,我将复制免责声明:

Note — Using an undocumented API is unsupported by Microsoft.注意 — Microsoft 不支持使用未记录的 API。 This method may break in future Windows releases.此方法可能会在未来的 Windows 版本中失效。 It can be used for personal tinkering but I would not suggest using it in any actual production projects.它可以用于个人修补,但我不建议在任何实际生产项目中使用它。

Please also note, that the following is just basic proof-of-concept code!另请注意,以下只是基本的概念验证代码!

Getting the currently active Byte Sequence:获取当前活动的字节序列:

import ctypes

output_buffer = ctypes.create_string_buffer(b"",16)

ctypes.windll.powrprof.PowerGetEffectiveOverlayScheme(output_buffer)
print("Current Effective Byte Sequence: " + output_buffer.value.hex())

ctypes.windll.powrprof.PowerGetActualOverlayScheme(output_buffer)
print("Current Actual Byte Sequence: " + output_buffer.value.hex())

On my system, this results in the following values:在我的系统上,这会产生以下值:

Mode模式 Byte Sequence字节序列
Better Battery更好的电池 77c71c9647259d4f81747d86181b8a7a
Better Performance更好的性能 00000000000000000000000000000000
Best Performance最棒的表演 b574d5dea045424f873746345c09c238

Apparently Aaron's and my system share the same peculiarity , where the "Better Performance" Byte Sequence is just all zeros (as opposed to the "expected" value of 3af9B8d9-7c97-431d-ad78-34a8bfea439f ).显然 Aaron 和我的系统具有相同的特性,其中“更好的性能”字节序列只是全零(与3af9B8d9-7c97-431d-ad78-34a8bfea439f的“预期”值相反)。

Please note, that the Byte Sequence 77c71c9647259d4f81747d86181b8a7a is equivalent to the GUID 961cc777-2547-4f9d-8174-7d86181b8a7a and b574d5dea045424f873746345c09c238 represents ded574b5-45a0-4f42-8737-46345c09c238 . Please note, that the Byte Sequence 77c71c9647259d4f81747d86181b8a7a is equivalent to the GUID 961cc777-2547-4f9d-8174-7d86181b8a7a and b574d5dea045424f873746345c09c238 represents ded574b5-45a0-4f42-8737-46345c09c238 .

This stems from the the fact that GUIDs are written down differently than how they're actually represented in memory. (If we assume a GUID's bytes to be written as ABCD-EF-GH-IJ-KLMN its Byte Sequence representation ends up being DCBAFEHGIJKLMN ).这是因为 GUID 的写法与它们在 memory 中的实际表示方式不同。(如果我们假设 GUID 的字节写为ABCD-EF-GH-IJ-KLMN其字节序列表示最终为DCBAFEHGIJKLMN ). See https://stackoverflow.com/a/6953207 (particularly the pararaph and table under "Binary encodings could differ") and/or https://uuid.ramsey.dev/en/latest/nonstandard/guid.html if you want to know more.请参阅https://stackoverflow.com/a/6953207 (特别是“二进制编码可能不同”下的段落和表格)和/或https://uuid.ramsey.dev/en/latest/nonstandard/guid.html如果您想知道更多。

Setting a value (for "Better Battery" in this example) works as follows:设置一个值(在本例中为“Better Battery”)的工作方式如下:

import ctypes

modes = {
    "better_battery":     "77c71c9647259d4f81747d86181b8a7a",
    "better_performance": "00000000000000000000000000000000",
    "best_performance":   "b574d5dea045424f873746345c09c238"
}

ctypes.windll.powrprof.PowerSetActiveOverlayScheme(bytes.fromhex(modes["better_battery"]))

For me, this was a nice opportunity to experiment with Python's ctypes :).对我来说,这是一个很好的机会来试验 Python 的ctypes :)。

Here is a PowerShell version that sets up a scheduled task to toggle the power overlay every minute.这里有一个 PowerShell 版本,它设置了一个计划任务,每分钟切换一次电源覆盖。 It is based off the godsend answers of Michael and Aaron .它基于MichaelAaron的天赐答案。

The CPU throttling issue has plagued me on multiple Lenovo X1 Yoga laptops (Gen2 and Gen4 models).在多台 Lenovo X1 Yoga 笔记本电脑(Gen2 和 Gen4 型号)上,CPU 节流问题一直困扰着我。

# Toggle power mode away from and then back to effective overlay 
$togglePowerOverlay = {
    $function = @'
    [DllImport("powrprof.dll", EntryPoint="PowerSetActiveOverlayScheme")]
    public static extern int PowerSetActiveOverlayScheme(Guid OverlaySchemeGuid);
    [DllImport("powrprof.dll", EntryPoint="PowerGetActualOverlayScheme")]
    public static extern int PowerGetActualOverlayScheme(out Guid ActualOverlayGuid);
    [DllImport("powrprof.dll", EntryPoint="PowerGetEffectiveOverlayScheme")]
    public static extern int PowerGetEffectiveOverlayScheme(out Guid EffectiveOverlayGuid);
'@
    $power = Add-Type -MemberDefinition $function -Name "Power" -PassThru -Namespace System.Runtime.InteropServices
    
    $modes = @{
        "better_battery"     = [guid] "961cc777-2547-4f9d-8174-7d86181b8a7a";
        "better_performance" = [guid] "00000000000000000000000000000000";
        "best_performance"   = [guid] "ded574b5-45a0-4f42-8737-46345c09c238"
    }
    
    $actualOverlayGuid = [Guid]::NewGuid()
    $ret = $power::PowerGetActualOverlayScheme([ref]$actualOverlayGuid)
    if ($ret -eq 0) {
        "Actual power overlay scheme: $($($modes.GetEnumerator()|where {$_.value -eq  $actualOverlayGuid}).Key)." | Write-Host
    }
    
    $effectiveOverlayGuid = [Guid]::NewGuid()
    $ret = $power::PowerGetEffectiveOverlayScheme([ref]$effectiveOverlayGuid)
    
    if ($ret -eq 0) {        
        "Effective power overlay scheme: $($($modes.GetEnumerator() | where { $_.value -eq  $effectiveOverlayGuid }).Key)." | Write-Host
        
        $toggleOverlayGuid = if ($effectiveOverlayGuid -ne $modes["best_performance"]) { $modes["best_performance"] } else { $modes["better_performance"] }     
        
        # Toggle Power Mode
        $ret = $power::PowerSetActiveOverlayScheme($toggleOverlayGuid)
        if ($ret -eq 0) {
            "Toggled power overlay scheme to: $($($modes.GetEnumerator()| where { $_.value -eq  $toggleOverlayGuid }).Key)." | Write-Host
        }

        $ret = $power::PowerSetActiveOverlayScheme($effectiveOverlayGuid)
        if ($ret -eq 0) {
            "Toggled power overlay scheme back to: $($($modes.GetEnumerator()|where {$_.value -eq  $effectiveOverlayGuid }).Key)." | Write-Host
        }
    }
    else {
        "Failed to toggle active power overlay scheme." | Write-Host      
    }
}

# Execute the above
& $togglePowerOverlay

Create a scheduled job that runs the above script every minute:创建一个每分钟运行上述脚本的计划作业:

  • Note that Register-ScheduledJob only works with Windows PowerShell, not PowerShell Core请注意 Register-ScheduledJob 仅适用于 Windows PowerShell,不适用于 PowerShell Core
  • I couldn't get the job to start without using the System principal.如果不使用系统主体,我将无法开始工作。 Otherwise gets stuck indefinitely in Task Scheduler with "The task has not run yet. (0x41303)".否则会无限期地卡在任务计划程序中,并显示“任务尚未运行。(0x41303)”。
  • Get-Job will show the job in Windows PowerShell, but Receive-Job doesn't return anything even though there is job output in dir $env:UserProfile\AppData\Local\Microsoft\Windows\PowerShell\ScheduledJobs$taskName\Output. Get-Job 将在 Windows PowerShell 中显示作业,但 Receive-Job 不会返回任何内容,即使目录 $env:UserProfile\AppData\Local\Microsoft\Windows\PowerShell\ScheduledJobs$taskName\Output 中有作业 output。 This might be due to running as System while trying to Receive-Job as another user?这可能是由于在尝试以另一个用户身份接收作业时以系统身份运行?
  • I wish -MaxResultCount 0 was supported to hide the job in Get-Job, but alas it is not.我希望支持 -MaxResultCount 0 以隐藏 Get-Job 中的作业,但可惜事实并非如此。
  • You can see the task in Windows Task Scheduler under Task Scheduler Library path \Microsoft\Windows\PowerShell\ScheduledJobs可以在Task Scheduler Library路径\Microsoft\Windows\PowerShell\ScheduledJobs下的Windows Task Scheduler中看到任务
  • It was necessary to have two script blocks, one as command and one as arguments (that gets serialized/deserialized as a string) because PowerShell script blocks use dynamic closures instead of lexical closures and thus referencing one script block from another when creating a new runspace is not readily possible.必须有两个脚本块,一个作为命令,一个作为 arguments(被序列化/反序列化为字符串),因为 PowerShell 脚本块使用动态闭包而不是词法闭包,因此在创建新的运行空间时从另一个脚本块引用一个脚本块不容易。
  • The min interval for scheduled tasks is 1 minute.计划任务的最小间隔为 1 分钟。 If it turns out that more frequent toggling is needed, might just add a loop in the toggling code and schedule the task only for startup or login.如果事实证明需要更频繁的切换,可能只需在切换代码中添加一个循环并安排任务仅用于启动或登录。
$registerJob = {
    param($script)
    $taskName = "FixCpuThrottling"
    Unregister-ScheduledJob -Name $taskName -ErrorAction Ignore
    $job = Register-ScheduledJob -Name $taskName -ScriptBlock $([scriptblock]::create($script)) -RunEvery $([TimeSpan]::FromMinutes(1)) -MaxResultCount 1
    $psSobsSchedulerPath = "\Microsoft\Windows\PowerShell\ScheduledJobs";
    $principal = New-ScheduledTaskPrincipal -UserId SYSTEM -LogonType ServiceAccount 
    $someResult = Set-ScheduledTask -TaskPath $psSobsSchedulerPath -TaskName $taskName -Principal $principal
}

# Run as Administrator needed in order to call Register-ScheduledJob
powershell.exe -command $registerJob -args $togglePowerOverlay

To stop and remove the scheduled job (must use Windows PowerShell):要停止并删除计划的作业(必须使用 Windows PowerShell):

$taskName = "FixCpuThrottling"
Unregister-ScheduledJob -Name $taskName-ErrorAction Ignore

Background背景

Hi.你好。 I have an SB2 (Surface Book 2), and I'm one of the unlucky users who are facing the infamous 0.4GHz throttling problem that is plaguing many of the SB2 machines.我有一个 SB2(Surface Book 2),我是不幸的用户之一,他们面临着困扰许多 SB2 机器的臭名昭著的 0.4GHz 节流问题 The problem is that the SB2 suddenly, and very frequently depending on the ambient temperature, throttles heavily from a boost of 4GHz to 0.4GHz and hangs in there for a minute or two (this causes a severe slowup of the whole laptop).问题是 SB2 突然,并且非常频繁地取决于环境温度,从 4GHz 加速到 0.4GHz 并在其中挂起一两分钟(这会导致整个笔记本电脑严重减速)。 This is extremely frustrating and almost makes the machine unusable for even the simplest of workloads.这非常令人沮丧,几乎使机器无法用于最简单的工作负载。

Microsoft apparently stated that it fixed the problem in the October 2019 update, but I and several other users are still facing it.微软显然表示它在 2019 年 10 月的更新中修复了这个问题,但我和其他几个用户仍然面临着这个问题。 I'm very positive my machine is up to date, and I even manually installed all the latest Surface Book 2 firmware updates.我非常肯定我的机器是最新的,我什至手动安装了所有最新的 Surface Book 2 固件更新。

Here's a capture of the CPU state when the problem is happening:这是发生问题时 CPU state 的捕获: 节流图像

As you can see, the temperature of the unit itself isn't high at all, but CPU is throttling at 0.4GHz exactly.如您所见,该单元本身的温度并不高,但 CPU 恰好在 0.4GHz 处节流。

More links about this: 1 2有关此的更多链接: 1 2

Workarounds解决方法

I tried pretty much EVERYTHING.我几乎尝试了一切。 Undervolting until freezing screens, disabling BD PROCHOT, disabling power throttling in GPE, messing up with the registry, tuning several CPU/GPU settings.电压不足直到冻结屏幕,禁用 BD PROCHOT,禁用 GPE 中的电源节流,弄乱注册表,调整几个 CPU/GPU 设置。 Nothing worked.没有任何效果。

You can do only 2 things when the throttling starts:节流开始时,您只能做两件事:

  1. Wait for it to finish (usually takes a minute or two).等待它完成(通常需要一两分钟)。
  2. Change the Power Mode in windows 10 .更改windows 10 中的电源模式 It doesn't even matter if you're changing it from "Best performance" to "Best battery life", what matters is that you change it.即使您将其从“最佳性能”更改为“最佳电池寿命”也无所谓,重要的是您要对其进行更改。 As soon as you do, throttling completely stops in a couple seconds.一旦你这样做,节流会在几秒钟内完全停止。 This is the only manual solution that worked.这是唯一有效的手动解决方案。

Question问题

In practice, changing this slider each 10 seconds no matter how heavy the workload is, indefinitely lead to a smooth experience without throttling.在实践中,无论工作量有多大,每 10 秒更换一次 slider,都可以无限期地带来流畅的体验而不会卡顿。 Of course, this isn't a feasible workaround by hand.当然,这不是一个可行的手动解决方法。

In theory, I thought that if I could find a way to control this mode programmatically, I might be able to wish this problem goodbye by switching power modes every 10 seconds or so.从理论上讲,我认为如果我能找到一种以编程方式控制此模式的方法,我也许可以通过每 10 秒左右切换一次电源模式来告别这个问题。

I don't mind if it's in win32 (winapi) or a .net thing.我不介意它是在 win32 (winapi) 还是 .net 中。 I looked a lot, found this about power management, but it seems there's no interface for setting in win32.看了很多,发现这个是关于电源管理的,但是win32好像没有设置界面。 I could have overlooked it, so here's my question:我可能会忽略它,所以这是我的问题:

Is there any way at all to control the Power Mode in Windows 10 programmatically?有没有办法以编程方式控制 Windows 10 中的电源模式?

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM