Skip to content

Commit 4a63e07

Browse files
committed
Apply suggestions by Linus Sellberg
Simpler PointerPairingHeap: let the Timers object deal with knowing whether we added to HEAD or actually deleted a timer, etc. Fix potential stack-overflow when merging pairs after inserting _lots_ of timers (over 1 million fills a 8KB thread stack).
1 parent de224a7 commit 4a63e07

File tree

3 files changed

+79
-60
lines changed

3 files changed

+79
-60
lines changed

spec/std/crystal/pairing_heap_spec.cr renamed to spec/std/crystal/pointer_pairing_heap_spec.cr

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,25 @@ private struct Node
1212
def heap_compare(other : Pointer(self)) : Bool
1313
key < other.value.key
1414
end
15+
16+
def inspect(io : IO, indent = 0) : Nil
17+
prv = @heap_previous
18+
nxt = @heap_next
19+
chd = @heap_child
20+
21+
indent.times { io << ' ' }
22+
io << "Node value=" << key
23+
io << " prv=" << prv.try(&.value.key)
24+
io << " nxt=" << nxt.try(&.value.key)
25+
io << " chd=" << chd.try(&.value.key)
26+
io.puts
27+
28+
node = heap_child?
29+
while node
30+
node.value.inspect(io, indent + 2)
31+
node = node.value.heap_next?
32+
end
33+
end
1534
end
1635

1736
describe Crystal::PointerPairingHeap do
@@ -52,36 +71,30 @@ describe Crystal::PointerPairingHeap do
5271

5372
# removes in ascending order
5473
10.times do |i|
55-
heap.shift?.should eq(nodes.to_unsafe + i)
74+
node = heap.shift?
75+
node.should eq(nodes.to_unsafe + i)
5676
end
5777
end
5878

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

63-
# noop: empty
64-
heap.delete(nodes.to_unsafe + 0).should eq({false, false})
65-
6683
# insert in random order
6784
(0..9).to_a.shuffle.each do |i|
6885
heap.add nodes.to_unsafe + i
6986
end
7087

71-
# noop: unknown node
72-
node11 = Node.new(11)
73-
heap.delete(pointerof(node11)).should eq({false, false})
74-
7588
# remove some values
76-
heap.delete(nodes.to_unsafe + 3).should eq({true, false})
77-
heap.delete(nodes.to_unsafe + 7).should eq({true, false})
78-
heap.delete(nodes.to_unsafe + 1).should eq({true, false})
89+
heap.delete(nodes.to_unsafe + 3)
90+
heap.delete(nodes.to_unsafe + 7)
91+
heap.delete(nodes.to_unsafe + 1)
7992

8093
# remove tail
81-
heap.delete(nodes.to_unsafe + 9).should eq({true, false})
94+
heap.delete(nodes.to_unsafe + 9)
8295

8396
# remove head
84-
heap.delete(nodes.to_unsafe + 0).should eq({true, true})
97+
heap.delete(nodes.to_unsafe + 0)
8598

8699
# repeatedly delete min
87100
[2, 4, 5, 6, 8].each do |i|
@@ -106,7 +119,7 @@ describe Crystal::PointerPairingHeap do
106119
heap.shift?.should be_nil
107120
end
108121

109-
it "randomly adds while we shift nodes" do
122+
it "randomly shift while we add nodes" do
110123
heap = Crystal::PointerPairingHeap(Node).new
111124

112125
nodes = uninitialized StaticArray(Node, 1000)

src/crystal/pointer_pairing_heap.cr

Lines changed: 42 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class Crystal::PointerPairingHeap(T)
1717
abstract def heap_compare(other : Pointer(self)) : Bool
1818
end
1919

20-
@head : T* | Nil
20+
@head : Pointer(T)?
2121

2222
private def head=(head)
2323
@head = head
@@ -29,44 +29,26 @@ class Crystal::PointerPairingHeap(T)
2929
@head.nil?
3030
end
3131

32-
def first? : T* | Nil
32+
def first? : Pointer(T)?
3333
@head
3434
end
3535

36-
def shift? : T* | Nil
36+
def shift? : Pointer(T)?
3737
if node = @head
3838
self.head = merge_pairs(node.value.heap_child?)
3939
node.value.heap_child = nil
4040
node
4141
end
4242
end
4343

44-
def add(node : T*) : Bool
44+
def add(node : Pointer(T)) : Nil
4545
if node.value.heap_previous? || node.value.heap_next? || node.value.heap_child?
4646
raise ArgumentError.new("The node is already in a Pairing Heap tree")
4747
end
4848
self.head = meld(@head, node)
49-
node == @head
5049
end
5150

