From 128fe5734a672bb540577b3bd9e0bc2b4a6ae426 Mon Sep 17 00:00:00 2001 From: Daniel Duan Date: Thu, 1 Jan 2026 23:59:29 -0800 Subject: [PATCH] Use fast key hash for parsing Replace String.hashValue with a lightweight FNV-1a hash for key prefiltering to reduce hashing overhead during parse and table lookups. --- Sources/TOMLDecoder/Parsing/Parser.swift | 34 ++++++++++++++++--- .../TOMLDecoder/Parsing/TOMLDocument.swift | 2 +- 2 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Sources/TOMLDecoder/Parsing/Parser.swift b/Sources/TOMLDecoder/Parsing/Parser.swift index 7f145b9..73c700d 100644 --- a/Sources/TOMLDecoder/Parsing/Parser.swift +++ b/Sources/TOMLDecoder/Parsing/Parser.swift @@ -399,7 +399,7 @@ struct Parser { mutating func createKeyValue(bytes: UnsafeBufferPointer, token: Token, inTable tableIndex: Int, isKeyed: Bool) throws(TOMLError) -> Int { let key = try normalizeKey(bytes: bytes, token: token, keyTransform: keyTransform) - let keyHash = key.hashValue + let keyHash = fastKeyHash(key) if tableValue(tableIndex: tableIndex, keyed: isKeyed, key: key, keyHash: keyHash) != nil { throw TOMLError(.badKey(lineNumber: token.lineNumber)) } @@ -423,7 +423,7 @@ struct Parser { mutating func createKeyTable(bytes: UnsafeBufferPointer, token: Token, inTable tableIndex: Int, isKeyed: Bool, implicit: Bool = false) throws(TOMLError) -> Int { let key = try normalizeKey(bytes: bytes, token: token, keyTransform: keyTransform) - let keyHash = key.hashValue + let keyHash = fastKeyHash(key) // Check if parent table is readOnly (inline table) if isKeyed ? keyTables[tableIndex].table.readOnly : tables[tableIndex].readOnly { throw TOMLError(.syntax(lineNumber: token.lineNumber, message: "cannot add to inline table")) @@ -466,7 +466,7 @@ struct Parser { mutating func createKeyArray(bytes: UnsafeBufferPointer, token: Token, inTable tableIndex: Int, isKeyed: Bool, kind: InternalTOMLArray.Kind? = nil) throws(TOMLError) -> Int { let key = try normalizeKey(bytes: bytes, token: token, keyTransform: keyTransform) - let keyHash = key.hashValue + let keyHash = fastKeyHash(key) if tableValue(tableIndex: tableIndex, keyed: isKeyed, key: key, keyHash: keyHash) != nil { throw TOMLError(.keyExists(lineNumber: token.lineNumber)) } @@ -811,7 +811,7 @@ struct Parser { if token.kind == .dot { let subTableKey = try normalizeKey(bytes: bytes, token: key, keyTransform: keyTransform) - let subTableHash = subTableKey.hashValue + let subTableHash = fastKeyHash(subTableKey) let subTableIndex: Int if let existingTableIndex = lookupTable(in: tableIndex, keyed: isKeyed, key: subTableKey, keyHash: subTableHash) { @@ -868,7 +868,7 @@ struct Parser { } let key = try normalizeKey(bytes: bytes, token: token, keyTransform: keyTransform) - tablePath.append((key: key, keyHash: key.hashValue, token: token)) + tablePath.append((key: key, keyHash: fastKeyHash(key), token: token)) try nextToken(bytes: bytes, isDotSpecial: true) if token.kind == .rbracket { @@ -1766,6 +1766,30 @@ func normalizeKey(bytes: UnsafeBufferPointer, token: Token, keyTransform: return makeString(bytes: bytes, range: start ..< end) } +@inline(__always) +func fastKeyHash(_ key: String) -> Int { + let offsetBasis: UInt64 = 14_695_981_039_346_656_037 + let prime: UInt64 = 1_099_511_628_211 + + if let hash = key.utf8.withContiguousStorageIfAvailable({ buffer -> UInt64 in + var hash = offsetBasis + for byte in buffer { + hash ^= UInt64(byte) + hash &*= prime + } + return hash + }) { + return Int(truncatingIfNeeded: hash) + } + + var hash = offsetBasis + for byte in key.utf8 { + hash ^= UInt64(byte) + hash &*= prime + } + return Int(truncatingIfNeeded: hash) +} + private func makeString(bytes: UnsafeBufferPointer, range: Range) -> String { String(decoding: bytes[range], as: UTF8.self) } diff --git a/Sources/TOMLDecoder/Parsing/TOMLDocument.swift b/Sources/TOMLDecoder/Parsing/TOMLDocument.swift index 431b977..273ff0d 100644 --- a/Sources/TOMLDecoder/Parsing/TOMLDocument.swift +++ b/Sources/TOMLDecoder/Parsing/TOMLDocument.swift @@ -132,7 +132,7 @@ struct InternalTOMLTable: Equatable, Sendable { } func contains(source: TOMLDocument, key: String) -> Bool { - let keyHash = key.hashValue + let keyHash = fastKeyHash(key) for kv in keyValues { let pair = source.keyValues[kv] if pair.keyHash == keyHash, pair.key == key {