From b70f62a64a455ab0ef6f1df02ef14ae52388950b Mon Sep 17 00:00:00 2001 From: Julien Portalier Date: Wed, 8 Jan 2025 15:04:59 +0100 Subject: [PATCH] Use Crystal::PointerLinkedList instead of Deque in Mutex The flat array also doesn't have much impact since it's never traversed: we only reach the head or the tail once (to dequeue or dequeue one). This spares a number of GC allocations since the Deque needs to be allocated and also a buffer that will have to be reallocated sometimes (and will only ever grow). --- src/fiber/waiting.cr | 13 +++++++++++++ src/mutex.cr | 20 ++++++++++++-------- src/wait_group.cr | 16 +++------------- 3 files changed, 28 insertions(+), 21 deletions(-) create mode 100644 src/fiber/waiting.cr diff --git a/src/fiber/waiting.cr b/src/fiber/waiting.cr new file mode 100644 index 000000000000..8c4d0138a9f1 --- /dev/null +++ b/src/fiber/waiting.cr @@ -0,0 +1,13 @@ +class Fiber + # :nodoc: + struct Waiting + include Crystal::PointerLinkedList::Node + + def initialize(@fiber : Fiber) + end + + def enqueue : Nil + @fiber.enqueue + end + end +end diff --git a/src/mutex.cr b/src/mutex.cr index 780eac468201..ad9bc1ac2ca5 100644 --- a/src/mutex.cr +++ b/src/mutex.cr @@ -1,3 +1,4 @@ +require "fiber/waiting" require "crystal/spin_lock" # A fiber-safe mutex. @@ -22,9 +23,9 @@ class Mutex @state = Atomic(Int32).new(UNLOCKED) @mutex_fiber : Fiber? @lock_count = 0 - @queue = Deque(Fiber).new + @queue = Crystal::PointerLinkedList(Fiber::Waiting).new @queue_count = Atomic(Int32).new(0) - @lock = Crystal::SpinLock.new + @spin = Crystal::SpinLock.new enum Protection Checked @@ -59,7 +60,9 @@ class Mutex loop do break if try_lock - @lock.sync do + waiting = Fiber::Waiting.new(Fiber.current) + + @spin.sync do @queue_count.add(1) if @state.get(:relaxed) == UNLOCKED @@ -71,7 +74,7 @@ class Mutex end end - @queue.push Fiber.current + @queue.push pointerof(waiting) end Fiber.suspend @@ -116,17 +119,18 @@ class Mutex return end - fiber = nil - @lock.sync do + waiting = nil + @spin.sync do if @queue_count.get == 0 return end - if fiber = @queue.shift? + if waiting = @queue.shift? @queue_count.add(-1) end end - fiber.enqueue if fiber + + waiting.try(&.value.enqueue) end def synchronize(&) diff --git a/src/wait_group.cr b/src/wait_group.cr index cf1ca8900e8f..efa19f15e1b4 100644 --- a/src/wait_group.cr +++ b/src/wait_group.cr @@ -1,4 +1,5 @@ require "fiber" +require "fiber/waiting" require "crystal/spin_lock" require "crystal/pointer_linked_list" @@ -31,17 +32,6 @@ require "crystal/pointer_linked_list" # wg.wait # ``` class WaitGroup - private struct Waiting - include Crystal::PointerLinkedList::Node - - def initialize(@fiber : Fiber) - end - - def enqueue : Nil - @fiber.enqueue - end - end - # Yields a `WaitGroup` instance and waits at the end of the block for all of # the work enqueued inside it to complete. # @@ -59,7 +49,7 @@ class WaitGroup end def initialize(n : Int32 = 0) - @waiting = Crystal::PointerLinkedList(Waiting).new + @waiting = Crystal::PointerLinkedList(Fiber::Waiting).new @lock = Crystal::SpinLock.new @counter = Atomic(Int32).new(n) end @@ -128,7 +118,7 @@ class WaitGroup def wait : Nil return if done? - waiting = Waiting.new(Fiber.current) + waiting = Fiber::Waiting.new(Fiber.current) @lock.sync do # must check again to avoid a race condition where #done may have