| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160 |
- using System;
- using System.Runtime.InteropServices;
- using System.Runtime.Versioning;
- namespace Ryujinx.Common.PreciseSleep
- {
- /// <summary>
- /// Access to Linux/MacOS nanosleep, with platform specific bias to improve precision.
- /// </summary>
- [SupportedOSPlatform("macos")]
- [SupportedOSPlatform("linux")]
- [SupportedOSPlatform("android")]
- [SupportedOSPlatform("ios")]
- internal static partial class Nanosleep
- {
- private const long LinuxBaseNanosleepBias = 50000; // 0.05ms
- // Penalty for max allowed sleep duration
- private const long LinuxNanosleepAccuracyPenaltyThreshold = 200000; // 0.2ms
- private const long LinuxNanosleepAccuracyPenalty = 30000; // 0.03ms
- // Penalty for base sleep duration
- private const long LinuxNanosleepBasePenaltyThreshold = 500000; // 0.5ms
- private const long LinuxNanosleepBasePenalty = 30000; // 0.03ms
- private const long LinuxNanosleepPenaltyPerMillisecond = 18000; // 0.018ms
- private const long LinuxNanosleepPenaltyCap = 18000; // 0.018ms
- private const long LinuxStrictBiasOffset = 150_000; // 0.15ms
- // Nanosleep duration is biased depending on the requested timeout on MacOS.
- // These match the results when measuring on an M1 processor at AboveNormal priority.
- private const long MacosBaseNanosleepBias = 5000; // 0.005ms
- private const long MacosBiasPerMillisecond = 140000; // 0.14ms
- private const long MacosBiasMaxNanoseconds = 20_000_000; // 20ms
- private const long MacosStrictBiasOffset = 150_000; // 0.15ms
- public static long Bias { get; }
- /// <summary>
- /// Get bias for a given nanosecond timeout.
- /// Some platforms calculate their bias differently, this method can be used to counteract it.
- /// </summary>
- /// <param name="timeoutNs">Nanosecond timeout</param>
- /// <returns>Bias in nanoseconds</returns>
- public static long GetBias(long timeoutNs)
- {
- if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
- {
- long biasNs = Math.Min(timeoutNs, MacosBiasMaxNanoseconds);
- return MacosBaseNanosleepBias + biasNs * MacosBiasPerMillisecond / 1_000_000;
- }
- else
- {
- long bias = LinuxBaseNanosleepBias;
- if (timeoutNs > LinuxNanosleepBasePenaltyThreshold)
- {
- long penalty = (timeoutNs - LinuxNanosleepBasePenaltyThreshold) * LinuxNanosleepPenaltyPerMillisecond / 1_000_000;
- bias += LinuxNanosleepBasePenalty + Math.Min(LinuxNanosleepPenaltyCap, penalty);
- }
- return bias;
- }
- }
- /// <summary>
- /// Get a stricter bias for a given nanosecond timeout,
- /// which can improve the chances the sleep completes before the timeout.
- /// Some platforms calculate their bias differently, this method can be used to counteract it.
- /// </summary>
- /// <param name="timeoutNs">Nanosecond timeout</param>
- /// <returns>Strict bias in nanoseconds</returns>
- public static long GetStrictBias(long timeoutNs)
- {
- if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
- {
- return GetBias(timeoutNs) + MacosStrictBiasOffset;
- }
- else
- {
- long bias = GetBias(timeoutNs) + LinuxStrictBiasOffset;
- if (timeoutNs > LinuxNanosleepAccuracyPenaltyThreshold)
- {
- bias += LinuxNanosleepAccuracyPenalty;
- }
- return bias;
- }
- }
- static Nanosleep()
- {
- Bias = GetBias(0);
- }
- [StructLayout(LayoutKind.Sequential)]
- private struct Timespec
- {
- public long tv_sec; // Seconds
- public long tv_nsec; // Nanoseconds
- }
- [LibraryImport("libc", SetLastError = true)]
- private static partial int nanosleep(ref Timespec req, ref Timespec rem);
- /// <summary>
- /// Convert a timeout in nanoseconds to a timespec for nanosleep.
- /// </summary>
- /// <param name="nanoseconds">Timeout in nanoseconds</param>
- /// <returns>Timespec for nanosleep</returns>
- private static Timespec GetTimespecFromNanoseconds(ulong nanoseconds)
- {
- return new Timespec
- {
- tv_sec = (long)(nanoseconds / 1_000_000_000),
- tv_nsec = (long)(nanoseconds % 1_000_000_000)
- };
- }
- /// <summary>
- /// Sleep for approximately a given time period in nanoseconds.
- /// </summary>
- /// <param name="nanoseconds">Time to sleep for in nanoseconds</param>
- public static void Sleep(long nanoseconds)
- {
- nanoseconds -= GetBias(nanoseconds);
- if (nanoseconds >= 0)
- {
- Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
- Timespec rem = new();
- nanosleep(ref req, ref rem);
- }
- }
- /// <summary>
- /// Sleep for at most a given time period in nanoseconds.
- /// Uses a stricter bias to wake before the requested duration.
- /// </summary>
- /// <remarks>
- /// Due to OS scheduling behaviour, this timeframe may still be missed.
- /// </remarks>
- /// <param name="nanoseconds">Maximum allowed time for sleep</param>
- public static void SleepAtMost(long nanoseconds)
- {
- // Stricter bias to ensure we wake before the timepoint.
- nanoseconds -= GetStrictBias(nanoseconds);
- if (nanoseconds >= 0)
- {
- Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
- Timespec rem = new();
- nanosleep(ref req, ref rem);
- }
- }
- }
- }
|