【问题标题】:Control Windows 10's "Power Mode" programmatically以编程方式控制 Windows 10 的“电源模式”
【发布时间】:2020-05-18 12:13:39
【问题描述】:

背景

嗨。我有一个 SB2(Surface Book 2),我是遇到困扰许多 SB2 机器的infamous 0.4GHz throttling problem 的不幸用户之一。问题是 SB2 突然,并且非常频繁地取决于环境温度,从 4GHz 加速到 0.4GHz 并在此停留一两分钟(这会导致整个笔记本电脑严重减速)。这非常令人沮丧,几乎使机器无法用于最简单的工作负载。

微软显然在 2019 年 10 月更新中stated that it fixed the problem,但我和其他几个用户仍然面临它。我非常肯定我的机器是最新的,我什至手动安装了所有最新的 Surface Book 2 固件更新。

以下是问题发生时 CPU 状态的捕获:

如您所见,设备本身的温度并不高,但 CPU 正好在 0.4GHz 处节流。

更多链接:12

解决方法

我几乎尝试了一切。电压不足直到冻结屏幕,禁用 BD PROCHOT,禁用 GPE 中的电源节流,弄乱注册表,调整几个 CPU/GPU 设置。没有任何效果。

节流开始时你只能做两件事:

  1. 等待它完成(通常需要一两分钟)。
  2. 更改Power Mode in windows 10。即使您将其从“最佳性能”更改为“最佳电池寿命”也无关紧要,重要的是您进行了更改。一旦你这样做,节流会在几秒钟内完全停止。这是唯一有效的手动解决方案。

问题

在实践中,无论工作量有多大,每 10 秒更改一次此滑块,可以无限期地带来流畅的体验而不会受到限制。当然,这不是一个可行的手动解决方法。

理论上,我认为如果我能找到一种以编程方式控制此模式的方法,我也许可以通过每 10 秒左右切换一次电源模式来告别这个问题。

我不介意它是在 win32 (winapi) 还是 .net 中。看了很多,找到this关于电源管理,但是win32好像没有设置界面。我可能会忽略它,所以这是我的问题:

有没有办法以编程方式控制 Windows 10 中的电源模式?

【问题讨论】:

  • 在“高性能”和“平衡”之间切换可以解决这个问题吗?见this
  • @str OP 正在尝试以编程方式更改该设置。他们已经知道如何手动完成。
  • 我很清楚什么是电源计划。更改电源模式并不能解决问题,但是当我更改模式时,节流会暂停一段时间作为副作用。正如我所说,我正在尝试以编程方式复制它。
  • @mrahhal 是的,也许你可以使用RegSetValueEx 来修改对应的键值。这需要注册表权限。
  • @StriveSun-MSFT 知道电源模式的注册表项和值是什么吗?我似乎在任何地方都找不到这个。 (我找到了电源方案,但那些与一般电源计划有关,而不是电源模式)

标签: c++ .net winapi


【解决方案1】:

好的...一段时间以来,我一直希望通过命令行或编程访问来调整电源滑块,并且在查看这篇文章时我已经多次遇到过这篇文章。我很惊讶没有其他人费心去弄清楚。我今天自己解决了这个问题,原因是 Windows 11 似乎已经从任务栏中删除了电源滑块,你必须深入“设置”应用来调整它。

如前所述,在注册表项 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes 中,您可以找到值“ActiveOverlayAcPowerScheme”和“ActiveOverlayDcPowerScheme”,它们记录了交流电源和电池电源滑块的当前值,分别。但是,更改这些值不足以调整功率滑块或系统的操作模式。

原来在 C:\Windows\System32\powrprof.dll 中有一个未记录的方法,称为 PowerSetActiveOverlayScheme。它需要一个参数。我“猜测”它会采用与 PowerSetActiveScheme 相同的方式使用 GUID,而且它似乎可以工作。

注意 — Microsoft 不支持使用未记录的 API。此方法可能会在未来的 Windows 版本中中断。它可以用于个人修补,但我不建议在任何实际生产项目中使用它。

这是 C# PInvoke 签名:

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

成功时返回零,失败时返回非零。

调用它很简单:

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

立即生效。这个特殊的 GUID 将滑块一直向右移动,并更新了注册表中的“ActiveOverlayAcPowerScheme”值。使用全零的 GUID 将滑块重置为中间值。当您将电源滑块设置到不同位置时,只需观察注册表中显示的值,您就可以看到哪些 GUID 选项可用。

有两种方法可以用来读取滑块的当前位置。我不确定它们之间的区别是什么,它们在我的测试中每次都返回相同的值。

[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);

它们还在成功时返回零,在失败时返回非零。它们可以被称为...

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

还有一种称为“PowerGetOverlaySchemes”的方法,我认为它可用于获取可用 GUID 的列表。它似乎需要三个参数,我没有费心去弄清楚。

我创建了一个命令行程序,可以用来设置电源模式,可以在https://github.com/AaronKelley/PowerMode找到。

