Skip to content

Commit 940c963

Browse files
AquaGeekTim VermeulenAmanuelEphremhassilaDante-Broggi
authored
Add a min-max heap implementation that can be used to back a priority queue (apple#61)
* Add PriorityQueue implementation built on top of a MinMaxHeap * Merge MinMaxHeap into PriorityQueue * Add `unordered` read-only view into underlying heap * Start filling in PriorityQueue benchmarks * Implement our own swapAt() storage.swapAt exhibits excessive allocations * Rename _delete(at:) to _remove(at:) * Rename removeMin/removeMax -> popMin/popMax * Use magic values 1, 2 to refer to the items in the first max level Co-authored-by: Tim Vermeulen <[email protected]> * Specify logarithmic complexities as "O(log `count`) / 2" instead of "O(log n)" Co-authored-by: Tim Vermeulen <[email protected]> * Clarify logic in popMax * Simplify logic in _remove(at:) * Move the bounds checking into the various index computation methods They will all now return `nil` for invalid indices. * Floyd's heap construction algorithm should start from count/2 - 1 * Add ObjC wrapper around CFBinaryHeap to benchmarks * Fix benchmarks that broke because of renames * Add removeMin/removeMax * Make _minMaxHeapIsMinLevel an instance method * Split _indexOfChildOrGrandchild(of:sortedUsing:) into two separate functions There is a large performance cost with passing a predicate function. * Defer comparing children when determining largest/smallest descendant If the given index has 4 grandchildren, we can skip comparing the children. * Added an iterator of the min and max views to the priority queue * seperated iterator implementation into a new file * Use renamed popMin/Max in Iterator instead of removeMin/Max * Fix code formatting Indentation of 2 spaces and 80 char column width * Add sequence initializer * Fix code formatting in benchmarks * Fix benchmark names * Remove init from Collection This should already be handled by the init from Sequence * Add conformance to ExpressibleByArrayLiteral * Move ExpressibleByArrayLiteral conformance to separate file * Update PriorityQueue's CMakeLists.txt * Make ascending and descending iterators public * Inline ALL THE THINGS! * Iterative instead of recursive implementation, @inline(__always) a couple more critical functions. * Address PR feedback, thanks! * Check invariants on insertion and deletion * Minor code formatting cleanup * Add copyright header to test file * Fix missing empty line * Add naïve implementation of insert(contentsOf:) * Add documentation on complexity of init<S:Sequence>(_:) * Cite source paper in documentation * Rename PriorityQueue -> MinMaxHeap We'll be reintroducing the PriorityQueue type as a wrapper. * Mark insert(contentsOf:) as inlinable This results in ~10x speedup in my initial tests. * Rename argument label "startingAt" -> "elementAt" These functions used to be recursive, so "startingAt" made sense. Now that they're iterative, we should fix the label. * Remove coefficients from complexity docs * Make `_minMaxHeapIsMinLevel` take an index instead of a count * Rename MinMaxHeap -> Heap * Add documentation for Heap * Make Heap.Iterator init and direction internal * Fix reference to queue in documentation Co-authored-by: Dante Broggi <[email protected]> * Don't wrap integers in CFBinaryHeap benchmark in NSNumber * Avoid heap allocation altogether * Add table with performance of operations * Add heap performance graph * Apply suggestions from code review Co-authored-by: Karoy Lorentey <[email protected]> * Make _checkInvariants comments a doc comment * Split Heap.Iterator into two separate views Adapted from Daryle Walker's (github.com/CTMacUser) suggestions * Prefix heap storage variable with an underscore * Update benchmark image * Fix Sources/PriorityQueueModule/CMakeLists.txt Heap+Iterator.swift was renamed to Heap+OrderedViews.swift Co-authored-by: Karoy Lorentey <[email protected]> * CFBinaryHeap is only available on Darwin * CFBinaryHeap is only available on Darwin * CFBinaryHeap is only available on Darwin Co-authored-by: Tim Vermeulen <[email protected]> Co-authored-by: Amanuel Ephem <[email protected]> Co-authored-by: Joakim Hassila <[email protected]> Co-authored-by: Dante Broggi <[email protected]> Co-authored-by: Karoy Lorentey <[email protected]>
1 parent 0959ba7 commit 940c963

File tree

22 files changed

+1721
-3
lines changed

22 files changed

+1721
-3
lines changed
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Scheme
3+
LastUpgradeVersion = "1250"
4+
version = "1.3">
5+
<BuildAction
6+
parallelizeBuildables = "YES"
7+
buildImplicitDependencies = "YES">
8+
<BuildActionEntries>
9+
<BuildActionEntry
10+
buildForTesting = "YES"
11+
buildForRunning = "YES"
12+
buildForProfiling = "YES"
13+
buildForArchiving = "YES"
14+
buildForAnalyzing = "YES">
15+
<BuildableReference
16+
BuildableIdentifier = "primary"
17+
BlueprintIdentifier = "PriorityQueueModule"
18+
BuildableName = "PriorityQueueModule"
19+
BlueprintName = "PriorityQueueModule"
20+
ReferencedContainer = "container:">
21+
</BuildableReference>
22+
</BuildActionEntry>
23+
</BuildActionEntries>
24+
</BuildAction>
25+
<TestAction
26+
buildConfiguration = "Debug"
27+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
28+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29+
shouldUseLaunchSchemeArgsEnv = "YES">
30+
<Testables>
31+
<TestableReference
32+
skipped = "NO">
33+
<BuildableReference
34+
BuildableIdentifier = "primary"
35+
BlueprintIdentifier = "PriorityQueueTests"
36+
BuildableName = "PriorityQueueTests"
37+
BlueprintName = "PriorityQueueTests"
38+
ReferencedContainer = "container:">
39+
</BuildableReference>
40+
</TestableReference>
41+
</Testables>
42+
</TestAction>
43+
<LaunchAction
44+
buildConfiguration = "Debug"
45+
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
46+
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
47+
launchStyle = "0"
48+
useCustomWorkingDirectory = "NO"
49+
ignoresPersistentStateOnLaunch = "NO"
50+
debugDocumentVersioning = "YES"
51+
debugServiceExtension = "internal"
52+
allowLocationSimulation = "YES">
53+
</LaunchAction>
54+
<ProfileAction
55+
buildConfiguration = "Release"
56+
shouldUseLaunchSchemeArgsEnv = "YES"
57+
savedToolIdentifier = ""
58+
useCustomWorkingDirectory = "NO"
59+
debugDocumentVersioning = "YES">
60+
<MacroExpansion>
61+
<BuildableReference
62+
BuildableIdentifier = "primary"
63+
BlueprintIdentifier = "PriorityQueueModule"
64+
BuildableName = "PriorityQueueModule"
65+
BlueprintName = "PriorityQueueModule"
66+
ReferencedContainer = "container:">
67+
</BuildableReference>
68+
</MacroExpansion>
69+
</ProfileAction>
70+
<AnalyzeAction
71+
buildConfiguration = "Debug">
72+
</AnalyzeAction>
73+
<ArchiveAction
74+
buildConfiguration = "Release"
75+
revealArchiveInOrganizer = "YES">
76+
</ArchiveAction>
77+
</Scheme>

.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-Package.xcscheme

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,20 @@
132132
ReferencedContainer = "container:">
133133
</BuildableReference>
134134
</BuildActionEntry>
135+
<BuildActionEntry
136+
buildForTesting = "YES"
137+
buildForRunning = "YES"
138+
buildForProfiling = "YES"
139+
buildForArchiving = "YES"
140+
buildForAnalyzing = "YES">
141+
<BuildableReference
142+
BuildableIdentifier = "primary"
143+
BlueprintIdentifier = "PriorityQueueModule"
144+
BuildableName = "PriorityQueueModule"
145+
BlueprintName = "PriorityQueueModule"
146+
ReferencedContainer = "container:">
147+
</BuildableReference>
148+
</BuildActionEntry>
135149
</BuildActionEntries>
136150
</BuildAction>
137151
<TestAction
@@ -170,6 +184,16 @@
170184
ReferencedContainer = "container:">
171185
</BuildableReference>
172186
</TestableReference>
187+
<TestableReference
188+
skipped = "NO">
189+
<BuildableReference
190+
BuildableIdentifier = "primary"
191+
BlueprintIdentifier = "PriorityQueueTests"
192+
BuildableName = "PriorityQueueTests"
193+
BlueprintName = "PriorityQueueTests"
194+
ReferencedContainer = "container:">
195+
</BuildableReference>
196+
</TestableReference>
173197
</Testables>
174198
</TestAction>
175199
<LaunchAction

.swiftpm/xcode/xcshareddata/xcschemes/swift-collections-benchmark.xcscheme

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,16 @@
4848
ReferencedContainer = "container:">
4949
</BuildableReference>
5050
</TestableReference>
51+
<TestableReference
52+
skipped = "NO">
53+
<BuildableReference
54+
BuildableIdentifier = "primary"
55+
BlueprintIdentifier = "PriorityQueueTests"
56+
BuildableName = "PriorityQueueTests"
57+
BlueprintName = "PriorityQueueTests"
58+
ReferencedContainer = "container:">
59+
</BuildableReference>
60+
</TestableReference>
5161
</Testables>
5262
</TestAction>
5363
<LaunchAction

Benchmarks/Benchmarks/Library.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,46 @@
764764
}
765765
]
766766
},
767+
{
768+
"kind": "group",
769+
"title": "PriorityQueue",
770+
"contents": [
771+
{
772+
"kind": "chart",
773+
"title": "operations",
774+
"tasks": [
775+
"Heap<Int> init from range",
776+
"Heap<Int> insert",
777+
"Heap<Int> insert(contentsOf:)",
778+
"Heap<Int> popMax",
779+
"Heap<Int> popMin"
780+
]
781+
},
782+
{
783+
"kind": "chart",
784+
"title": "initializers",
785+
"tasks": [
786+
"Heap<Int> init from range"
787+
]
788+
},
789+
{
790+
"kind": "chart",
791+
"title": "insert",
792+
"tasks": [
793+
"Heap<Int> insert",
794+
"Heap<Int> insert(contentsOf:)"
795+
]
796+
},
797+
{
798+
"kind": "chart",
799+
"title": "remove",
800+
"tasks": [
801+
"Heap<Int> popMax",
802+
"Heap<Int> popMin"
803+
]
804+
}
805+
]
806+
},
767807
{
768808
"kind": "group",
769809
"title": "Against other Swift collections",
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Collections open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
import CollectionsBenchmark
13+
import PriorityQueueModule
14+
import CppBenchmarks
15+
16+
extension Benchmark {
17+
public mutating func addHeapBenchmarks() {
18+
self.addSimple(
19+
title: "Heap<Int> init from range",
20+
input: Int.self
21+
) { size in
22+
blackHole(Heap(0..<size))
23+
}
24+
25+
self.addSimple(
26+
title: "Heap<Int> insert",
27+
input: [Int].self
28+
) { input in
29+
var queue = Heap<Int>()
30+
for i in input {
31+
queue.insert(i)
32+
}
33+
precondition(queue.count == input.count)
34+
blackHole(queue)
35+
}
36+
37+
self.add(
38+
title: "Heap<Int> insert(contentsOf:)",
39+
input: ([Int], [Int]).self
40+
) { (existing, new) in
41+
return { timer in
42+
var queue = Heap(existing)
43+
queue.insert(contentsOf: new)
44+
precondition(queue.count == existing.count + new.count)
45+
blackHole(queue)
46+
}
47+
}
48+
49+
self.add(
50+
title: "Heap<Int> popMax",
51+
input: [Int].self
52+
) { input in
53+
return { timer in
54+
var queue = Heap(input)
55+
timer.measure {
56+
while let max = queue.popMax() {
57+
blackHole(max)
58+
}
59+
}
60+
precondition(queue.isEmpty)
61+
blackHole(queue)
62+
}
63+
}
64+
65+
self.add(
66+
title: "Heap<Int> popMin",
67+
input: [Int].self
68+
) { input in
69+
return { timer in
70+
var queue = Heap(input)
71+
timer.measure {
72+
while let min = queue.popMin() {
73+
blackHole(min)
74+
}
75+
}
76+
precondition(queue.isEmpty)
77+
blackHole(queue)
78+
}
79+
}
80+
}
81+
}
82+
83+
// MARK: -
84+
85+
extension Benchmark {
86+
public mutating func addCFBinaryHeapBenchmarks() {
87+
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
88+
self.addSimple(
89+
title: "CFBinaryHeap insert",
90+
input: [Int].self
91+
) { input in
92+
let heap = BinaryHeap()
93+
for i in input {
94+
heap.insert(i)
95+
}
96+
precondition(heap.count == input.count)
97+
blackHole(heap)
98+
}
99+
100+
self.add(
101+
title: "CFBinaryHeap removeMinimumValue",
102+
input: [Int].self
103+
) { input in
104+
return { timer in
105+
let heap = BinaryHeap()
106+
for i in input {
107+
heap.insert(i)
108+
}
109+
110+
timer.measure {
111+
while heap.count > 0 {
112+
let min = heap.popMinimum()
113+
blackHole(min)
114+
}
115+
}
116+
precondition(heap.count == 0)
117+
blackHole(heap)
118+
}
119+
}
120+
#endif
121+
}
122+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift Collections open source project
4+
//
5+
// Copyright (c) 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
//
10+
//===----------------------------------------------------------------------===//
11+
12+
#ifndef BinaryHeap_h
13+
#define BinaryHeap_h
14+
15+
#if __APPLE__ // CFBinaryHeap only exists on Apple platforms
16+
17+
@import Foundation;
18+
19+
@interface BinaryHeap: NSObject
20+
21+
@property (nonatomic, readonly) NSUInteger count;
22+
23+
- (void)insert:(NSInteger)value;
24+
- (NSInteger)popMinimum;
25+
26+
@end
27+
28+
#endif // __APPLE__
29+
#endif /* BinaryHeap_h */

Benchmarks/CppBenchmarks/include/module.modulemap

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ module CppBenchmarks {
44
header "DequeBenchmarks.h"
55
header "UnorderedSetBenchmarks.h"
66
header "UnorderedMapBenchmarks.h"
7+
header "BinaryHeap.h"
78
export *
89
}
9-

0 commit comments

Comments
 (0)