Skip to content

Commit c912296

Browse files
committed
SpinLock: loopHint & yield distinction
1 parent 26e08d5 commit c912296

File tree

3 files changed

+25
-26
lines changed

3 files changed

+25
-26
lines changed

lib/std/mutex.zig

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ else if (builtin.os == .windows)
7575

7676
fn acquireSlow(self: *Mutex) Held {
7777
@setCold(true);
78-
while (true) : (SpinLock.yield(1)) {
78+
while (true) : (SpinLock.loopHint(1)) {
7979
const waiters = @atomicLoad(u32, &self.waiters, .Monotonic);
8080

8181
// try and take lock if unlocked
@@ -99,7 +99,7 @@ else if (builtin.os == .windows)
9999
// unlock without a rmw/cmpxchg instruction
100100
@atomicStore(u8, @ptrCast(*u8, &self.mutex.locked), 0, .Release);
101101

102-
while (true) : (SpinLock.yield(1)) {
102+
while (true) : (SpinLock.loopHint(1)) {
103103
const waiters = @atomicLoad(u32, &self.mutex.waiters, .Monotonic);
104104

105105
// no one is waiting
@@ -142,10 +142,6 @@ else if (builtin.link_libc or builtin.os == .linux)
142142
self.* = undefined;
143143
}
144144

145-
fn yield() void {
146-
os.sched_yield() catch SpinLock.yield(30);
147-
}
148-
149145
pub fn tryAcquire(self: *Mutex) ?Held {
150146
if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)
151147
return null;
@@ -175,7 +171,7 @@ else if (builtin.link_libc or builtin.os == .linux)
175171
} else if (state & QUEUE_MASK == 0) {
176172
break;
177173
}
178-
yield();
174+
SpinLock.yield();
179175
state = @atomicLoad(usize, &self.state, .Monotonic);
180176
}
181177

@@ -198,7 +194,7 @@ else if (builtin.link_libc or builtin.os == .linux)
198194
break;
199195
};
200196
}
201-
yield();
197+
SpinLock.yield();
202198
state = @atomicLoad(usize, &self.state, .Monotonic);
203199
}
204200
}
@@ -225,7 +221,7 @@ else if (builtin.link_libc or builtin.os == .linux)
225221
// try and lock the LFIO queue to pop a node off,
226222
// stopping altogether if its already locked or the queue is empty
227223
var state = @atomicLoad(usize, &self.state, .Monotonic);
228-
while (true) : (std.SpinLock.yield(1)) {
224+
while (true) : (SpinLock.loopHint(1)) {
229225
if (state & QUEUE_LOCK != 0 or state & QUEUE_MASK == 0)
230226
return;
231227
state = @cmpxchgWeak(usize, &self.state, state, state | QUEUE_LOCK, .Acquire, .Monotonic) orelse break;
@@ -234,7 +230,7 @@ else if (builtin.link_libc or builtin.os == .linux)
234230
// acquired the QUEUE_LOCK, try and pop a node to wake it.
235231
// if the mutex is locked, then unset QUEUE_LOCK and let
236232
// the thread who holds the mutex do the wake-up on unlock()
237-
while (true) : (std.SpinLock.yield(1)) {
233+
while (true) : (SpinLock.loopHint(1)) {
238234
if ((state & MUTEX_LOCK) != 0) {
239235
state = @cmpxchgWeak(usize, &self.state, state, state & ~QUEUE_LOCK, .Release, .Acquire) orelse return;
240236
} else {

lib/std/reset_event.zig

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,7 @@ const AtomicEvent = struct {
234234
timer = time.Timer.start() catch unreachable;
235235

236236
while (@atomicLoad(i32, ptr, .Acquire) == expected) {
237-
switch (builtin.os) {
238-
.windows => SpinLock.yield(400),
239-
else => os.sched_yield() catch SpinLock.yield(1),
240-
}
237+
SpinLock.yield();
241238
if (timeout) |timeout_ns| {
242239
if (timer.read() >= timeout_ns)
243240
return error.TimedOut;
@@ -320,7 +317,7 @@ const AtomicEvent = struct {
320317
return @intToPtr(?windows.HANDLE, handle);
321318
},
322319
LOADING => {
323-
SpinLock.yield(1000);
320+
SpinLock.yield();
324321
handle = @atomicLoad(usize, &event_handle, .Monotonic);
325322
},
326323
else => {

lib/std/spinlock.zig

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,33 @@ pub const SpinLock = struct {
3535
pub fn acquire(self: *SpinLock) Held {
3636
while (true) {
3737
return self.tryAcquire() orelse {
38-
// On native windows, SwitchToThread is too expensive,
39-
// and yielding for 380-410 iterations was found to be
40-
// a nice sweet spot. Posix systems on the other hand,
41-
// especially linux, perform better by yielding the thread.
42-
switch (builtin.os) {
43-
.windows => yield(400),
44-
else => std.os.sched_yield() catch yield(1),
45-
}
38+
yield();
4639
continue;
4740
};
4841
}
4942
}
5043

44+
pub fn yield() void {
45+
// On native windows, SwitchToThread is too expensive,
46+
// and yielding for 380-410 iterations was found to be
47+
// a nice sweet spot. Posix systems on the other hand,
48+
// especially linux, perform better by yielding the thread.
49+
switch (builtin.os) {
50+
.windows => loopHint(400),
51+
else => std.os.sched_yield() catch loopHint(1),
52+
}
53+
}
54+
5155
/// Hint to the cpu that execution is spinning
5256
/// for the given amount of iterations.
53-
pub fn yield(iterations: usize) void {
57+
pub fn loopHint(iterations: usize) void {
5458
var i = iterations;
5559
while (i != 0) : (i -= 1) {
5660
switch (builtin.arch) {
57-
.i386, .x86_64 => asm volatile ("pause"),
58-
.arm, .aarch64 => asm volatile ("yield"),
61+
// these instructions use a memory clobber as they
62+
// flush the pipeline of any speculated reads/writes.
63+
.i386, .x86_64 => asm volatile ("pause" ::: "memory"),
64+
.arm, .aarch64 => asm volatile ("yield" ::: "memory"),
5965
else => std.os.sched_yield() catch {},
6066
}
6167
}

0 commit comments

Comments
 (0)