authorgravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2019-12-15 19:37:52-06:00
committergravatar for kbutcher6200@gmail.comkprotty <kbutcher6200@gmail.com> 2019-12-17 15:38:00-06:00
log947db78622f4691ad49873b5e3e3a6fbdc9ee0e7
tree086a00d722887292feb9f7896e0a4fe0731a7141
parentd8499f7abe43ec641027eb7f94b41906c9bf5cca

Spinlock: remove Backoff & improve yielding


1 files changed, 37 insertions(+), 35 deletions(-)

lib/std/spinlock.zig+37-35
......@@ -1,69 +1,71 @@
11const std = @import("std.zig");
22const builtin = @import("builtin");
3const assert = std.debug.assert;
4const time = std.time;
5const os = std.os;
63
74pub 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 };
911
1012 pub const Held = struct {
1113 spinlock: *SpinLock,
1214
1315 pub fn release(self: Held) void {
14 @atomicStore(u8, &self.spinlock.lock, 0, .Release);
16 @atomicStore(State, &self.spinlock.state, .Unlocked, .Release);
1517 }
1618 };
1719
1820 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 };
2033 }
2134
2235 pub fn acquire(self: *SpinLock) Held {
23 var backoff = Backoff.init();
24 while (@atomicRmw(u8, &self.lock, .Xchg, 1, .Acquire) != 0)
25 backoff.yield();
26 return Held{ .spinlock = self };
36 while (true) {
37 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 }
46 continue;
47 };
48 }
2749 }
2850
51 /// Hint to the cpu that execution is spinning
52 /// for the given amount of iterations.
2953 pub fn yield(iterations: usize) void {
3054 var i = iterations;
3155 while (i != 0) : (i -= 1) {
3256 switch (builtin.arch) {
3357 .i386, .x86_64 => asm volatile ("pause"),
3458 .arm, .aarch64 => asm volatile ("yield"),
35 else => time.sleep(0),
59 else => std.os.sched_yield() catch {},
3660 }
3761 }
3862 }
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 };
6363};
6464
6565test "spinlock" {
6666 var lock = SpinLock.init();
67 defer lock.deinit();
68
6769 const held = lock.acquire();
6870 defer held.release();
6971}