【讨论】:

  • 哇!干得好!从那以后我一直在使用另一台机器,但我会尝试一下,看看它是否真的解决了我的问题。当我这样做时会报告。
  • 今天下午又玩了一些。我想知道,当您有机会解决这个问题时,您是否可以帮我检查一下这种行为……我想看看它在我的系统上是否很奇怪,或者这是否是“正常”行为。 GUID“3af9B8d9-7c97-431d-ad78-34a8bfea439f”应该用于“更好的性能”,即滑块的中间选项。当我设置此值时,在交流电源上,滑块会转到“最佳性能”,而在电池电源上,滑块会转到“省电模式”。要将其设置为“更好的性能”,我必须使用全零 GUID。其他值按预期工作。
  • 这很奇怪。似乎一直在与我合作,它将其设置为“更好的性能”(我有稳定的 Windows 10)。
  • 感谢您的确认。不确定我的系统(在这种情况下也是 Windows 10)发生了什么。也许是因为 OEM 覆盖。 (我知道 OEM 可以设置电源模式行为。)我发布了命令行工具并进行了设置,以便您可以在必要时将自定义 GUID 放入配置文件中。
  • 使用此方法前请阅读this article。没有人能保证这种方法永远可靠。这个答案不建议标注,仅供参考。
【解决方案2】:

Aaron's answer 很棒,对我帮助很大,谢谢。

如果你和我一样

  1. 没有准备好为自己和/或编译他的工具的 Visual Studio

  2. 不一定要从 GitHub 上运行任意可执行文件(没有冒犯),

您可以使用 Python(在本例中为 3)来完成同样的事情。

为了完整起见,我将复制免责声明:

注意 — Microsoft 不支持使用未记录的 API。此方法可能会在未来的 Windows 版本中中断。它可以用于个人修补,但我不建议在任何实际生产项目中使用它。

还请注意,以下只是基本的概念验证代码!

获取当前活动的字节序列:

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())

在我的系统上,这会产生以下值:

Mode Byte Sequence
Better Battery 77c71c9647259d4f81747d86181b8a7a
Better Performance 00000000000000000000000000000000
Best Performance b574d5dea045424f873746345c09c238

显然 Aaron 和我的系统 share the same peculiarity,其中“更好的性能”字节序列全为零(与 3af9B8d9-7c97-431d-ad78-34a8bfea439f 的“预期”值相反)。

请注意,字节序列77c71c9647259d4f81747d86181b8a7a 等同于GUID 961cc777-2547-4f9d-8174-7d86181b8a7ab574d5dea045424f873746345c09c238 代表ded574b5-45a0-4f42-8737-46345c09c238

这是因为 GUID 的写入方式与它们在内存中的实际表示方式不同。 (如果我们假设一个 GUID 的字节被写为ABCD-EF-GH-IJ-KLMN 它的字节序列表示最终是DCBAFEHGIJKLMN)。如果您想了解更多信息,请参阅https://stackoverflow.com/a/6953207(尤其是“二进制编码可能不同”下的段落和表格)和/或https://uuid.ramsey.dev/en/latest/nonstandard/guid.html

设置值(在本例中为“更好的电池”)的工作原理如下:

import ctypes

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

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

对我来说,这是一个试验 Python 的 ctypes 的好机会:)。

【讨论】:

    【解决方案3】:

    这是一个 PowerShell 版本,它设置计划任务以每分钟切换一次电源覆盖。它基于MichaelAaron 的天赐答案。

    在多台联想 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
    

    创建一个每分钟运行上述脚本的计划作业:

    • 请注意,Register-ScheduledJob 仅适用于 Windows PowerShell,不适用于 PowerShell Core
    • 如果不使用系统主体,我将无法开始工作。否则会无限期卡在任务计划程序中,并显示“任务尚未运行。(0x41303)”。
    • Get-Job 将在 Windows PowerShell 中显示作业,但 Receive-Job 不返回任何内容,即使 dir $env:UserProfile\AppData\Local\Microsoft\Windows\PowerShell\ScheduledJobs$taskName\ 中有作业输出输出。这可能是因为在尝试以其他用户身份接收作业时以系统身份运行?
    • 我希望支持 -MaxResultCount 0 以在 Get-Job 中隐藏作业,但可惜不是。
    • 您可以在 Windows 任务计划程序中的任务计划程序库路径 \Microsoft\Windows\PowerShell\ScheduledJobs 下看到该任务
    • 必须有两个脚本块,一个作为命令,一个作为参数(作为字符串被序列化/反序列化),因为 PowerShell 脚本块使用动态闭包而不是词法闭包,因此在创建时会从另一个脚本块引用一个脚本块新的运行空间不太可能。
    • 计划任务的最小间隔为 1 分钟。如果事实证明需要更频繁的切换,可能只需在切换代码中添加一个循环,并将任务安排为仅用于启动或登录。
    $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
    

    停止和删除计划作业(必须使用 Windows PowerShell):

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

    【讨论】:

      猜你喜欢
      • 2013-09-07
      • 1970-01-01
      • 2011-04-17
      • 1970-01-01
      • 1970-01-01
      • 2022-08-23
      • 2013-05-15
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多