Skip to content

Commit

Permalink
Add a min-max heap implementation that can be used to back a priority…
Browse files Browse the repository at this point in the history
… queue (#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 <tvermeulen@apple.com>

* Specify logarithmic complexities as "O(log `count`) / 2" instead of "O(log n)"

Co-authored-by: Tim Vermeulen <tvermeulen@apple.com>

* 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 <34220985+Dante-Broggi@users.noreply.github.com>

* 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 <klorentey@apple.com>

* 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 <klorentey@apple.com>

* CFBinaryHeap is only available on Darwin

* CFBinaryHeap is only available on Darwin

* CFBinaryHeap is only available on Darwin

Co-authored-by: Tim Vermeulen <tvermeulen@apple.com>
Co-authored-by: Amanuel Ephem <amanuelephrem776@gmail.com>
Co-authored-by: Joakim Hassila <joj@mac.com>
Co-authored-by: Dante Broggi <34220985+Dante-Broggi@users.noreply.github.com>
Co-authored-by: Karoy Lorentey <klorentey@apple.com>
  • Loading branch information
6 people authored Aug 7, 2021
1 parent 0959ba7 commit 940c963
Show file tree
Hide file tree
Showing 22 changed files with 1,721 additions and 3 deletions.
77 changes: 77 additions & 0 deletions .swiftpm/xcode/xcshareddata/xcschemes/PriorityQueueModule.xcscheme
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;

@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

0 comments on commit 940c963

Please sign in to comment.