52-
def delete(node : T*) : {Bool, Bool}
53-
if node == @head
54-
self.head = merge_pairs(node.value.heap_child?)
55-
node.value.heap_child = nil
56-
return {true, true}
57-
end
58-
59-
if remove?(node)
60-
subtree = merge_pairs(node.value.heap_child?)
61-
self.head = meld(@head, subtree)
62-
unlink(node)
63-
return {true, false}
64-
end
65-
66-
{false, false}
67-
end
68-
69-
private def remove?(node)
51+
def delete(node : Pointer(T)) : Nil
7052
if previous_node = node.value.heap_previous?
7153
next_sibling = node.value.heap_next?
7254

@@ -80,9 +62,13 @@ class Crystal::PointerPairingHeap(T)
8062
next_sibling.value.heap_previous = previous_node
8163
end
8264

83-
true
65+
subtree = merge_pairs(node.value.heap_child?)
66+
clear(node)
67+
self.head = meld(@head, subtree)
8468
else
85-
false
69+
# removing head
70+
self.head = merge_pairs(node.value.heap_child?)
71+
node.value.heap_child = nil
8672
end
8773
end
8874

@@ -99,29 +85,29 @@ class Crystal::PointerPairingHeap(T)
9985
clear_recursive(child)
10086
child = child.value.heap_next?
10187
end
102-
unlink(node)
88+
clear(node)
10389
end
10490

105-
private def meld(a : T*, b : T*) : T*
91+
private def meld(a : Pointer(T), b : Pointer(T)) : Pointer(T)
10692
if a.value.heap_compare(b)
10793
add_child(a, b)
10894
else
10995
add_child(b, a)
11096
end
11197
end
11298

113-
private def meld(a : T*, b : Nil) : T*
99+
private def meld(a : Pointer(T), b : Nil) : Pointer(T)
114100
a
115101
end
116102

117-
private def meld(a : Nil, b : T*) : T*
103+
private def meld(a : Nil, b : Pointer(T)) : Pointer(T)
118104
b
119105
end
120106

121107
private def meld(a : Nil, b : Nil) : Nil
122108
end
123109

124-
private def add_child(parent : T*, node : T*) : T*
110+
private def add_child(parent : Pointer(T), node : Pointer(T)) : Pointer(T)
125111
first_child = parent.value.heap_child?
126112
parent.value.heap_child = node
127113

@@ -132,28 +118,39 @@ class Crystal::PointerPairingHeap(T)
132118
parent
133119
end
134120

135-
# Twopass merge of the children of *node* into pairs of two.
136-
private def merge_pairs(a : T*) : T* | Nil
137-
a.value.heap_previous = nil
121+
private def merge_pairs(node : Pointer(T)?) : Pointer(T)?
122+
return unless node
138123

139-
if b = a.value.heap_next?
140-
a.value.heap_next = nil
141-
b.value.heap_previous = nil
142-
else
143-
return a
124+
# 1st pass: meld children into pairs (left to right)
125+
tail = nil
126+
127+
while a = node
128+
if b = a.value.heap_next?
129+
node = b.value.heap_next?
130+
root = meld(a, b)
131+
root.value.heap_previous = tail
132+
tail = root
133+
else
134+
a.value.heap_previous = tail
135+
tail = a
136+
break
137+
end
144138
end
145139

146-
rest = merge_pairs(b.value.heap_next?)
147-
b.value.heap_next = nil
140+
# 2nd pass: meld the pairs back into a single tree (right to left)
141+
root = nil
148142

149-
pair = meld(a, b)
150-
meld(pair, rest)
151-
end
143+
while tail
144+
node = tail.value.heap_previous?
145+
root = meld(root, tail)
146+
tail = node
147+
end
152148

153-
private def merge_pairs(node : Nil) : Nil
149+
root.value.heap_next = nil if root
150+
root
154151
end
155152

156-
private def unlink(node) : Nil
153+
private def clear(node) : Nil
157154
node.value.heap_previous = nil
158155
node.value.heap_next = nil
159156
node.value.heap_child = nil

src/crystal/system/unix/evented/timers.cr

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,12 +38,21 @@ struct Crystal::Evented::Timers
3838
# Add a new timer into the list. Returns true if it is the next ready timer.
3939
def add(event : Evented::Event*) : Bool
4040
@heap.add(event)
41+
@heap.first? == event
4142
end
4243

4344
# Remove a timer from the list. Returns a tuple(dequeued, was_next_ready) of
4445
# booleans. The first bool tells whether the event was dequeued, in which case
4546
# the second one tells if it was the next ready event.
4647
def delete(event : Evented::Event*) : {Bool, Bool}
47-
@heap.delete(event)
48+
if @heap.first? == event
49+
@heap.shift?
50+
{true, true}
51+
elsif event.value.heap_previous?
52+
@heap.delete(event)
53+
{true, false}
54+
else
55+
{false, false}
56+
end
4857
end
4958
end

0 commit comments

Comments
 (0)