Skip to content

Commit af06df9

Browse files
committed
[stdlib] Optimize Set.subtracting even more
1 parent 8e4b53b commit af06df9

File tree

2 files changed

+29
-1
lines changed

2 files changed

+29
-1
lines changed

stdlib/public/core/NativeSet.swift

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -681,13 +681,31 @@ extension _NativeSet {
681681
internal __consuming func subtracting<S: Sequence>(_ other: S) -> _NativeSet
682682
where S.Element == Element {
683683
guard count > 0 else { return _NativeSet() }
684+
685+
// Find one item that we need to remove before creating a result set.
686+
var it = other.makeIterator()
687+
var bucket: Bucket? = nil
688+
while let next = it.next() {
689+
let (b, found) = find(next)
690+
if found {
691+
bucket = b
692+
break
693+
}
694+
}
695+
guard let bucket = bucket else { return self }
696+
684697
// Rather than directly creating a new set, calculate the difference in a
685698
// bitset first. This ensures we hash each element (in both sets) only once,
686699
// and that we'll have an exact count for the result set, preventing
687700
// rehashings during insertions.
688701
return _UnsafeBitset.withTemporaryCopy(of: hashTable.bitset) { difference in
689702
var remainingCount = self.count
690-
for element in other {
703+
704+
let removed = difference.uncheckedRemove(bucket.offset)
705+
_internalInvariant(removed)
706+
remainingCount -= 1
707+
708+
while let element = it.next() {
691709
let (bucket, found) = find(element)
692710
if found {
693711
if difference.uncheckedRemove(bucket.offset) {

stdlib/public/core/Set.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,6 +1158,16 @@ extension Set {
11581158
/// - Returns: A new set.
11591159
@inlinable
11601160
public __consuming func subtracting(_ other: Set<Element>) -> Set<Element> {
1161+
// Heuristic: if `other` is small enough, it's better to make a copy of the
1162+
// set and remove each item one by one. (The best cutoff point depends on
1163+
// the `Element` type; the one below is an educated guess.) FIXME: Derive a
1164+
// better cutoff by benchmarking.
1165+
if other.count <= self.count / 8 {
1166+
var copy = self
1167+
copy._subtract(other)
1168+
return copy
1169+
}
1170+
// Otherwise do a regular subtraction using a temporary bitmap.
11611171
return self._subtracting(other)
11621172
}
11631173

0 commit comments

Comments
 (0)