Skip to content

Commit 3d5d5ac

Browse files
committed
Implement B-Tree deletion and respective tests.
Key Changes: - Implement removal through SortedDictionary subscript - Implement _BTree.contains for efficient existence checking - Add tests - _Node.UnsafeHandle.rotation/collapse tests - _BTree.removeAny tests - Expand benchmarks - Add C++ reference lookup benchmarks - Correct old benchmarks Minor Changes: - Remove B-Tree benchmarks, correct SortedDictionary benchmarks - Adjust debug B-Tree printer to show both numElements and numTotalElements - Add total count synchronization invariant check - Implement CustomReflectable for B-Tree
1 parent 821e3e3 commit 3d5d5ac

File tree

13 files changed

+668
-212
lines changed

13 files changed

+668
-212
lines changed

Benchmarks/Benchmarks/CppBenchmarks.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -729,14 +729,14 @@ extension Benchmark {
729729

730730
//--------------------------------------------------------------------------
731731

732-
// self.addSimple(
733-
// title: "std::map<intptr_t, intptr_t> insert",
734-
// input: [Int].self
735-
// ) { input in
736-
// input.withUnsafeBufferPointer { buffer in
737-
// cpp_map_insert_integers(buffer.baseAddress, buffer.count)
738-
// }
739-
// }
732+
self.addSimple(
733+
title: "std::map<intptr_t, intptr_t> insert",
734+
input: [Int].self
735+
) { input in
736+
input.withUnsafeBufferPointer { buffer in
737+
cpp_map_insert_integers(buffer.baseAddress, buffer.count)
738+
}
739+
}
740740

741741
self.add(
742742
title: "std::map<intptr_t, intptr_t> successful find",
@@ -745,7 +745,7 @@ extension Benchmark {
745745
let map = CppMap(input)
746746
return { timer in
747747
lookups.withUnsafeBufferPointer { buffer in
748-
cpp_map_lookups(map.ptr, buffer.baseAddress, buffer.count, true)
748+
cpp_map_lookups(map.ptr, buffer.baseAddress, buffer.count)
749749
}
750750
}
751751
}

Benchmarks/Benchmarks/SortedDictionaryBenchmarks.swift

Lines changed: 29 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -13,77 +13,44 @@ import CollectionsBenchmark
1313

1414
extension Benchmark {
1515
public mutating func addSortedDictionaryBenchmarks() {
16-
// self.add(
17-
// title: "SortedDictionary<Int, Int> init(uniqueKeysWithValues:)",
18-
// input: [Int].self
19-
// ) { input in
20-
// let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
21-
//
22-
// return { timer in
23-
// blackHole(SortedDictionary(uniqueKeysWithValues: keysAndValues))
24-
// }
25-
// }
26-
//
27-
// self.add(
28-
// title: "SortedDictionary<Int, Int> subscript, append",
29-
// input: [Int].self
30-
// ) { input in
31-
// let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
32-
// var sortedDictionary = SortedDictionary<Int, Int>()
33-
//
34-
// return { timer in
35-
// for (key, value) in keysAndValues {
36-
// sortedDictionary[key] = value
37-
// }
38-
// blackHole(sortedDictionary)
39-
// }
40-
// }
41-
4216
self.add(
43-
title: "SortedDictionary<Int, Int> subscript, successful lookups",
17+
title: "SortedDictionary<Int, Int> init(uniqueKeysWithValues:)",
4418
input: [Int].self
4519
) { input in
4620
let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
47-
let sortedDictionary = SortedDictionary<Int, Int>(uniqueKeysWithValues: keysAndValues)
48-
21+
22+
return { timer in
23+
blackHole(SortedDictionary(uniqueKeysWithValues: keysAndValues))
24+
}
25+
}
26+
27+
self.add(
28+
title: "SortedDictionary<Int, Int> subscript, append",
29+
input: [Int].self
30+
) { input in
31+
let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
32+
var sortedDictionary = SortedDictionary<Int, Int>()
33+
4934
return { timer in
5035
for (key, value) in keysAndValues {
51-
precondition(sortedDictionary[key] == value)
36+
sortedDictionary[key] = value
5237
}
38+
blackHole(sortedDictionary)
5339
}
5440
}
5541

56-
// self.add(
57-
// title: "SortedDictionary<Int, Int>._BTree firstValue",
58-
// input: [Int].self
59-
// ) { input in
60-
// let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
61-
// var tree = _BTree<Int, Int>()
62-
//
63-
// for (key, value) in keysAndValues {
64-
// tree.insertOrUpdate((key, value))
65-
// }
66-
//
67-
// return { timer in
68-
// for (key, value) in keysAndValues {
69-
// precondition(tree.anyValue(for: key) != nil)
70-
// }
71-
// }
72-
// }
73-
74-
// self.add(
75-
// title: "SortedDictionary<Int, Int>._BTree insertOrUpdate(element:)",
76-
// input: [Int].self
77-
// ) { input in
78-
// let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
79-
//
80-
// return { timer in
81-
// var tree = _BTree<Int, Int>()
82-
// for (key, value) in keysAndValues {
83-
// tree.insertOrUpdate((key, value))
84-
// }
85-
// blackHole(tree)
86-
// }
87-
// }
42+
self.add(
43+
title: "SortedDictionary<Int, Int> subscript, successful lookups",
44+
input: ([Int], [Int]).self
45+
) { input, lookups in
46+
let keysAndValues = input.lazy.map { (key: $0, value: 2 * $0) }
47+
let sortedDictionary = SortedDictionary<Int, Int>(uniqueKeysWithValues: keysAndValues)
48+
49+
return { timer in
50+
for key in lookups {
51+
precondition(sortedDictionary._root.contains(key: key))
52+
}
53+
}
54+
}
8855
}
8956
}

Benchmarks/CppBenchmarks/include/MapBenchmarks.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ extern void cpp_map_destroy(void *ptr);
2929

3030
extern void cpp_map_insert_integers(const intptr_t *start, size_t count);
3131

32-
extern void cpp_map_lookups(void *ptr, const intptr_t *start, size_t count, bool expectMatch);
32+
extern void cpp_map_lookups(void *ptr, const intptr_t *start, size_t count);
33+
extern void cpp_map_subscript(void *ptr, const intptr_t *start, size_t count);
3334

3435
#ifdef __cplusplus
3536
}

Benchmarks/CppBenchmarks/src/MapBenchmarks.cpp

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,20 @@ auto find(custom_map* map, intptr_t value)
5151
}
5252

