1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
|
#include <pthread.h>
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/futex.h>
#include <immintrin.h>
#define SPIN_THRESHOLD_NS 500000 // NOTE(peter): 500µs spin threshold for Linux
struct timer_handle {
uint64_t interval_ns;
struct timespec next_deadline;
uint32_t running;
pthread_t timer_thread;
volatile int futex_word;
#ifdef TIMER_DEBUG
struct timespec last_wait_start;
#endif
};
static void timespec_add_ns(struct timespec *ts, uint64_t ns) {
ts->tv_nsec += ns;
while(ts->tv_nsec >= 1000000000L) {
ts->tv_nsec -= 1000000000L;
ts->tv_sec++;
}
}
static int64_t timespec_diff_ns(struct timespec *a, struct timespec *b) {
int64_t sec = a->tv_sec - b->tv_sec;
int64_t nsec = a->tv_nsec - b->tv_nsec;
if(nsec < 0) {
nsec += 1000000000L;
sec -= 1;
}
return sec * 1000000000LL + nsec;
}
static int futex_wait(volatile int *addr, int val) {
return syscall(SYS_futex, addr, FUTEX_WAIT_PRIVATE, val, 0, 0, 0);
}
static int futex_wake(volatile int *addr) {
return syscall(SYS_futex, addr, FUTEX_WAKE_PRIVATE, 1, 0, 0, 0);
}
static void *timer_thread_func(void *arg) {
struct timer_handle *t = (struct timer_handle *)arg;
while(t->running) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
#ifdef TIMER_DEBUG
int64_t remaining_after_sleep_ns = -1;
#endif
int64_t diff_ns = timespec_diff_ns(&t->next_deadline, &now);
if(diff_ns > SPIN_THRESHOLD_NS) {
struct timespec sleep_time;
uint64_t sleep_ns = diff_ns - SPIN_THRESHOLD_NS;
sleep_time.tv_sec = sleep_ns / 1000000000;
sleep_time.tv_nsec = sleep_ns % 1000000000;
nanosleep(&sleep_time, 0);
#ifdef TIMER_DEBUG
clock_gettime(CLOCK_MONOTONIC_RAW, &now);
remaining_after_sleep_ns = timespec_diff_ns(&t->next_deadline, &now);
#endif
}
while(clock_gettime(CLOCK_MONOTONIC_RAW, &now), timespec_diff_ns(&t->next_deadline, &now) > 0) {
_mm_pause();
}
t->futex_word = 1;
futex_wake(&t->futex_word);
#ifdef TIMER_DEBUG
if(t->last_wait_start.tv_sec) {
int64_t total_frame_time_ns = timespec_diff_ns(&now, &t->last_wait_start);
int64_t overshoot_ns = timespec_diff_ns(&now, &t->next_deadline);
if(overshoot_ns < 0) overshoot_ns = 0;
if(remaining_after_sleep_ns >= 0) {
DEBUG_PRINT("[DEBUG] Woke up with %ld ns left. Overshoot: %5ld ns\n", remaining_after_sleep_ns, overshoot_ns);
} else {
DEBUG_PRINT("[DEBUG] No sleep. Overshoot: %ld ns\n", overshoot_ns);
}
}
t->last_wait_start = now;
#endif
timespec_add_ns(&t->next_deadline, t->interval_ns);
}
return 0;
}
static struct timer_handle *timer_new(uint64_t interval_ns) {
struct timer_handle *t = calloc(1, sizeof(struct timer_handle));
t->interval_ns = interval_ns;
clock_gettime(CLOCK_MONOTONIC_RAW, &t->next_deadline);
timespec_add_ns(&t->next_deadline, interval_ns);
t->running = 1;
t->futex_word = 0;
#ifdef TIMER_DEBUG
t->last_wait_start.tv_sec = 0;
t->last_wait_start.tv_nsec = 0;
#endif
pthread_create(&t->timer_thread, 0, timer_thread_func, t);
return t;
}
static void timer_init() {
}
static void timer_shutdown() {
}
static uint32_t timer_wait(struct timer_handle *t) {
futex_wait(&t->futex_word, 0);
t->futex_word = 0;
return 1;
}
static void timer_destroy(struct timer_handle *t) {
t->running = 0;
t->futex_word = 1;
futex_wake(&t->futex_word);
pthread_join(t->timer_thread, 0);
free(t);
}
|