Browse Source

Merge pull request #1719 from ftphikari/precise_sleep

time: add accurate sleep procedure
Jeroen van Rijn 3 years ago
parent
commit
b6f3fa6ee1
4 changed files with 57 additions and 0 deletions
  1. 1 0
      core/runtime/core.odin
  2. 38 0
      core/time/time.odin
  3. 14 0
      core/time/time_unix.odin
  4. 4 0
      core/time/time_windows.odin

+ 1 - 0
core/runtime/core.odin

@@ -401,6 +401,7 @@ Raw_Cstring :: struct {
 		Linux,
 		Linux,
 		Essence,
 		Essence,
 		FreeBSD,
 		FreeBSD,
+		OpenBSD,
 		WASI,
 		WASI,
 		JS,
 		JS,
 		Freestanding,
 		Freestanding,

+ 38 - 0
core/time/time.odin

@@ -213,6 +213,44 @@ time_add :: proc(t: Time, d: Duration) -> Time {
 	return Time{t._nsec + i64(d)}
 	return Time{t._nsec + i64(d)}
 }
 }
 
 
+// Accurate sleep borrowed from: https://blat-blatnik.github.io/computerBear/making-accurate-sleep-function/
+//
+// Accuracy seems to be pretty good out of the box on Linux, to within around 4µs worst case.
+// On Windows it depends but is comparable with regular sleep in the worst case.
+// To get the same kind of accuracy as on Linux, have your program call `win32.time_begin_period(1)` to
+// tell Windows to use a more accurate timer for your process.
+accurate_sleep :: proc(d: Duration) {
+	to_sleep, estimate, mean, m2, count: Duration
+
+	to_sleep = d
+	estimate = 5 * Millisecond
+	mean     = 5 * Millisecond
+	count = 1
+
+	for to_sleep > estimate {
+		start := tick_now()
+		sleep(1 * Millisecond)
+
+		observed := tick_since(start)
+		to_sleep -= observed
+
+		count += 1
+
+		delta := observed - mean
+		mean += delta / count
+		m2 += delta * (observed - mean)
+		stddev := intrinsics.sqrt(f64(m2) / f64(count - 1))
+		estimate = mean + Duration(stddev)
+	}
+
+	start := tick_now()
+	for to_sleep > tick_since(start) {
+		// prevent the spinlock from taking the thread hostage, still accurate enough
+		_yield()
+		// NOTE: it might be possible that it yields for too long, in that case it should spinlock freely for a while
+		// TODO: needs actual testing done to check if that's the case
+	}
+}
 
 
 ABSOLUTE_ZERO_YEAR :: i64(-292277022399) // Day is chosen so that 2001-01-01 is Monday in the calculations
 ABSOLUTE_ZERO_YEAR :: i64(-292277022399) // Day is chosen so that 2001-01-01 is Monday in the calculations
 ABSOLUTE_TO_INTERNAL :: i64(-9223371966579724800) // i64((ABSOLUTE_ZERO_YEAR - 1) * 365.2425 * SECONDS_PER_DAY);
 ABSOLUTE_TO_INTERNAL :: i64(-9223371966579724800) // i64((ABSOLUTE_ZERO_YEAR - 1) * 365.2425 * SECONDS_PER_DAY);

+ 14 - 0
core/time/time_unix.odin

@@ -17,6 +17,20 @@ foreign libc {
 	@(link_name="nanosleep")     _unix_nanosleep     :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 ---
 	@(link_name="nanosleep")     _unix_nanosleep     :: proc(requested: ^TimeSpec, remaining: ^TimeSpec) -> i32 ---
 }
 }
 
 
+foreign import "system:pthread"
+
+import "core:c"
+
+@(private="file")
+@(default_calling_convention="c")
+foreign pthread {
+	sched_yield :: proc() -> c.int ---
+}
+
+_yield :: proc "contextless" () {
+	sched_yield()
+}
+
 TimeSpec :: struct {
 TimeSpec :: struct {
 	tv_sec  : i64,  /* seconds */
 	tv_sec  : i64,  /* seconds */
 	tv_nsec : i64,  /* nanoseconds */
 	tv_nsec : i64,  /* nanoseconds */

+ 4 - 0
core/time/time_windows.odin

@@ -35,3 +35,7 @@ _tick_now :: proc "contextless" () -> Tick {
 	_nsec := mul_div_u64(i64(now), 1e9, i64(qpc_frequency))
 	_nsec := mul_div_u64(i64(now), 1e9, i64(qpc_frequency))
 	return Tick{_nsec = _nsec}
 	return Tick{_nsec = _nsec}
 }
 }
+
+_yield :: proc "contextless" () {
+	win32.SwitchToThread()
+}