Skip to content

Commit 98f2785

Browse files
committed
Continue applying refactors and optimizations.
Key Changes: - _BTree.init() dynamically computes capacity from stride - _BTree.UnsafePath uses Unmanaged +0 references. Minor Changes: - Fix _BTree.Iterator bug on single-leaf trees.
1 parent 8de0b4f commit 98f2785

25 files changed

+192
-176
lines changed

Sources/SortedCollections/BTree/_BTree+BidirectionalCollection.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ extension _BTree: BidirectionalCollection {
1414
/// - Complexity: O(1)
1515
@inlinable
1616
@inline(__always)
17-
internal var count: Int { self.root.storage.header.totalElements }
17+
internal var count: Int { self.root.storage.header.subtreeCount }
1818

1919
/// A Boolean value that indicates whether the BTree is empty.
2020
@inlinable
@@ -61,7 +61,8 @@ extension _BTree: BidirectionalCollection {
6161
/// - Parameters:
6262
/// - start: A valid index of the collection.
6363
/// - end: Another valid index of the collection. If end is equal to start, the result is zero.
64-
/// - Returns: The distance between start and end. The result can be negative only if the collection conforms to the BidirectionalCollection protocol.
64+
/// - Returns: The distance between start and end. The result can be negative only if the collection
65+
/// conforms to the BidirectionalCollection protocol.
6566
/// - Complexity: O(1)
6667
@inlinable
6768
internal func distance(from start: Index, to end: Index) -> Int {
@@ -77,9 +78,9 @@ extension _BTree: BidirectionalCollection {
7778
preconditionFailure("Attempt to advance out of collection bounds.")
7879
}
7980

80-
let shouldSeekWithinLeaf = Node(path.node).read({
81+
let shouldSeekWithinLeaf = path.readNode {
8182
$0.isLeaf && _fastPath(path.slot + 1 < $0.elementCount)
82-
})
83+
}
8384

8485
if shouldSeekWithinLeaf {
8586
// Continue searching within the same leaf
@@ -143,10 +144,10 @@ extension _BTree: BidirectionalCollection {
143144

144145
// TODO: optimization for searching within children
145146

146-
if var path = i.path, path.node.header.children == nil {
147+
if var path = i.path, path.readNode({ $0.isLeaf }) {
147148
// Check if the target element will be in the same node
148149
let targetSlot = path.slot + distance
149-
if 0 <= targetSlot && targetSlot < path.node.header.count {
150+
if 0 <= targetSlot && targetSlot < path.readNode({ $0.elementCount }) {
150151
path.slot = targetSlot
151152
path.offset = newIndex
152153
i.path = path

Sources/SortedCollections/BTree/_BTree+Partial RangeReplaceableCollection.swift

Lines changed: 9 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ extension _BTree {
2222
// TODO: optimize implementation to O(n)
2323
var newTree: _BTree = _BTree()
2424
for element in self where try isIncluded(element) {
25-
newTree.setAnyValue(element.value, forKey: element.key)
25+
newTree.updateAnyValue(element.value, forKey: element.key)
2626
}
2727
return newTree
2828
}
@@ -102,21 +102,21 @@ extension _BTree {
102102
}
103103
}
104104

105-
// MARK: Offset Removal
106-
/// Removes the element of a tree at a given offset.
105+
// MARK: Index Removal
106+
/// Removes the element of a tree at a given index.
107107
///
108-
/// - Parameter offset: the offset which must be in-bounds.
108+
/// - Parameter index: a valid index of the tree, not `endIndex`
109109
/// - Returns: The moved element of the tree
110110
@inlinable
111111
@inline(__always)
112112
@discardableResult
113-
internal mutating func remove(at offset: Int) -> Element {
113+
internal mutating func remove(at index: Index) -> Element {
114114
invalidateIndices()
115-
let removedElement = self.root.update { $0.remove(at: offset) }
116-
self._balanceRoot()
117-
return removedElement
115+
guard let path = index.path else { preconditionFailure("Index out of bounds.") }
116+
return self.remove(atOffset: path.offset)
118117
}
119118

119+
// MARK: Bulk Removal
120120
@inlinable
121121
@inline(__always)
122122
internal mutating func removeAll() {
@@ -125,20 +125,6 @@ extension _BTree {
125125
self.root = _Node(withCapacity: _BTree.defaultLeafCapacity, isLeaf: true)
126126
}
127127

128-
// MARK: Index Removal
129-
/// Removes the element of a tree at a given index.
130-
///
131-
/// - Parameter index: a valid index of the tree, not `endIndex`
132-
/// - Returns: The moved element of the tree
133-
@inlinable
134-
@inline(__always)
135-
@discardableResult
136-
internal mutating func remove(at index: Index) -> Element {
137-
invalidateIndices()
138-
guard let path = index.path else { preconditionFailure("Index out of bounds.") }
139-
return self.remove(at: path.offset)
140-
}
141-
142128
/// Removes the elements in the specified subrange from the collection.
143129
@inlinable
144130
internal mutating func removeSubrange(_ bounds: Range<Index>) {
@@ -149,7 +135,7 @@ extension _BTree {
149135
let startOffset = startPath.offset
150136

151137
for _ in 0..<rangeSize {
152-
self.remove(at: startOffset)
138+
self.remove(atOffset: startOffset)
153139
}
154140
}
155141

Sources/SortedCollections/BTree/_BTree+Sequence.swift

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@ extension _BTree: Sequence {
3535
self.offsets = .init(repeating: 0)
3636
self.parents = .init(repeating: .passUnretained(tree.root.storage))
3737

38+
// Simple case for an empty tree
39+
if self.tree.isEmpty {
40+
self.currentNode = .passUnretained(tree.root.storage)
41+
self.slot = -1
42+
return
43+
}
44+
3845
// TODO: maybe convert to unowned(unsafe)
3946
var node = tree.root
4047
while !node.read({ $0.isLeaf }) {
@@ -52,6 +59,7 @@ extension _BTree: Sequence {
5259
@inlinable
5360
@inline(__always)
5461
internal mutating func next() -> Element? {
62+
// Check slot sentinel value for end of tree.
5563
if _slowPath(self.slot == -1) {
5664
return nil
5765
}
@@ -63,11 +71,13 @@ extension _BTree: Sequence {
6371
if !handle.isLeaf {
6472
self.parents.append(self.currentNode)
6573
self.offsets.append(UInt16(self.slot + 1))
74+
75+
// TODO: make these descents Unmanaged
6676
var node = handle[childAt: self.slot + 1]
6777

6878
while !node.read({ $0.isLeaf }) {
69-
self.offsets.append(0)
7079
self.parents.append(.passUnretained(node.storage))
80+
self.offsets.append(0)
7181
node = node.read({ $0[childAt: 0] })
7282
}
7383

@@ -78,6 +88,11 @@ extension _BTree: Sequence {
7888
self.slot += 1
7989
} else {
8090
while true {
91+
if self.parents.depth == 0 {
92+
self.slot = -1
93+
break
94+
}
95+
8196
// If we are at a leaf, then ascend to the
8297
let parent = self.parents.pop()
8398
let offset = self.offsets.pop()
@@ -88,9 +103,6 @@ extension _BTree: Sequence {
88103
self.currentNode = parent
89104
self.slot = Int(offset)
90105
break
91-
} else if self.parents.depth == 0 {
92-
self.slot = -1
93-
break
94106
}
95107
}
96108
}

Sources/SortedCollections/BTree/_BTree.Index.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ extension _BTree {
4141
@inlinable
4242
@inline(__always)
4343
internal static func ==(lhs: Index, rhs: Index) -> Bool {
44-
return lhs.path?.node === rhs.path?.node
44+
return lhs.path?.offset == rhs.path?.offset
4545
}
4646

4747
@inlinable
@@ -73,7 +73,9 @@ extension _BTree {
7373
@inline(__always)
7474
internal func ensureValid(with index: Index) {
7575
precondition(
76-
self.root != nil && self.root === index.root && self.version == index.version,
76+
self.root != nil &&
77+
self.root === index.root &&
78+
self.version == index.version,
7779
"Attempt to use an invalid indices.")
7880
}
7981
}

Sources/SortedCollections/BTree/_BTree.PackedList.swift

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,17 @@
99
//
1010
//===----------------------------------------------------------------------===//
1111

12-
@usableFromInline
13-
internal let _PACKED_OFFSET_LIST_MAX_SIZE = 16
14-
1512
extension _BTree {
1613
/// A stack-allocated list of some values.
14+
///
1715
/// - Warning: This may hold strong references to objects after they
1816
/// do not put non-trivial types in this.
1917
@usableFromInline
2018
internal struct FixedSizeArray<Value> {
19+
@inlinable
20+
@inline(__always)
21+
internal static var maxSize: UInt8 { 16 }
22+
2123
@usableFromInline
2224
internal var depth: UInt8
2325

@@ -45,7 +47,8 @@ extension _BTree {
4547
@inlinable
4648
@inline(__always)
4749
internal mutating func append(_ offset: Value) {
48-
assert(depth < _PACKED_OFFSET_LIST_MAX_SIZE, "Out of bounds access in offset list.")
50+
assert(depth < FixedSizeArray.maxSize,
51+
"Out of bounds access in offset list.")
4952
self.depth &+= 1
5053
self[self.depth] = offset
5154
}
@@ -63,7 +66,8 @@ extension _BTree {
6366
@inline(__always)
6467
internal subscript(_ offset: UInt8) -> Value {
6568
get {
66-
assert(offset <= depth && depth <= _PACKED_OFFSET_LIST_MAX_SIZE, "Out of bounds access in offset list.")
69+
assert(offset <= depth && depth <= FixedSizeArray.maxSize,
70+
"Out of bounds access in offset list.")
6771

6872
switch offset {
6973
case 0: return self.values.0
@@ -87,7 +91,9 @@ extension _BTree {
8791
}
8892

8993
_modify {
90-
assert(offset <= depth && depth <= _PACKED_OFFSET_LIST_MAX_SIZE, "Out of bounds access in offset list.")
94+
assert(offset <= depth && depth <= FixedSizeArray.maxSize,
95+
"Out of bounds access in offset list.")
96+
9197
switch offset {
9298
case 0: yield &self.values.0
9399
case 1: yield &self.values.1

Sources/SortedCollections/BTree/_BTree.UnsafePath.swift

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@ extension _BTree {
2828
@usableFromInline
2929
internal var childSlots: Offsets
3030

31-
// TODO: switch to Unmanaged<Node.Storage>
3231
@usableFromInline
33-
internal unowned(unsafe) var node: Node.Storage
32+
internal var node: Unmanaged<Node.Storage>
3433

3534
@usableFromInline
3635
internal var slot: Int
@@ -85,7 +84,7 @@ extension _BTree {
8584
childSlots: Offsets,
8685
offset: Int
8786
) {
88-
self.node = node
87+
self.node = .passUnretained(node)
8988
self.slot = slot
9089
self.childSlots = childSlots
9190
self.offset = offset
@@ -97,7 +96,16 @@ extension _BTree {
9796
@inlinable
9897
@inline(__always)
9998
internal var element: Element {
100-
return Node(self.node).read { $0[elementAt: self.slot] }
99+
return self.readNode { $0[elementAt: self.slot] }
100+
}
101+
102+
/// Operators on a handle of the node
103+
@inlinable
104+
@inline(__always)
105+
internal func readNode<R>(
106+
_ body: (Node.UnsafeHandle) throws -> R
107+
) rethrows -> R {
108+
return try self.node._withUnsafeGuaranteedRef { try $0.read(body) }
101109
}
102110
}
103111
}
@@ -110,7 +118,7 @@ extension _BTree.UnsafePath: Equatable {
110118
@inlinable
111119
public static func ==(lhs: _BTree.UnsafePath, rhs: _BTree.UnsafePath) -> Bool {
112120
// We assume the parents are the same
113-
return lhs.node === rhs.node && lhs.slot == rhs.slot
121+
return lhs.node.toOpaque() == rhs.node.toOpaque() && lhs.slot == rhs.slot
114122
}
115123
}
116124

0 commit comments

Comments
 (0)