5353
void
54-
cpp_map_lookups(void *ptr, const intptr_t *start, size_t count, bool expectMatch)
54+
cpp_map_lookups(void *ptr, const intptr_t *start, size_t count)
5555
{
5656
auto map = static_cast<custom_map *>(ptr);
5757
for (auto it = start; it < start + count; ++it) {
58-
auto found = find(map, *it) != map->end();
59-
if (found != expectMatch) { abort(); }
58+
auto isCorrect = find(map, *it)->second == *it * 2;
59+
if (!isCorrect) { abort(); }
60+
}
61+
}
62+
63+
void
64+
cpp_map_subscript(void *ptr, const intptr_t *start, size_t count)
65+
{
66+
auto map = static_cast<custom_map *>(ptr);
67+
for (auto it = start; it < start + count; ++it) {
68+
black_hole((*map)[*it]);
6069
}
6170
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
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+
extension _BTree: CustomReflectable {
13+
/// The custom mirror for this instance.
14+
@inlinable
15+
internal var customMirror: Mirror {
16+
Mirror(self, unlabeledChildren: self, displayStyle: .dictionary)
17+
}
18+
}

Sources/SortedCollections/BTree/_BTree.Index.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,8 @@
1010
//===----------------------------------------------------------------------===//
1111

1212
extension _BTree {
13+
1314
/// An index to an element of the BTree represented as a path.
14-
/// - Warning: operations using this type do not perform checks if the
15-
/// tree is still valid. Therefore, operations using this may result in
16-
/// undefined behavior if the tree is muated or deallocated.
1715
@usableFromInline
1816
internal struct Index: Comparable {
1917
/// The path to the element in the BTree. A `nil` value indicates

Sources/SortedCollections/BTree/_BTree.swift

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,12 +180,61 @@ extension _BTree {
180180

181181
// TODO: Don't create CoW copy until needed.
182182
// TODO: Handle root deletion
183-
return self.root.update { $0.removeAny(key: key) }
183+
let removedElement = self.root.update { $0.removeAny(key: key) }
184+
185+
// Check if the tree height needs to be reduced
186+
if self.root.read({ $0.numElements == 0 && !$0.isLeaf }) {
187+
let newRoot: Node = self.root.update { handle in
188+
let newRoot = handle.moveChild(at: 0)
189+
handle.drop()
190+
return newRoot
191+
}
192+
193+
self.root = newRoot
194+
}
195+
196+
return removedElement
184197
}
185198
}
186199

187200
// MARK: Read Operations
188-
extension _BTree {
201+
extension _BTree {
202+
/// Determines if a key exists within a tree.
203+
///
204+
/// - Parameter key: The key to search for.
205+
/// - Returns: Whether or not the key was found.
206+
@inlinable
207+
internal func contains(key: Key) -> Bool {
208+
// the retain/release calls
209+
// Retain
210+
var node: Node? = self.root
211+
212+
while let currentNode = node {
213+
let found: Bool = currentNode.read { handle in
214+
let slot = handle.firstSlot(for: key)
215+
216+
if slot < handle.numElements && handle[keyAt: slot] == key {
217+
return true
218+
} else {
219+
if handle.isLeaf {
220+
node = nil
221+
} else {
222+
// Release
223+
// Retain
224+
node = handle[childAt: slot]
225+
}
226+
}
227+
228+
return false
229+
}
230+
231+
if found { return true }
232+
}
233+
234+
return false
235+
}
236+
237+
189238
/// Returns the value corresponding to the first found instance of the key.
190239
///
191240
/// This may not be the first instance of the key. This is marginally more efficient

Sources/SortedCollections/BTree/_Node.UnsafeHandle+CustomDebugStringConvertible.swift

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension _Node.UnsafeHandle: CustomDebugStringConvertible {
1414
#if DEBUG
1515
private enum PrintPosition { case start, end, middle }
1616
private func indentDescription(_ node: _Node<Key, Value>.UnsafeHandle, position: PrintPosition) -> String {
17-
let label = "(\(node.numTotalElements))"
17+
let label = "(\(node.numElements)/\(node.numTotalElements))"
1818

1919
let spaces = String(repeating: " ", count: label.count)
2020

@@ -54,6 +54,20 @@ extension _Node.UnsafeHandle: CustomDebugStringConvertible {
5454

5555
/// A textual representation of this instance, suitable for debugging.
5656
private func describeNode(_ node: _Node<Key, Value>.UnsafeHandle) -> String {
57+
if node.numElements == 0 {
58+
var result = ""
59+
if !node.isLeaf {
60+
node[childAt: 0].read { handle in
61+
result += indentDescription(handle, position: .start) + "\n"
62+
}
63+
64+
result += "┗━ << EMPTY >>"
65+
} else {
66+
result = "╺━ << EMPTY >>"
67+
}
68+
return result
69+
}
70+
5771
var result = ""
5872
for slot in 0..<node.numElements {
5973
if !node.isLeaf {

0 commit comments

Comments
 (0)