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

Add a min-max heap implementation that can be used to back a priority queue #61

Merged
merged 63 commits into from
Aug 7, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
91b7e50
Add PriorityQueue implementation built on top of a MinMaxHeap
AquaGeek May 28, 2021
f81c588
Merge MinMaxHeap into PriorityQueue
AquaGeek Jun 3, 2021
c42096c
Add `unordered` read-only view into underlying heap
AquaGeek Jun 8, 2021
107a9b4
Start filling in PriorityQueue benchmarks
AquaGeek Jun 9, 2021
db87e18
Implement our own swapAt()
AquaGeek Jun 17, 2021
101bc2c
Rename _delete(at:) to _remove(at:)
AquaGeek Jun 17, 2021
1171c0a
Rename removeMin/removeMax -> popMin/popMax
AquaGeek Jun 17, 2021
11ba582
Use magic values 1, 2 to refer to the items in the first max level
AquaGeek Jun 17, 2021
a9efa56
Specify logarithmic complexities as "O(log `count`) / 2" instead of "…
AquaGeek Jun 17, 2021
919c019
Clarify logic in popMax
AquaGeek Jun 17, 2021
5cca169
Simplify logic in _remove(at:)
AquaGeek Jun 17, 2021
5b80eeb
Move the bounds checking into the various index computation methods
AquaGeek Jun 18, 2021
9fe1481
Floyd's heap construction algorithm should start from count/2 - 1
AquaGeek Jun 18, 2021
1ceb496
Add ObjC wrapper around CFBinaryHeap to benchmarks
AquaGeek Jun 18, 2021
9f319ab
Fix benchmarks that broke because of renames
AquaGeek Jun 18, 2021
9182557
Add removeMin/removeMax
AquaGeek Jun 18, 2021
8e92d77
Make _minMaxHeapIsMinLevel an instance method
AquaGeek Jun 18, 2021
18de4fc
Split _indexOfChildOrGrandchild(of:sortedUsing:) into two separate fu…
AquaGeek Jun 18, 2021
25b88e6
Defer comparing children when determining largest/smallest descendant
AquaGeek Jun 18, 2021
0b46a53
Added an iterator of the min and max views to the priority queue
AmanuelEphrem Jun 10, 2021
daa4f94
seperated iterator implementation into a new file
AmanuelEphrem Jun 18, 2021
ee35cc1
Use renamed popMin/Max in Iterator instead of removeMin/Max
AquaGeek Jun 18, 2021
c095031
Fix code formatting
AquaGeek Jun 18, 2021
6452adf
Add sequence initializer
AquaGeek Jun 18, 2021
4f6471f
Fix code formatting in benchmarks
AquaGeek Jun 18, 2021
309fa96
Fix benchmark names
AquaGeek Jun 18, 2021
edc6bcd
Remove init from Collection
AquaGeek Jun 21, 2021
43d13ea
Add conformance to ExpressibleByArrayLiteral
AquaGeek Jun 21, 2021
feb9b7c
Move ExpressibleByArrayLiteral conformance to separate file
AquaGeek Jun 21, 2021
21efcf9
Update PriorityQueue's CMakeLists.txt
AquaGeek Jun 21, 2021
e8501bc
Make ascending and descending iterators public
AquaGeek Jun 21, 2021
6cb5842
Inline ALL THE THINGS!
AquaGeek Jun 22, 2021
07f0649
Iterative instead of recursive implementation, @inline(__always) a co…
hassila Jun 23, 2021
fdcd604
Address PR feedback, thanks!
hassila Jun 23, 2021
593eadb
Check invariants on insertion and deletion
AquaGeek Jun 24, 2021
021704b
Minor code formatting cleanup
AquaGeek Jun 24, 2021
88faaee
Add copyright header to test file
AquaGeek Jun 24, 2021
2557ef1
Fix missing empty line
AquaGeek Jun 24, 2021
a4f9ba0
Add naïve implementation of insert(contentsOf:)
AquaGeek Jun 25, 2021
69583d8
Add documentation on complexity of init<S:Sequence>(_:)
AquaGeek Jun 25, 2021
b8507fa
Cite source paper in documentation
AquaGeek Jun 30, 2021
cb78920
Rename PriorityQueue -> MinMaxHeap
AquaGeek Jul 6, 2021
f3349e5
Mark insert(contentsOf:) as inlinable
AquaGeek Jul 6, 2021
2bc784a
Rename argument label "startingAt" -> "elementAt"
AquaGeek Jul 6, 2021
ceae6ea
Remove coefficients from complexity docs
AquaGeek Jul 6, 2021
272bb02
Make `_minMaxHeapIsMinLevel` take an index instead of a count
AquaGeek Jul 6, 2021
d1c529e
Rename MinMaxHeap -> Heap
AquaGeek Jul 7, 2021
d7bf55d
Add documentation for Heap
AquaGeek Jul 8, 2021
60b6ab2
Make Heap.Iterator init and direction internal
AquaGeek Jul 9, 2021
c37395c
Fix reference to queue in documentation
AquaGeek Jul 12, 2021
23dbd4f
Don't wrap integers in CFBinaryHeap benchmark in NSNumber
AquaGeek Jul 16, 2021
7b633fd
Avoid heap allocation altogether
AquaGeek Jul 16, 2021
b7a0f7f
Add table with performance of operations
AquaGeek Jul 16, 2021
55538df
Add heap performance graph
AquaGeek Jul 19, 2021
55b5500
Apply suggestions from code review
AquaGeek Aug 6, 2021
563c75e
Make _checkInvariants comments a doc comment
AquaGeek Aug 6, 2021
42fbc80
Split Heap.Iterator into two separate views
AquaGeek Aug 6, 2021
32358a9
Prefix heap storage variable with an underscore
AquaGeek Aug 6, 2021
7d1044e
Update benchmark image
AquaGeek Aug 6, 2021
bd1f007
Fix Sources/PriorityQueueModule/CMakeLists.txt
AquaGeek Aug 6, 2021
c6a7954
CFBinaryHeap is only available on Darwin
lorentey Aug 7, 2021
4bc94ac
CFBinaryHeap is only available on Darwin
lorentey Aug 7, 2021
083102e
CFBinaryHeap is only available on Darwin
lorentey Aug 7, 2021
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1250"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PriorityQueueModule"
BuildableName = "PriorityQueueModule"
BlueprintName = "PriorityQueueModule"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PriorityQueueTests"
BuildableName = "PriorityQueueTests"
BlueprintName = "PriorityQueueTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PriorityQueueModule"
BuildableName = "PriorityQueueModule"
BlueprintName = "PriorityQueueModule"
ReferencedContainer = "container:">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,20 @@
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PriorityQueueModule"
BuildableName = "PriorityQueueModule"
BlueprintName = "PriorityQueueModule"
ReferencedContainer = "container:">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
Expand Down Expand Up @@ -170,6 +184,16 @@
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PriorityQueueTests"
BuildableName = "PriorityQueueTests"
BlueprintName = "PriorityQueueTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "PriorityQueueTests"
BuildableName = "PriorityQueueTests"
BlueprintName = "PriorityQueueTests"
ReferencedContainer = "container:">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
Expand Down
40 changes: 40 additions & 0 deletions Benchmarks/Benchmarks/Library.json
Original file line number Diff line number Diff line change
Expand Up @@ -764,6 +764,46 @@
}
]
},
{
"kind": "group",
"title": "PriorityQueue",
"contents": [
{
"kind": "chart",
"title": "operations",
"tasks": [
"Heap<Int> init from range",
"Heap<Int> insert",
"Heap<Int> insert(contentsOf:)",
"Heap<Int> popMax",
"Heap<Int> popMin"
]
},
{
"kind": "chart",
"title": "initializers",
"tasks": [
"Heap<Int> init from range"
]
},
{
"kind": "chart",
"title": "insert",
"tasks": [
"Heap<Int> insert",
"Heap<Int> insert(contentsOf:)"
]
},
{
"kind": "chart",
"title": "remove",
"tasks": [
"Heap<Int> popMax",
"Heap<Int> popMin"
]
}
]
},
{
"kind": "group",
"title": "Against other Swift collections",
Expand Down
122 changes: 122 additions & 0 deletions Benchmarks/Benchmarks/PriorityQueueBenchmarks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Collections open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

import CollectionsBenchmark
import PriorityQueueModule
import CppBenchmarks

extension Benchmark {
public mutating func addHeapBenchmarks() {
self.addSimple(
title: "Heap<Int> init from range",
input: Int.self
) { size in
blackHole(Heap(0..<size))
}

self.addSimple(
title: "Heap<Int> insert",
input: [Int].self
) { input in
var queue = Heap<Int>()
for i in input {
queue.insert(i)
}
precondition(queue.count == input.count)
blackHole(queue)
}

self.add(
title: "Heap<Int> insert(contentsOf:)",
input: ([Int], [Int]).self
) { (existing, new) in
return { timer in
var queue = Heap(existing)
queue.insert(contentsOf: new)
precondition(queue.count == existing.count + new.count)
blackHole(queue)
}
}

self.add(
title: "Heap<Int> popMax",
input: [Int].self
) { input in
return { timer in
var queue = Heap(input)
timer.measure {
while let max = queue.popMax() {
blackHole(max)
}
}
precondition(queue.isEmpty)
blackHole(queue)
}
}

self.add(
title: "Heap<Int> popMin",
input: [Int].self
) { input in
return { timer in
var queue = Heap(input)
timer.measure {
while let min = queue.popMin() {
blackHole(min)
}
}
precondition(queue.isEmpty)
blackHole(queue)
}
}
}
}

// MARK: -

extension Benchmark {
public mutating func addCFBinaryHeapBenchmarks() {
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
self.addSimple(
title: "CFBinaryHeap insert",
input: [Int].self
) { input in
let heap = BinaryHeap()
for i in input {
heap.insert(i)
}
precondition(heap.count == input.count)
blackHole(heap)
}

self.add(
title: "CFBinaryHeap removeMinimumValue",
input: [Int].self
) { input in
return { timer in
let heap = BinaryHeap()
for i in input {
heap.insert(i)
}

timer.measure {
while heap.count > 0 {
let min = heap.popMinimum()
blackHole(min)
}
}
precondition(heap.count == 0)
blackHole(heap)
}
}
#endif
}
}
29 changes: 29 additions & 0 deletions Benchmarks/CppBenchmarks/include/BinaryHeap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift Collections open source project
//
// Copyright (c) 2021 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
//
//===----------------------------------------------------------------------===//

#ifndef BinaryHeap_h
#define BinaryHeap_h

#if __APPLE__ // CFBinaryHeap only exists on Apple platforms

@import Foundation;

@interface BinaryHeap: NSObject

@property (nonatomic, readonly) NSUInteger count;

- (void)insert:(NSInteger)value;
- (NSInteger)popMinimum;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FIXME: I wonder if objc_msg_send introduces measurable overhead in the benchmarks -- it's fast, but I don't expect it fares well when compared to a direct function call, and these microbenchmarks are tiny enough that even minute overheads have an effect. It would be interesting to compare results to a variant where these are all C functions.

(This doesn't need to be resolved before this lands.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. I started writing a C++ benchmark for comparison but haven't been able to finish that yet. I'd be happy for any assistance improving the comparative benchmarks here from anyone; I'm quickly approaching the limits of my understanding of perf.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can have a go at it relatively soon.


@end

#endif // __APPLE__
#endif /* BinaryHeap_h */
2 changes: 1 addition & 1 deletion Benchmarks/CppBenchmarks/include/module.modulemap
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ module CppBenchmarks {
header "DequeBenchmarks.h"
header "UnorderedSetBenchmarks.h"
header "UnorderedMapBenchmarks.h"
header "BinaryHeap.h"
export *
}

Loading