Skip to content

Commit 2da382d

Browse files
authored
Merge pull request #59243 from rctcwyvrn/lily-dictionary-optimization
Optimize Dictionary.filter for native dictionaries
2 parents 3396ede + a56e658 commit 2da382d

File tree

3 files changed

+65
-7
lines changed

3 files changed

+65
-7
lines changed

stdlib/public/core/Dictionary.swift

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -608,15 +608,19 @@ extension Dictionary {
608608
public __consuming func filter(
609609
_ isIncluded: (Element) throws -> Bool
610610
) rethrows -> [Key: Value] {
611-
// FIXME(performance): Try building a bitset of elements to keep, so that we
612-
// eliminate rehashings during insertion.
613-
var result = _NativeDictionary<Key, Value>()
614-
for element in self {
615-
if try isIncluded(element) {
616-
result.insertNew(key: element.key, value: element.value)
611+
#if _runtime(_ObjC)
612+
guard _variant.isNative else {
613+
// Slow path for bridged dictionaries
614+
var result = _NativeDictionary<Key, Value>()
615+
for element in self {
616+
if try isIncluded(element) {
617+
result.insertNew(key: element.key, value: element.value)
618+
}
617619
}
620+
return Dictionary(_native: result)
618621
}
619-
return Dictionary(_native: result)
622+
#endif
623+
return Dictionary(_native: try _variant.asNative.filter(isIncluded))
620624
}
621625
}
622626

stdlib/public/core/NativeDictionary.swift

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,27 @@ extension _NativeDictionary {
599599
(_values + a.offset).moveInitialize(from: _values + b.offset, count: 1)
600600
(_values + b.offset).initialize(to: value)
601601
}
602+
603+
@_alwaysEmitIntoClient
604+
internal func extractDictionary(
605+
using bitset: _UnsafeBitset,
606+
count: Int
607+
) -> _NativeDictionary<Key, Value> {
608+
var count = count
609+
if count == 0 { return _NativeDictionary<Key, Value>() }
610+
if count == self.count { return self }
611+
let result = _NativeDictionary<Key, Value>(capacity: count)
612+
for offset in bitset {
613+
let key = self.uncheckedKey(at: Bucket(offset: offset))
614+
let value = self.uncheckedValue(at: Bucket(offset: offset))
615+
result._unsafeInsertNew(key: key, value: value)
616+
// The hash table can have set bits after the end of the bitmap.
617+
// Ignore them.
618+
count -= 1
619+
if count == 0 { break }
620+
}
621+
return result
622+
}
602623
}
603624

604625
extension _NativeDictionary where Value: Equatable {
@@ -771,6 +792,26 @@ extension _NativeDictionary { // High-level operations
771792
}
772793
}
773794
}
795+
796+
@_alwaysEmitIntoClient
797+
internal func filter(
798+
_ isIncluded: (Element) throws -> Bool
799+
) rethrows -> _NativeDictionary<Key, Value> {
800+
try _UnsafeBitset.withTemporaryBitset(
801+
capacity: _storage._bucketCount
802+
) { bitset in
803+
var count = 0
804+
for bucket in hashTable {
805+
if try isIncluded(
806+
(uncheckedKey(at: bucket), uncheckedValue(at: bucket))
807+
) {
808+
bitset.uncheckedInsert(bucket.offset)
809+
count += 1
810+
}
811+
}
812+
return extractDictionary(using: bitset, count: count)
813+
}
814+
}
774815
}
775816

776817
extension _NativeDictionary: Sequence {

validation-test/stdlib/Dictionary.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2013,6 +2013,19 @@ DictionaryTestSuite.test("mapValues(_:)") {
20132013
}
20142014
}
20152015

2016+
DictionaryTestSuite.test("filter(_:)") {
2017+
let d1 = [1: 1, 2: 2, 3: 100, 4: 4, 5: 100, 6: 6]
2018+
let d2 = d1.filter() {key, value in key == value}
2019+
2020+
expectEqual(d2.count, 4)
2021+
for (key, value) in d2 {
2022+
expectEqual(key, value)
2023+
}
2024+
2025+
expectNil(d2[3])
2026+
expectNil(d2[5])
2027+
}
2028+
20162029
DictionaryTestSuite.test("capacity/init(minimumCapacity:)") {
20172030
let d0 = Dictionary<String, Int>(minimumCapacity: 0)
20182031
expectGE(d0.capacity, 0)

0 commit comments

Comments
 (0)