SpinLock: loopHint & yield distinction

This commit is contained in:
kprotty 2019-12-17 08:57:07 -06:00
parent 26e08d5701
commit c912296443
3 changed files with 25 additions and 26 deletions

View File

@ -75,7 +75,7 @@ else if (builtin.os == .windows)
fn acquireSlow(self: *Mutex) Held {
@setCold(true);
while (true) : (SpinLock.yield(1)) {
while (true) : (SpinLock.loopHint(1)) {
const waiters = @atomicLoad(u32, &self.waiters, .Monotonic);
// try and take lock if unlocked
@ -99,7 +99,7 @@ else if (builtin.os == .windows)
// unlock without a rmw/cmpxchg instruction
@atomicStore(u8, @ptrCast(*u8, &self.mutex.locked), 0, .Release);
while (true) : (SpinLock.yield(1)) {
while (true) : (SpinLock.loopHint(1)) {
const waiters = @atomicLoad(u32, &self.mutex.waiters, .Monotonic);
// no one is waiting
@ -142,10 +142,6 @@ else if (builtin.link_libc or builtin.os == .linux)
self.* = undefined;
}
fn yield() void {
os.sched_yield() catch SpinLock.yield(30);
}
pub fn tryAcquire(self: *Mutex) ?Held {
if (@cmpxchgWeak(usize, &self.state, 0, MUTEX_LOCK, .Acquire, .Monotonic) != null)
return null;
@ -175,7 +171,7 @@ else if (builtin.link_libc or builtin.os == .linux)
} else if (state & QUEUE_MASK == 0) {
break;
}
yield();
SpinLock.yield();
state = @atomicLoad(usize, &self.state, .Monotonic);
}
@ -198,7 +194,7 @@ else if (builtin.link_libc or builtin.os == .linux)
break;
};
}
yield();
SpinLock.yield();
state = @atomicLoad(usize, &self.state, .Monotonic);
}
}
@ -225,7 +221,7 @@ else if (builtin.link_libc or builtin.os == .linux)
// try and lock the LFIO queue to pop a node off,
// stopping altogether if its already locked or the queue is empty
var state = @atomicLoad(usize, &self.state, .Monotonic);
while (true) : (std.SpinLock.yield(1)) {
while (true) : (SpinLock.loopHint(1)) {
if (state & QUEUE_LOCK != 0 or state & QUEUE_MASK == 0)
return;
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)
// acquired the QUEUE_LOCK, try and pop a node to wake it.
// if the mutex is locked, then unset QUEUE_LOCK and let
// the thread who holds the mutex do the wake-up on unlock()
while (true) : (std.SpinLock.yield(1)) {
while (true) : (SpinLock.loopHint(1)) {
if ((state & MUTEX_LOCK) != 0) {
state = @cmpxchgWeak(usize, &self.state, state, state & ~QUEUE_LOCK, .Release, .Acquire) orelse return;
} else {

View File

@ -234,10 +234,7 @@ const AtomicEvent = struct {
timer = time.Timer.start() catch unreachable;
while (@atomicLoad(i32, ptr, .Acquire) == expected) {
switch (builtin.os) {
.windows => SpinLock.yield(400),
else => os.sched_yield() catch SpinLock.yield(1),
}
SpinLock.yield();
if (timeout) |timeout_ns| {
if (timer.read() >= timeout_ns)
return error.TimedOut;
@ -320,7 +317,7 @@ const AtomicEvent = struct {
return @intToPtr(?windows.HANDLE, handle);
},
LOADING => {
SpinLock.yield(1000);
SpinLock.yield();
handle = @atomicLoad(usize, &event_handle, .Monotonic);
},
else => {

View File

@ -35,27 +35,33 @@ pub const SpinLock = struct {
pub fn acquire(self: *SpinLock) Held {
while (true) {
return self.tryAcquire() orelse {
// On native windows, SwitchToThread is too expensive,
// and yielding for 380-410 iterations was found to be
// a nice sweet spot. Posix systems on the other hand,
// especially linux, perform better by yielding the thread.
switch (builtin.os) {
.windows => yield(400),
else => std.os.sched_yield() catch yield(1),
}
yield();
continue;
};
}
}
pub fn yield() void {
// On native windows, SwitchToThread is too expensive,
// and yielding for 380-410 iterations was found to be
// a nice sweet spot. Posix systems on the other hand,
// especially linux, perform better by yielding the thread.
switch (builtin.os) {
.windows => loopHint(400),
else => std.os.sched_yield() catch loopHint(1),
}
}
/// Hint to the cpu that execution is spinning
/// for the given amount of iterations.
pub fn yield(iterations: usize) void {
pub fn loopHint(iterations: usize) void {
var i = iterations;
while (i != 0) : (i -= 1) {
switch (builtin.arch) {
.i386, .x86_64 => asm volatile ("pause"),
.arm, .aarch64 => asm volatile ("yield"),
// these instructions use a memory clobber as they
// flush the pipeline of any speculated reads/writes.
.i386, .x86_64 => asm volatile ("pause" ::: "memory"),
.arm, .aarch64 => asm volatile ("yield" ::: "memory"),
else => std.os.sched_yield() catch {},
}
}