| ... | @@ -1,69 +1,71 @@ | ... | @@ -1,69 +1,71 @@ |
| 1 | const std = @import("std.zig"); | 1 | const std = @import("std.zig"); |
| 2 | const builtin = @import("builtin"); | 2 | const builtin = @import("builtin"); |
| 3 | const assert = std.debug.assert; | | |
| 4 | const time = std.time; | | |
| 5 | const os = std.os; | | |
| 6 | | 3 | |
| 7 | pub const SpinLock = struct { | 4 | pub const SpinLock = struct { |
| 8 | lock: u8, // TODO use a bool or enum | 5 | state: State, |
| | 6 | |
| | 7 | const State = enum(u8) { |
| | 8 | Unlocked, |
| | 9 | Locked, |
| | 10 | }; |
| 9 | | 11 | |
| 10 | pub const Held = struct { | 12 | pub const Held = struct { |
| 11 | spinlock: *SpinLock, | 13 | spinlock: *SpinLock, |
| 12 | | 14 | |
| 13 | pub fn release(self: Held) void { | 15 | pub fn release(self: Held) void { |
| 14 | @atomicStore(u8, &self.spinlock.lock, 0, .Release); | 16 | @atomicStore(State, &self.spinlock.state, .Unlocked, .Release); |
| 15 | } | 17 | } |
| 16 | }; | 18 | }; |
| 17 | | 19 | |
| 18 | pub fn init() SpinLock { | 20 | pub fn init() SpinLock { |
| 19 | return SpinLock{ .lock = 0 }; | 21 | return SpinLock{ .state = .Unlocked }; |
| | 22 | } |
| | 23 | |
| | 24 | pub fn deinit(self: *SpinLock) void { |
| | 25 | self.* = undefined; |
| | 26 | } |
| | 27 | |
| | 28 | pub fn tryAcquire(self: *SpinLock) ?Held { |
| | 29 | return switch (@atomicRmw(State, &self.state, .Xchg, .Locked, .Acquire)) { |
| | 30 | .Unlocked => Held{ .spinlock = self }, |
| | 31 | .Locked => null, |
| | 32 | }; |
| 20 | } | 33 | } |
| 21 | | 34 | |
| 22 | pub fn acquire(self: *SpinLock) Held { | 35 | pub fn acquire(self: *SpinLock) Held { |
| 23 | var backoff = Backoff.init(); | 36 | while (true) { |
| 24 | while (@atomicRmw(u8, &self.lock, .Xchg, 1, .Acquire) != 0) | 37 | return self.tryAcquire() orelse { |
| 25 | backoff.yield(); | 38 | // On native windows, SwitchToThread is too expensive, |
| 26 | return Held{ .spinlock = self }; | 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 | } |
| | 46 | continue; |
| | 47 | }; |
| | 48 | } |
| 27 | } | 49 | } |
| 28 | | 50 | |
| | 51 | /// Hint to the cpu that execution is spinning |
| | 52 | /// for the given amount of iterations. |
| 29 | pub fn yield(iterations: usize) void { | 53 | pub fn yield(iterations: usize) void { |
| 30 | var i = iterations; | 54 | var i = iterations; |
| 31 | while (i != 0) : (i -= 1) { | 55 | while (i != 0) : (i -= 1) { |
| 32 | switch (builtin.arch) { | 56 | switch (builtin.arch) { |
| 33 | .i386, .x86_64 => asm volatile ("pause"), | 57 | .i386, .x86_64 => asm volatile ("pause"), |
| 34 | .arm, .aarch64 => asm volatile ("yield"), | 58 | .arm, .aarch64 => asm volatile ("yield"), |
| 35 | else => time.sleep(0), | 59 | else => std.os.sched_yield() catch {}, |
| 36 | } | 60 | } |
| 37 | } | 61 | } |
| 38 | } | 62 | } |
| 39 | | | |
| 40 | /// Provides a method to incrementally yield longer each time its called. | | |
| 41 | pub const Backoff = struct { | | |
| 42 | iteration: usize, | | |
| 43 | | | |
| 44 | pub fn init() @This() { | | |
| 45 | return @This(){ .iteration = 0 }; | | |
| 46 | } | | |
| 47 | | | |
| 48 | /// Modified hybrid yielding from | | |
| 49 | /// http://www.1024cores.net/home/lock-free-algorithms/tricks/spinning | | |
| 50 | pub fn yield(self: *@This()) void { | | |
| 51 | defer self.iteration +%= 1; | | |
| 52 | if (self.iteration < 20) { | | |
| 53 | SpinLock.yield(self.iteration); | | |
| 54 | } else if (self.iteration < 24) { | | |
| 55 | os.sched_yield() catch time.sleep(1); | | |
| 56 | } else if (self.iteration < 26) { | | |
| 57 | time.sleep(1 * time.millisecond); | | |
| 58 | } else { | | |
| 59 | time.sleep(10 * time.millisecond); | | |
| 60 | } | | |
| 61 | } | | |
| 62 | }; | | |
| 63 | }; | 63 | }; |
| 64 | | 64 | |
| 65 | test "spinlock" { | 65 | test "spinlock" { |
| 66 | var lock = SpinLock.init(); | 66 | var lock = SpinLock.init(); |
| | 67 | defer lock.deinit(); |
| | 68 | |
| 67 | const held = lock.acquire(); | 69 | const held = lock.acquire(); |
| 68 | defer held.release(); | 70 | defer held.release(); |
| 69 | } | 71 | } |