Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EventLoop: store Timers in min Pairing Heap #15206

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 42 additions & 14 deletions spec/std/crystal/evented/arena_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ require "spec"
describe Crystal::Evented::Arena do
describe "#allocate_at?" do
it "yields block when not allocated" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
pointer = nil
index = nil
called = 0
Expand All @@ -31,7 +31,7 @@ describe Crystal::Evented::Arena do
end

it "allocates up to capacity" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
indexes = [] of Crystal::Evented::Arena::Index

indexes = 32.times.map do |i|
Expand All @@ -49,15 +49,15 @@ describe Crystal::Evented::Arena do
end

it "checks bounds" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
expect_raises(IndexError) { arena.allocate_at?(-1) { } }
expect_raises(IndexError) { arena.allocate_at?(33) { } }
end
end

describe "#get" do
it "returns previously allocated object" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
pointer = nil

index = arena.allocate_at(30) do |ptr|
Expand All @@ -77,15 +77,15 @@ describe Crystal::Evented::Arena do
end

it "can't access unallocated object" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)

expect_raises(RuntimeError) do
arena.get(Crystal::Evented::Arena::Index.new(10, 0)) { }
end
end

it "checks generation" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
called = 0

index1 = arena.allocate_at(2) { called += 1 }
Expand All @@ -102,15 +102,15 @@ describe Crystal::Evented::Arena do
end

it "checks out of bounds" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(-1, 0)) { } }
expect_raises(IndexError) { arena.get(Crystal::Evented::Arena::Index.new(33, 0)) { } }
end
end

describe "#get?" do
it "returns previously allocated object" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
pointer = nil

index = arena.allocate_at(30) do |ptr|
Expand All @@ -131,7 +131,7 @@ describe Crystal::Evented::Arena do
end

it "can't access unallocated index" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)

called = 0
ret = arena.get?(Crystal::Evented::Arena::Index.new(10, 0)) { called += 1 }
Expand All @@ -140,7 +140,7 @@ describe Crystal::Evented::Arena do
end

it "checks generation" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
called = 0

old_index = arena.allocate_at(2) { }
Expand All @@ -166,7 +166,7 @@ describe Crystal::Evented::Arena do
end

it "checks out of bounds" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
called = 0

arena.get?(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 }.should be_false
Expand All @@ -178,7 +178,7 @@ describe Crystal::Evented::Arena do

describe "#free" do
it "deallocates the object" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)

index1 = arena.allocate_at(3) { |ptr| ptr.value = 123 }
arena.free(index1) { }
Expand All @@ -192,7 +192,7 @@ describe Crystal::Evented::Arena do
end

it "checks generation" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)

called = 0
old_index = arena.allocate_at(1) { }
Expand All @@ -214,7 +214,7 @@ describe Crystal::Evented::Arena do
end

it "checks out of bounds" do
arena = Crystal::Evented::Arena(Int32).new(32)
arena = Crystal::Evented::Arena(Int32, 96).new(32)
called = 0

arena.free(Crystal::Evented::Arena::Index.new(-1, 0)) { called += 1 }
Expand All @@ -223,4 +223,32 @@ describe Crystal::Evented::Arena do
called.should eq(0)
end
end

it "#each_index" do
arena = Crystal::Evented::Arena(Int32, 96).new(32)
indices = [] of {Int32, Crystal::Evented::Arena::Index}

arena.each_index { |i, index| indices << {i, index} }
indices.should be_empty

index5 = arena.allocate_at(5) { }

arena.each_index { |i, index| indices << {i, index} }
indices.should eq([{5, index5}])

index3 = arena.allocate_at(3) { }
index11 = arena.allocate_at(11) { }
index10 = arena.allocate_at(10) { }
index30 = arena.allocate_at(30) { }

indices.clear
arena.each_index { |i, index| indices << {i, index} }
indices.should eq([
{3, index3},
{5, index5},
{10, index10},
{11, index11},
{30, index30},
])
end
end
18 changes: 10 additions & 8 deletions spec/std/crystal/evented/timers_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,16 @@ describe Crystal::Evented::Timers do
event0.wake_at = -1.minute
timers.add(pointerof(event0)).should be_true # added new head (next ready)

