Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[net7] Use high-resolution sleep primitives to enhance CPU efficiency #1599

Closed
wants to merge 12 commits into from
8 changes: 5 additions & 3 deletions .nuke/build.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,6 @@
"items": {
"type": "string",
"enum": [
"Angle",
"Assimp",
"BuildLibSilkDroid",
"Clean",
Expand All @@ -160,9 +159,11 @@
"RegenerateBindings",
"Restore",
"SDL2",
"Shaderc",
"ShipApi",
"SignPackages",
"Sln",
"SPIRVCross",
"SPIRVReflect",
"SwiftShader",
"Test",
Expand All @@ -183,7 +184,6 @@
"items": {
"type": "string",
"enum": [
"Angle",
"Assimp",
"BuildLibSilkDroid",
"Clean",
Expand All @@ -201,9 +201,11 @@
"RegenerateBindings",
"Restore",
"SDL2",
"Shaderc",
"ShipApi",
"SignPackages",
"Sln",
"SPIRVCross",
"SPIRVReflect",
"SwiftShader",
"Test",
Expand Down Expand Up @@ -231,4 +233,4 @@
}
}
}
}
}
15 changes: 15 additions & 0 deletions Silk.NET.sln
Original file line number Diff line number Diff line change
Expand Up @@ -594,6 +594,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Shaderc", "src\SPI
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Silk.NET.Shaderc.Native", "src\Native\Silk.NET.Shaderc.Native\Silk.NET.Shaderc.Native.csproj", "{D1E4EDC7-0A06-498A-B0F9-275B7D508A0E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimeTrials", "src\Lab\Experiments\TimeTrials\TimeTrials.csproj", "{021EE492-FB0E-46CF-AC07-768D5A999798}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -3615,6 +3617,18 @@ Global
{D1E4EDC7-0A06-498A-B0F9-275B7D508A0E}.Release|x64.Build.0 = Release|Any CPU
{D1E4EDC7-0A06-498A-B0F9-275B7D508A0E}.Release|x86.ActiveCfg = Release|Any CPU
{D1E4EDC7-0A06-498A-B0F9-275B7D508A0E}.Release|x86.Build.0 = Release|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Debug|Any CPU.Build.0 = Debug|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Debug|x64.ActiveCfg = Debug|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Debug|x64.Build.0 = Debug|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Debug|x86.ActiveCfg = Debug|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Debug|x86.Build.0 = Debug|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Release|Any CPU.ActiveCfg = Release|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Release|Any CPU.Build.0 = Release|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Release|x64.ActiveCfg = Release|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Release|x64.Build.0 = Release|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Release|x86.ActiveCfg = Release|Any CPU
{021EE492-FB0E-46CF-AC07-768D5A999798}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -3904,6 +3918,7 @@ Global
{1E7C6166-58B2-46B3-A9BA-18099BD83AF0} = {20A4A2D1-D699-4D71-AA97-950154638576}
{E77BE8DB-3C74-42EB-9B65-67EAAA9AD7DB} = {15FC3D1A-25D7-446B-87A7-B45BA3C2225F}
{D1E4EDC7-0A06-498A-B0F9-275B7D508A0E} = {72E7FA64-5B1E-477D-BD30-63B7F206B3C4}
{021EE492-FB0E-46CF-AC07-768D5A999798} = {39B598E9-44BA-4A61-A1BB-7C543734DBA6}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {F5273D7F-3334-48DF-94E3-41AE6816CD4D}
Expand Down
221 changes: 221 additions & 0 deletions src/Core/Silk.NET.Core/Miscellaneous/BreakneckSleep.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

#if NET7_0_OR_GREATER
using System;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Runtime.Intrinsics.Arm;
using System.Runtime.Intrinsics.X86;
using Silk.NET.Core.Native;

namespace Silk.NET.Core;

/// <summary>
/// Implementation of a high-resolution clock intended for short-duration sleeps.
/// </summary>
public readonly struct BreakneckSleep : IDisposable
{
/// <summary>
/// Determines how accurate a <see cref="BreakneckSleep"/> implementation is.
/// </summary>
public enum AccuracyMode
{
/// <summary>
/// The underlying implementation can be trusted to be fully accurate.
/// </summary>
HighestResolution,

/// <summary>
/// The underlying implementation is mostly accurate, but uses a busy loop for the 10% to account for potential
/// inaccuracies.
/// </summary>
HighResolutionWithBusyLoop,

/// <summary>
/// There is no high-resolution implementation available thus a busy loop is used.
/// </summary>
BusyLoopOnly
}

[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static double TrustFactor(AccuracyMode mode) => mode switch
{
AccuracyMode.HighestResolution => 0.9,
_ => 0.75
};

[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool ShouldTrust(double fps, AccuracyMode mode) => (fps, mode) switch
{
(>= 200, AccuracyMode.HighestResolution) => false,
(>= 100, AccuracyMode.HighResolutionWithBusyLoop) => false,
(>= 50, _) => false,
_ => true
};

/// <summary>
/// The underlying handle of the timer, if any.
/// </summary>
public nint Handle { get; }

/// <summary>
/// The accuracy of the underlying implementation.
/// </summary>
public AccuracyMode Accuracy { get; }

/// <summary>
/// Creates a high-resolution timer
/// </summary>
public BreakneckSleep()
{
Accuracy = AccuracyMode.BusyLoopOnly;
if (OperatingSystem.IsWindows())
{
var (handle, isHigh) = WindowsCreate();
Handle = handle;
Accuracy = isHigh ? AccuracyMode.HighestResolution : AccuracyMode.HighResolutionWithBusyLoop;
return;
}

Handle = 0;
if (OperatingSystem.IsLinux() || OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() ||
OperatingSystem.IsTvOS() || OperatingSystem.IsWatchOS() || OperatingSystem.IsMacCatalyst())
{
Accuracy = AccuracyMode.HighResolutionWithBusyLoop;
}
}

/// <summary>
/// Sleeps for <paramref cref="duration"/>.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Sleep(TimeSpan duration)
{
var start = Stopwatch.GetTimestamp();
if (ShouldTrust(1 / duration.TotalSeconds, Accuracy))
{
if (OperatingSystem.IsWindows())
{
WindowsWait(duration);
}

if (OperatingSystem.IsLinux())
{
LinuxWait(duration);
}

if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS() || OperatingSystem.IsTvOS() ||
OperatingSystem.IsWatchOS() || OperatingSystem.IsMacCatalyst())
{
AppleWait(duration);
}
}

do
{
if (X86Base.IsSupported)
{
X86Base.Pause();
}

if (ArmBase.IsSupported)
{
ArmBase.Yield();
}
} while (Stopwatch.GetElapsedTime(start, Stopwatch.GetTimestamp()) < duration);
}

private static unsafe (nint Handle, bool IsHighResolution) WindowsCreate()
{
const uint createWaitableTimerManualReset = 0x00000001;
const uint createWaitableTimerHighResolution = 0x00000002;
const uint timerAllAccess = 0x1F0003;
[DllImport("kernel32.dll", ExactSpelling = true)]
static extern nint CreateWaitableTimerExW
(
SecurityAttributes* lpTimerAttributes,
char* lpTimerName,
uint dwFlags,
uint dwDesiredAccess
);

var ret = CreateWaitableTimerExW
(null, null, createWaitableTimerManualReset | createWaitableTimerHighResolution, timerAllAccess);
return ret != 0
? (ret, true)
: (CreateWaitableTimerExW(null, null, createWaitableTimerManualReset, timerAllAccess), false);
}

private unsafe bool WindowsWait(TimeSpan duration)
{
[DllImport("kernel32.dll", ExactSpelling = true)]
static extern uint SetWaitableTimerEx
(
nint hTimer,
FILETIME* lpDueTime,
long lPeriod,
void* pfnCompletionRoutine,
void* lpArgToCompletionRoutine,
void* wakeContext,
ulong tolerableDelay
);

static FILETIME CreateFileTime(TimeSpan ts)
{
var ul = unchecked((ulong) -ts.Ticks);
return new FILETIME
{
dwHighDateTime = (int) (ul >> 32),
dwLowDateTime = (int) (ul & 0xFFFFFFFF)
};
}

var ft = CreateFileTime
(
Accuracy == AccuracyMode.HighestResolution
? duration
: TimeSpan.FromMicroseconds(duration.TotalMicroseconds * TrustFactor(Accuracy))
);
if (SetWaitableTimerEx(Handle, &ft, 0, null, null, null, 0) == 1)
{
SilkMarshal.WaitWindowsObjects(Handle);
return true;
}

return false;
}

private unsafe void LinuxWait(TimeSpan duration)
{
duration *= TrustFactor(AccuracyMode.HighResolutionWithBusyLoop);

[DllImport("libc", EntryPoint = "nanosleep")]
static extern int Nanosleep(Timespec* req, Timespec* rem);

var ts = new Timespec
{
Seconds = duration.Seconds,
Nanoseconds = duration.Nanoseconds - duration.Seconds * 1000000000
};
_ = Nanosleep(&ts, null);
}

private void AppleWait(TimeSpan duration)
{
[DllImport("libc", EntryPoint = "usleep")]
static extern int Usleep(uint micros);
_ = Usleep((uint) (duration.TotalMicroseconds * TrustFactor(AccuracyMode.HighResolutionWithBusyLoop)));
}

public void Dispose()
{
if (OperatingSystem.IsWindows())
{
SilkMarshal.ThrowHResult(SilkMarshal.CloseWindowsHandle(Handle));
}
}
}
#endif
Loading
Loading