-
-
Notifications
You must be signed in to change notification settings - Fork 59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimise FlatDictionary init for scenarios where no values are stored and llow inlining access #456
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,104 +26,154 @@ public struct FlatDictionary<Key: Hashable, Value>: Collection, ExpressibleByDic | |
public typealias Element = (key: Key, value: Value) | ||
public typealias Index = Array<Element>.Index | ||
|
||
@usableFromInline | ||
struct Storage { | ||
@usableFromInline internal /* private */ var elements: [Element] | ||
@usableFromInline internal /* private */ var hashKeys: [Int] | ||
|
||
@usableFromInline | ||
init(elements: [Element], hashKeys: [Int]) { | ||
self.elements = elements | ||
self.hashKeys = hashKeys | ||
} | ||
} | ||
|
||
@usableFromInline internal /* private */ var storage: Storage? | ||
|
||
// MARK: Collection requirements | ||
|
||
/// The position of the first element | ||
public var startIndex: Index { self.elements.startIndex } | ||
@inlinable | ||
public var startIndex: Index { self.storage?.elements.startIndex ?? 0 } | ||
/// The position of the element just after the last element | ||
public var endIndex: Index { self.elements.endIndex } | ||
@inlinable | ||
public var endIndex: Index { self.storage?.elements.endIndex ?? 0 } | ||
/// Access element at specific position | ||
public subscript(_ index: Index) -> Element { return self.elements[index] } | ||
@inlinable | ||
public subscript(_ index: Index) -> Element { return self.storage!.elements[index] } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
/// Returns the index immediately after the given index | ||
public func index(after index: Index) -> Index { self.elements.index(after: index) } | ||
@inlinable | ||
public func index(after index: Index) -> Index { index + 1 } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason you changed this? I know |
||
|
||
/// Create a new FlatDictionary | ||
public init() { | ||
self.elements = [] | ||
self.hashKeys = [] | ||
} | ||
@inlinable | ||
public init() {} | ||
|
||
/// Create a new FlatDictionary initialized with a dictionary literal | ||
public init(dictionaryLiteral elements: (Key, Value)...) { | ||
self.elements = elements.map { (key: $0.0, value: $0.1) } | ||
self.hashKeys = elements.map { | ||
Self.hashKey($0.0) | ||
} | ||
self.storage = Storage( | ||
elements: elements.map { (key: $0.0, value: $0.1) }, | ||
hashKeys: elements.map { | ||
Self.hashKey($0.0) | ||
} | ||
) | ||
} | ||
|
||
/// Create a new FlatDictionary from an array of key value pairs | ||
public init(_ values: [Element]) { | ||
self.elements = values | ||
self.hashKeys = values.map { | ||
Self.hashKey($0.key) | ||
} | ||
self.storage = Storage( | ||
elements: values, | ||
hashKeys: values.map { | ||
Self.hashKey($0.0) | ||
} | ||
) | ||
} | ||
|
||
/// Access the value associated with a given key for reading and writing | ||
/// | ||
/// Because FlatDictionary allows for key clashes this function will | ||
/// return the first entry in the array with the associated key | ||
@inlinable | ||
public subscript(_ key: Key) -> Value? { | ||
get { | ||
let hashKey = Self.hashKey(key) | ||
if let index = hashKeys.firstIndex(of: hashKey) { | ||
return self.elements[index].value | ||
if let storage, let index = storage.hashKeys.firstIndex(of: hashKey) { | ||
return storage.elements[index].value | ||
} else { | ||
return nil | ||
} | ||
} | ||
set { | ||
let hashKey = Self.hashKey(key) | ||
if let index = hashKeys.firstIndex(of: hashKey) { | ||
if var storage, let index = storage.hashKeys.firstIndex(of: hashKey) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The ordering of the if statements here is slightly confusing. Currently you test for the existence of storage twice when the key doesn't exist. Perhaps it should be if let storage {
// do stuff when storage already exists
} else {
// create storage if needed
} |
||
if let newValue { | ||
self.elements[index].value = newValue | ||
storage.elements[index].value = newValue | ||
} else { | ||
self.elements.remove(at: index) | ||
self.hashKeys.remove(at: index) | ||
storage.elements.remove(at: index) | ||
storage.hashKeys.remove(at: index) | ||
} | ||
|
||
self.storage = storage | ||
} else if let newValue { | ||
self.elements.append((key: key, value: newValue)) | ||
self.hashKeys.append(hashKey) | ||
if var storage { | ||
storage.elements.append((key: key, value: newValue)) | ||
storage.hashKeys.append(hashKey) | ||
self.storage = storage | ||
} else { | ||
self.storage = Storage( | ||
elements: [(key: key, value: newValue)], | ||
hashKeys: [hashKey] | ||
) | ||
} | ||
} | ||
} | ||
} | ||
|
||
/// Return all the values, associated with a given key | ||
@inlinable | ||
public subscript(values key: Key) -> [Value] { | ||
guard let storage else { | ||
return [] | ||
} | ||
|
||
var values: [Value] = [] | ||
let hashKey = Self.hashKey(key) | ||
|
||
for hashIndex in 0..<self.hashKeys.count { | ||
if self.hashKeys[hashIndex] == hashKey { | ||
values.append(self.elements[hashIndex].value) | ||
for hashIndex in 0..<storage.hashKeys.count { | ||
if storage.hashKeys[hashIndex] == hashKey { | ||
values.append(storage.elements[hashIndex].value) | ||
} | ||
} | ||
return values | ||
} | ||
|
||
/// Return if dictionary has this value | ||
/// - Parameter key: | ||
@inlinable | ||
public func has(_ key: Key) -> Bool { | ||
guard let storage else { | ||
return false | ||
} | ||
|
||
let hashKey = Self.hashKey(key) | ||
return self.hashKeys.firstIndex(of: hashKey) != nil | ||
return storage.hashKeys.firstIndex(of: hashKey) != nil | ||
} | ||
|
||
/// Append a new key value pair to the list of key value pairs | ||
@inlinable | ||
public mutating func append(key: Key, value: Value) { | ||
let hashKey = Self.hashKey(key) | ||
self.elements.append((key: key, value: value)) | ||
self.hashKeys.append(hashKey) | ||
|
||
if var storage { | ||
storage.elements.append((key: key, value: value)) | ||
storage.hashKeys.append(hashKey) | ||
self.storage = storage | ||
} else { | ||
self.storage = Storage( | ||
elements: [(key: key, value: value)], | ||
hashKeys: [hashKey] | ||
) | ||
} | ||
} | ||
|
||
private static func hashKey(_ key: Key) -> Int { | ||
@usableFromInline | ||
internal /* private */ static func hashKey(_ key: Key) -> Int { | ||
var hasher = Hasher() | ||
hasher.combine(key) | ||
return hasher.finalize() | ||
} | ||
|
||
private var elements: [Element] | ||
private var hashKeys: [Int] | ||
} | ||
|
||
// FlatDictionary is Sendable when Key and Value are Sendable | ||
extension FlatDictionary.Storage: Sendable where Key: Sendable, Value: Sendable {} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I added this in to get rid of a Sendable warning |
||
extension FlatDictionary: Sendable where Key: Sendable, Value: Sendable {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we remove the commented out
/* private */
if we require everything to beinternal