events = [] of Crystal::Evented::Event*
timers.each { |event| events << event }
events.should eq([
pointerof(event0),
pointerof(event1),
pointerof(event3),
pointerof(event2),
])
# TODO: forcefully dequeue the next ready event, then check the order

# events = [] of Crystal::Evented::Event*
# timers.each { |event| events << event }
# events.should eq([
# pointerof(event0),
# pointerof(event1),
# pointerof(event3),
# pointerof(event2),
# ])
timers.empty?.should be_false
end

Expand Down
137 changes: 137 additions & 0 deletions spec/std/crystal/pairing_heap_spec.cr
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
require "spec"
require "../../../src/crystal/pointer_pairing_heap"

private struct Node
getter key : Int32

include Crystal::PointerPairingHeap::Node

def initialize(@key : Int32)
end

def heap_compare(other : Pointer(self)) : Bool
key < other.value.key
end
end

describe Crystal::PointerPairingHeap do
it "#add" do
heap = Crystal::PointerPairingHeap(Node).new
node1 = Node.new(1)
node2 = Node.new(2)
node2b = Node.new(2)
node3 = Node.new(3)

# can add distinct nodes
heap.add(pointerof(node3))
heap.add(pointerof(node1))
heap.add(pointerof(node2))

# can add duplicate key (different nodes)
heap.add(pointerof(node2b))

# can't add same node twice
expect_raises(ArgumentError) { heap.add(pointerof(node1)) }

# can re-add removed nodes
heap.delete(pointerof(node3))
heap.add(pointerof(node3))

heap.shift?.should eq(pointerof(node1))
heap.add(pointerof(node1))
end

it "#shift?" do
heap = Crystal::PointerPairingHeap(Node).new
nodes = StaticArray(Node, 10).new { |i| Node.new(i) }

# insert in random order
(0..9).to_a.shuffle.each do |i|
heap.add nodes.to_unsafe + i
end

# removes in ascending order
10.times do |i|
heap.shift?.should eq(nodes.to_unsafe + i)
end
end

it "#delete" do
heap = Crystal::PointerPairingHeap(Node).new
nodes = StaticArray(Node, 10).new { |i| Node.new(i) }

# noop: empty
heap.delete(nodes.to_unsafe + 0).should eq({false, false})

# insert in random order
(0..9).to_a.shuffle.each do |i|
heap.add nodes.to_unsafe + i
end

# noop: unknown node
node11 = Node.new(11)
heap.delete(pointerof(node11)).should eq({false, false})

# remove some values
heap.delete(nodes.to_unsafe + 3).should eq({true, false})
heap.delete(nodes.to_unsafe + 7).should eq({true, false})
heap.delete(nodes.to_unsafe + 1).should eq({true, false})

# remove tail
heap.delete(nodes.to_unsafe + 9).should eq({true, false})

# remove head
heap.delete(nodes.to_unsafe + 0).should eq({true, true})

# repeatedly delete min
[2, 4, 5, 6, 8].each do |i|
heap.shift?.should eq(nodes.to_unsafe + i)
end
heap.shift?.should be_nil
end

it "adds 1000 nodes then shifts them in order" do
heap = Crystal::PointerPairingHeap(Node).new

nodes = StaticArray(Node, 1000).new { |i| Node.new(i) }
(0..999).to_a.shuffle.each { |i| heap.add(nodes.to_unsafe + i) }

i = 0
while node = heap.shift?
node.value.key.should eq(i)
i += 1
end
i.should eq(1000)

heap.shift?.should be_nil
end

it "randomly adds while we shift nodes" do
heap = Crystal::PointerPairingHeap(Node).new

nodes = uninitialized StaticArray(Node, 1000)
(0..999).to_a.shuffle.each_with_index { |i, j| nodes[j] = Node.new(i) }

i = 0
removed = 0

# regularly calls delete-min while we insert
loop do
if rand(0..5) == 0
removed += 1 if heap.shift?
else
heap.add(nodes.to_unsafe + i)
break if (i += 1) == 1000
end
end

# exhaust the heap
while heap.shift?
removed += 1
end

# we must have added and removed all nodes _once_
i.should eq(1000)
removed.should eq(1000)
end
end
Loading
Loading