Nanosleep.cs 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. using System;
  2. using System.Runtime.InteropServices;
  3. using System.Runtime.Versioning;
  4. namespace Ryujinx.Common.PreciseSleep
  5. {
  6. /// <summary>
  7. /// Access to Linux/MacOS nanosleep, with platform specific bias to improve precision.
  8. /// </summary>
  9. [SupportedOSPlatform("macos")]
  10. [SupportedOSPlatform("linux")]
  11. [SupportedOSPlatform("android")]
  12. [SupportedOSPlatform("ios")]
  13. internal static partial class Nanosleep
  14. {
  15. private const long LinuxBaseNanosleepBias = 50000; // 0.05ms
  16. // Penalty for max allowed sleep duration
  17. private const long LinuxNanosleepAccuracyPenaltyThreshold = 200000; // 0.2ms
  18. private const long LinuxNanosleepAccuracyPenalty = 30000; // 0.03ms
  19. // Penalty for base sleep duration
  20. private const long LinuxNanosleepBasePenaltyThreshold = 500000; // 0.5ms
  21. private const long LinuxNanosleepBasePenalty = 30000; // 0.03ms
  22. private const long LinuxNanosleepPenaltyPerMillisecond = 18000; // 0.018ms
  23. private const long LinuxNanosleepPenaltyCap = 18000; // 0.018ms
  24. private const long LinuxStrictBiasOffset = 150_000; // 0.15ms
  25. // Nanosleep duration is biased depending on the requested timeout on MacOS.
  26. // These match the results when measuring on an M1 processor at AboveNormal priority.
  27. private const long MacosBaseNanosleepBias = 5000; // 0.005ms
  28. private const long MacosBiasPerMillisecond = 140000; // 0.14ms
  29. private const long MacosBiasMaxNanoseconds = 20_000_000; // 20ms
  30. private const long MacosStrictBiasOffset = 150_000; // 0.15ms
  31. public static long Bias { get; }
  32. /// <summary>
  33. /// Get bias for a given nanosecond timeout.
  34. /// Some platforms calculate their bias differently, this method can be used to counteract it.
  35. /// </summary>
  36. /// <param name="timeoutNs">Nanosecond timeout</param>
  37. /// <returns>Bias in nanoseconds</returns>
  38. public static long GetBias(long timeoutNs)
  39. {
  40. if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
  41. {
  42. long biasNs = Math.Min(timeoutNs, MacosBiasMaxNanoseconds);
  43. return MacosBaseNanosleepBias + biasNs * MacosBiasPerMillisecond / 1_000_000;
  44. }
  45. else
  46. {
  47. long bias = LinuxBaseNanosleepBias;
  48. if (timeoutNs > LinuxNanosleepBasePenaltyThreshold)
  49. {
  50. long penalty = (timeoutNs - LinuxNanosleepBasePenaltyThreshold) * LinuxNanosleepPenaltyPerMillisecond / 1_000_000;
  51. bias += LinuxNanosleepBasePenalty + Math.Min(LinuxNanosleepPenaltyCap, penalty);
  52. }
  53. return bias;
  54. }
  55. }
  56. /// <summary>
  57. /// Get a stricter bias for a given nanosecond timeout,
  58. /// which can improve the chances the sleep completes before the timeout.
  59. /// Some platforms calculate their bias differently, this method can be used to counteract it.
  60. /// </summary>
  61. /// <param name="timeoutNs">Nanosecond timeout</param>
  62. /// <returns>Strict bias in nanoseconds</returns>
  63. public static long GetStrictBias(long timeoutNs)
  64. {
  65. if (OperatingSystem.IsMacOS() || OperatingSystem.IsIOS())
  66. {
  67. return GetBias(timeoutNs) + MacosStrictBiasOffset;
  68. }
  69. else
  70. {
  71. long bias = GetBias(timeoutNs) + LinuxStrictBiasOffset;
  72. if (timeoutNs > LinuxNanosleepAccuracyPenaltyThreshold)
  73. {
  74. bias += LinuxNanosleepAccuracyPenalty;
  75. }
  76. return bias;
  77. }
  78. }
  79. static Nanosleep()
  80. {
  81. Bias = GetBias(0);
  82. }
  83. [StructLayout(LayoutKind.Sequential)]
  84. private struct Timespec
  85. {
  86. public long tv_sec; // Seconds
  87. public long tv_nsec; // Nanoseconds
  88. }
  89. [LibraryImport("libc", SetLastError = true)]
  90. private static partial int nanosleep(ref Timespec req, ref Timespec rem);
  91. /// <summary>
  92. /// Convert a timeout in nanoseconds to a timespec for nanosleep.
  93. /// </summary>
  94. /// <param name="nanoseconds">Timeout in nanoseconds</param>
  95. /// <returns>Timespec for nanosleep</returns>
  96. private static Timespec GetTimespecFromNanoseconds(ulong nanoseconds)
  97. {
  98. return new Timespec
  99. {
  100. tv_sec = (long)(nanoseconds / 1_000_000_000),
  101. tv_nsec = (long)(nanoseconds % 1_000_000_000)
  102. };
  103. }
  104. /// <summary>
  105. /// Sleep for approximately a given time period in nanoseconds.
  106. /// </summary>
  107. /// <param name="nanoseconds">Time to sleep for in nanoseconds</param>
  108. public static void Sleep(long nanoseconds)
  109. {
  110. nanoseconds -= GetBias(nanoseconds);
  111. if (nanoseconds >= 0)
  112. {
  113. Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
  114. Timespec rem = new();
  115. nanosleep(ref req, ref rem);
  116. }
  117. }
  118. /// <summary>
  119. /// Sleep for at most a given time period in nanoseconds.
  120. /// Uses a stricter bias to wake before the requested duration.
  121. /// </summary>
  122. /// <remarks>
  123. /// Due to OS scheduling behaviour, this timeframe may still be missed.
  124. /// </remarks>
  125. /// <param name="nanoseconds">Maximum allowed time for sleep</param>
  126. public static void SleepAtMost(long nanoseconds)
  127. {
  128. // Stricter bias to ensure we wake before the timepoint.
  129. nanoseconds -= GetStrictBias(nanoseconds);
  130. if (nanoseconds >= 0)
  131. {
  132. Timespec req = GetTimespecFromNanoseconds((ulong)nanoseconds);
  133. Timespec rem = new();
  134. nanosleep(ref req, ref rem);
  135. }
  136. }
  137. }
  138. }