Skip to content

Commit

Permalink
Implemented power-management, add docs (#68) (#952)
Browse files Browse the repository at this point in the history
* Implemented power-management, add docs (#68)

* Power Management review changes (#68)

* Comparing power plans (#68)

Cosmetic fixes (#68)
  • Loading branch information
MarekM25 authored and AndreyAkinshin committed Apr 1, 2019
1 parent e1d4d2f commit 8aa6ade
Show file tree
Hide file tree
Showing 12 changed files with 327 additions and 1 deletion.
18 changes: 18 additions & 0 deletions docs/articles/configs/powerplans.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
uid: docs.powerplans
name: Power Plans
---

# Power Plans

BenchmarkDotNet forces Windows OS to execute on the High-Performance power plan. You can disable this feature by modify PowerPlanMode property. You can see it in the @BenchmarkDotNet.Samples.IntroPowerPlan.

Please note. During an execution, BenchmarkDotNet saves the current power plan and applies it according to the PowerPlanMode property. When all of the benchmarks finish, a previous power plan comes back. However, if someone killed process or energy was plugged off, we could stay with the High-Performance power plan. In this situation, we should return it manually in Windows Control Panel or by powercfg command.

### Links

* Power policy settings: https://docs.microsoft.com/en-us/windows/desktop/power/power-policy-settings
* Powercfg command: https://docs.microsoft.com/en-us/windows-hardware/design/device-experiences/powercfg-command-line-options
* @BenchmarkDotNet.Samples.IntroPowerPlan

---
18 changes: 18 additions & 0 deletions docs/articles/samples/IntroPowerPlan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
uid: BenchmarkDotNet.Samples.IntroPowerPlan
---

## Sample: IntroPowerPlan

This sample shows how we can turn off changing power plans.

### Source code

[!code-csharp[IntroPowerPlan.cs](../../../samples/BenchmarkDotNet.Samples/IntroPowerPlan.cs)]

### Links

* @docs.powerplans
* The permanent link to this sample: @BenchmarkDotNet.Samples.IntroPowerPlan

---
36 changes: 36 additions & 0 deletions samples/BenchmarkDotNet.Samples/IntroPowerPlan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;

namespace BenchmarkDotNet.Samples
{
[Config(typeof(Config))]
public class IntroPowerPlan
{
private class Config : ManualConfig
{
public Config()
{
Add(Job.MediumRun.WithPowerPlan(PowerPlan.HighPerformance));
Add(Job.MediumRun.WithPowerPlan(PowerPlan.UserPowerPlan));
}
}

[Benchmark]
public int IterationTest()
{
int j = 0;
for (int i = 0; i < short.MaxValue; ++i)
{
j = i;
}

return j;
}

[Benchmark]
public int SplitJoin()
=> string.Join(",", new string[1000]).Split(',').Length;
}
}
8 changes: 8 additions & 0 deletions src/BenchmarkDotNet/Environments/PowerPlan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace BenchmarkDotNet.Environments
{
public enum PowerPlan
{
HighPerformance,
UserPowerPlan,
}
}
63 changes: 63 additions & 0 deletions src/BenchmarkDotNet/Helpers/PowerManagementHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
using System;
using System.Runtime.InteropServices;
using System.Text;

namespace BenchmarkDotNet.Helpers
{
internal class PowerManagementHelper
{
private const uint ErrorMoreData = 234;
private const uint SuccessCode = 0;

internal static Guid? CurrentPlan
{
get
{
IntPtr activeGuidPtr = IntPtr.Zero;
uint res = PowerGetActiveScheme(IntPtr.Zero, ref activeGuidPtr);
if (res != SuccessCode)
return null;

return (Guid)Marshal.PtrToStructure(activeGuidPtr, typeof(Guid));
}
}

internal static string CurrentPlanFriendlyName
{
get
{
uint buffSize = 0;
StringBuilder buffer = new StringBuilder();
IntPtr activeGuidPtr = IntPtr.Zero;
uint res = PowerGetActiveScheme(IntPtr.Zero, ref activeGuidPtr);
if (res != SuccessCode)
return null;
res = PowerReadFriendlyName(IntPtr.Zero, activeGuidPtr, IntPtr.Zero, IntPtr.Zero, buffer, ref buffSize);
if (res == ErrorMoreData)
{
buffer.Capacity = (int)buffSize;
res = PowerReadFriendlyName(IntPtr.Zero, activeGuidPtr,
IntPtr.Zero, IntPtr.Zero, buffer, ref buffSize);
}
if (res != SuccessCode)
return null;

return buffer.ToString();
}
}

internal static bool Set(Guid newPolicy)
{
return PowerSetActiveScheme(IntPtr.Zero, ref newPolicy) == 0;
}

[DllImport("powrprof.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
private static extern uint PowerReadFriendlyName(IntPtr RootPowerKey, IntPtr SchemeGuid, IntPtr SubGroupOfPowerSettingGuid, IntPtr PowerSettingGuid, StringBuilder Buffer, ref uint BufferSize);

[DllImport("powrprof.dll", ExactSpelling = true)]
private static extern int PowerSetActiveScheme(IntPtr ReservedZero, ref Guid policyGuid);

[DllImport("powrprof.dll", ExactSpelling = true)]
private static extern uint PowerGetActiveScheme(IntPtr UserRootPowerKey, ref IntPtr ActivePolicyGuid);
}
}
8 changes: 8 additions & 0 deletions src/BenchmarkDotNet/Jobs/EnvironmentMode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public sealed class EnvironmentMode : JobMode<EnvironmentMode>
public static readonly Characteristic<IntPtr> AffinityCharacteristic = CreateCharacteristic<IntPtr>(nameof(Affinity));
public static readonly Characteristic<GcMode> GcCharacteristic = CreateCharacteristic<GcMode>(nameof(Gc));
public static readonly Characteristic<IReadOnlyList<EnvironmentVariable>> EnvironmentVariablesCharacteristic = CreateCharacteristic<IReadOnlyList<EnvironmentVariable>>(nameof(EnvironmentVariables));
public static readonly Characteristic<PowerPlanMode> PowerPlanModeCharacteristic = CreateCharacteristic<PowerPlanMode>(nameof(PowerPlanMode));

public static readonly EnvironmentMode Clr = new EnvironmentMode(Runtime.Clr).Freeze();
public static readonly EnvironmentMode Core = new EnvironmentMode(Runtime.Core).Freeze();
Expand Down Expand Up @@ -42,6 +43,7 @@ [PublicAPI] public EnvironmentMode(string id, Jit jit, Platform platform) : this
[PublicAPI] public EnvironmentMode(string id) : base(id)
{
GcCharacteristic[this] = new GcMode();
PowerPlanModeCharacteristic[this] = new PowerPlanMode();
}

/// <summary>
Expand Down Expand Up @@ -107,5 +109,11 @@ public void SetEnvironmentVariable(EnvironmentVariable variable)
newVariables.Add(variable);
EnvironmentVariables = newVariables;
}

public PowerPlanMode PowerPlanMode
{
get => PowerPlanModeCharacteristic[this];
set => PowerPlanModeCharacteristic[this] = value;
}
}
}
7 changes: 7 additions & 0 deletions src/BenchmarkDotNet/Jobs/JobExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,13 @@ public static Job WithHeapAffinitizeMask(this Job job, int heapAffinitizeMask) =
/// </summary>
public static Job WithMaxIterationCount(this Job job, int count) => job.WithCore(j => j.Run.MaxIterationCount = count);

/// <summary>
/// Power plan for benchmarks.
/// The default value is HighPerformance.
/// <remarks>Only available for Windows.</remarks>
/// </summary>
public static Job WithPowerPlan(this Job job, PowerPlan powerPlan) => job.WithCore(j => j.Environment.PowerPlanMode.PowerPlan = powerPlan);

// Infrastructure
public static Job With(this Job job, IToolchain toolchain) => job.WithCore(j => j.Infrastructure.Toolchain = toolchain);
[PublicAPI] public static Job With(this Job job, IClock clock) => job.WithCore(j => j.Infrastructure.Clock = clock);
Expand Down
28 changes: 28 additions & 0 deletions src/BenchmarkDotNet/Jobs/PowerPlanMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using BenchmarkDotNet.Characteristics;
using BenchmarkDotNet.Environments;

namespace BenchmarkDotNet.Jobs
{
public sealed class PowerPlanMode : JobMode<PowerPlanMode>
{
public static readonly Characteristic<PowerPlan> PowerPlanCharacteristic = CreateCharacteristic<PowerPlan>(nameof(PowerPlan));

public PowerPlanMode() : this(null)
{
}

private PowerPlanMode(string id) : base(id)
{
}

public PowerPlan PowerPlan
{
get { return PowerPlanCharacteristic[this]; }
set { PowerPlanCharacteristic[this] = value; }
}

public bool Equals(PowerPlanMode other)
=> other != null
&& other.PowerPlan == PowerPlan;
}
}
3 changes: 3 additions & 0 deletions src/BenchmarkDotNet/Running/BenchmarkRunnerClean.cs
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,15 @@ private static Summary Run(BenchmarkRunInfo benchmarkRunInfo,
var config = benchmarkRunInfo.Config;
var reports = new List<BenchmarkReport>();
string title = GetTitle(new[] { benchmarkRunInfo });
var powerManagementApplier = new PowerManagementApplier(logger);

logger.WriteLineInfo($"// Found {benchmarks.Length} benchmarks:");
foreach (var benchmark in benchmarks)
logger.WriteLineInfo($"// {benchmark.DisplayInfo}");
logger.WriteLine();
foreach (var benchmark in benchmarks)
{
powerManagementApplier.ApplyPerformancePlan(benchmark.Job.Environment.PowerPlanMode.PowerPlan);
var info = buildResults[benchmark];
var buildResult = info.buildResult;

Expand Down Expand Up @@ -168,6 +170,7 @@ private static Summary Run(BenchmarkRunInfo benchmarkRunInfo,

var clockSpan = runChronometer.GetElapsed();

powerManagementApplier.ApplyUserPowerPlan();
return new Summary(title,
reports.ToImmutableArray(),
HostEnvironmentInfo.GetCurrent(),
Expand Down
89 changes: 89 additions & 0 deletions src/BenchmarkDotNet/Running/PowerManagementApplier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Loggers;
using BenchmarkDotNet.Portability;

namespace BenchmarkDotNet.Running
{
internal class PowerManagementApplier
{
private readonly ILogger logger;
private Guid? userCurrentPowerPlan;
private bool powerPlanChanged = false;
private bool isInitialized = false;
private static readonly Dictionary<PowerPlan, string> powerPlansDict = new Dictionary<PowerPlan, string>()
{
{ PowerPlan.UserPowerPlan, null },
{ PowerPlan.HighPerformance, "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c" }
};

internal PowerManagementApplier(ILogger logger)
{
this.logger = logger;
}

internal void ApplyPerformancePlan(PowerPlan powerPlan)
{
var guid = powerPlansDict[powerPlan];
ApplyPerformancePlan(guid);
}

internal void ApplyPerformancePlan(string guid)
{
if (RuntimeInformation.IsWindows())
{
if (string.IsNullOrEmpty(guid) == false && powerPlanChanged == false)
ApplyPlanByGuid(guid);
else if (string.IsNullOrEmpty(guid))
ApplyUserPowerPlan();
}
}

internal void ApplyUserPowerPlan()
{
if (powerPlanChanged && RuntimeInformation.IsWindows())
{
try
{
if (userCurrentPowerPlan != null && PowerManagementHelper.Set(userCurrentPowerPlan.Value))
{
powerPlanChanged = false;
var powerPlanFriendlyName = PowerManagementHelper.CurrentPlanFriendlyName;
logger.WriteInfo($"Succesfully reverted power plan (GUID: {userCurrentPowerPlan.Value} FriendlyName: {powerPlanFriendlyName})");
}
}
catch (Exception ex)
{
logger.WriteLineError($"Cannot revert power plan (error message: {ex.Message})");
}
}
}

private void ApplyPlanByGuid(string guid)
{
try
{
if (isInitialized == false)
{
userCurrentPowerPlan = PowerManagementHelper.CurrentPlan;
isInitialized = true;
}

if (PowerManagementHelper.Set(new Guid(guid)))
{
powerPlanChanged = true;
var powerPlanFriendlyName = PowerManagementHelper.CurrentPlanFriendlyName;
logger.WriteInfo($"Setup power plan (GUID: {guid} FriendlyName: {powerPlanFriendlyName})");
}
else
logger.WriteLineError($"Cannot setup power plan (GUID: {guid})");
}
catch (Exception ex)
{
logger.WriteLineError($"Cannot setup power plan (GUID: {guid}, error message: {ex.Message})");
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Helpers;
using BenchmarkDotNet.Portability;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Tests.Loggers;
using BenchmarkDotNet.Tests.XUnit;
using System.Threading;
using Xunit;
using Xunit.Abstractions;

namespace BenchmarkDotNet.IntegrationTests
{
public class PowerManagementApplierTests : BenchmarkTestExecutor
{
public const string HighPerformancePlanGuid = "8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c";

public PowerManagementApplierTests(ITestOutputHelper output) : base(output) { }

[FactWindowsOnly("Setting high-performance plan is suitable only on Windows")]
public void TestSettingAndRevertingBackGuid()
{
var userPlan = PowerManagementHelper.CurrentPlan;
var logger = new OutputLogger(Output);
var powerManagementApplier = new PowerManagementApplier(logger);
var config = DefaultConfig.Instance.With(logger);
powerManagementApplier.ApplyPerformancePlan(PowerPlan.HighPerformance);
Assert.Equal(HighPerformancePlanGuid, PowerManagementHelper.CurrentPlan.ToString());
Assert.Equal("High performance", PowerManagementHelper.CurrentPlanFriendlyName);
powerManagementApplier.ApplyUserPowerPlan();
Assert.Equal(userPlan, PowerManagementHelper.CurrentPlan);
}

[FactWindowsOnly("Setting high-performance plan is suitable only on Windows")]
public void TestPowerPlanShouldNotChange()
{
var userPlan = PowerManagementHelper.CurrentPlan;
var logger = new OutputLogger(Output);
var powerManagementApplier = new PowerManagementApplier(logger);
var config = DefaultConfig.Instance.With(logger);
powerManagementApplier.ApplyPerformancePlan(PowerPlan.UserPowerPlan);
Assert.Equal(userPlan.ToString(), PowerManagementHelper.CurrentPlan.ToString());
powerManagementApplier.ApplyUserPowerPlan();
Assert.Equal(userPlan, PowerManagementHelper.CurrentPlan);
}
}
}
2 changes: 1 addition & 1 deletion tests/BenchmarkDotNet.Tests/Configs/JobTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -415,7 +415,7 @@ public static void Test07GetCharacteristics()
Assert.Equal("Id;Accuracy;AnalyzeLaunchVariance;EvaluateOverhead;" +
"MaxAbsoluteError;MaxRelativeError;MinInvokeCount;MinIterationTime;OutlierMode;Environment;Affinity;EnvironmentVariables;" +
"Jit;Platform;Runtime;Gc;AllowVeryLargeObjects;Concurrent;CpuGroups;Force;HeapAffinitizeMask;HeapCount;NoAffinitize;" +
"RetainVm;Server;Infrastructure;Arguments;BuildConfiguration;Clock;EngineFactory;NuGetReferences;Toolchain;Meta;Baseline;IsDefault;IsMutator;Run;InvocationCount;IterationCount;IterationTime;" +
"RetainVm;Server;PowerPlanMode;PowerPlan;Infrastructure;Arguments;BuildConfiguration;Clock;EngineFactory;NuGetReferences;Toolchain;Meta;Baseline;IsDefault;IsMutator;Run;InvocationCount;IterationCount;IterationTime;" +
"LaunchCount;MaxIterationCount;MaxWarmupIterationCount;MinIterationCount;MinWarmupIterationCount;RunStrategy;UnrollFactor;WarmupCount", string.Join(";", a));
}

Expand Down

0 comments on commit 8aa6ade

Please sign in to comment.