Spinlock: remove Backoff & improve yielding

This commit is contained in:
kprotty 2019-12-15 19:37:52 -06:00
parent d8499f7abe
commit 947db78622

View File

@ -1,69 +1,71 @@
const std = @import("std.zig");
const builtin = @import("builtin");
const assert = std.debug.assert;
const time = std.time;
const os = std.os;
pub const SpinLock = struct {
lock: u8, // TODO use a bool or enum
state: State,
const State = enum(u8) {
Unlocked,
Locked,
};
pub const Held = struct {
spinlock: *SpinLock,
pub fn release(self: Held) void {
@atomicStore(u8, &self.spinlock.lock, 0, .Release);
@atomicStore(State, &self.spinlock.state, .Unlocked, .Release);
}
};
pub fn init() SpinLock {
return SpinLock{ .lock = 0 };
return SpinLock{ .state = .Unlocked };
}
pub fn deinit(self: *SpinLock) void {
self.* = undefined;
}
pub fn tryAcquire(self: *SpinLock) ?Held {
return switch (@atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire)) {
.Unlocked => Held{ .spinlock = self },
.Locked => null,
};
}
pub fn acquire(self: *SpinLock) Held {
var backoff = Backoff.init();
while (@atomicRmw(u8, &self.lock, .Xchg, 1, .Acquire) != 0)
backoff.yield();
return Held{ .spinlock = self };
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),
}
continue;
};
}
}
/// Hint to the cpu that execution is spinning
/// for the given amount of iterations.
pub fn yield(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"),
else => time.sleep(0),
else => std.os.sched_yield() catch {},
}
}
}
/// Provides a method to incrementally yield longer each time its called.
pub const Backoff = struct {
iteration: usize,
pub fn init() @This() {
return @This(){ .iteration = 0 };
}
/// Modified hybrid yielding from
/// http://www.1024cores.net/home/lock-free-algorithms/tricks/spinning
pub fn yield(self: *@This()) void {
defer self.iteration +%= 1;
if (self.iteration < 20) {
SpinLock.yield(self.iteration);
} else if (self.iteration < 24) {
os.sched_yield() catch time.sleep(1);
} else if (self.iteration < 26) {
time.sleep(1 * time.millisecond);
} else {
time.sleep(10 * time.millisecond);
}
}
};
};
test "spinlock" {
var lock = SpinLock.init();
defer lock.deinit();
const held = lock.acquire();
defer held.release();
}