-
Notifications
You must be signed in to change notification settings - Fork 15
Make hashing cheaper on hot paths #85
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
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 |
|---|---|---|
|
|
@@ -299,7 +299,10 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, | |
| // List of streams that have app input data in their reassembly queue. | ||
| var pendingReassemblyDequeue = QUICStreamList.pendingReassemblyDequeueList() | ||
|
|
||
| private(set) var knownFlows = [QUICStreamID: MultiplexedFlowIdentifier]() | ||
| // The logical key choice is 'QUICStreamID', however, Swift special cases the hashing of | ||
|
Collaborator
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. @glbrntt what if we just made
Contributor
Author
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 looked into that: sadly So for
Contributor
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. We may want to consider typealias QUICStreamID = UInt64 and then deal with the methods as extensions to UInt64 to avoid more problems like this? |
||
| // various primitives (by avoiding the construction of a Hasher altogether). The result is | ||
| // that hashing the raw value is significantly cheaper which adds up on hot paths. | ||
| private(set) var knownFlows = [UInt64: MultiplexedFlowIdentifier]() | ||
|
Collaborator
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. Nice, thank you for uncovering this @glbrntt ! |
||
|
|
||
| private(set) var localCIDLength: Int = 0 | ||
| private var initialSourceConnectionID: QUICConnectionID? | ||
|
|
@@ -2446,7 +2449,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, | |
| if dataLength > 0 { | ||
| processOutbound(frame: frame, flowID: flowID, stream: stream, isLast: isFinal) | ||
| continue | ||
| } else if isFinal, let _ = knownFlows[streamID] { | ||
| } else if isFinal, let _ = knownFlows[streamID.value] { | ||
| log.datapath("Treating zero length fin as a stop message") | ||
| disconnect(flow: flowID, direction: .outbound) | ||
| } else { | ||
|
|
@@ -2631,7 +2634,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, | |
|
|
||
| if let streamID { | ||
| log.debug("Set known flow \(flowID.debugDescription) for key \(streamID)") | ||
| knownFlows[streamID] = flowID | ||
| knownFlows[streamID.value] = flowID | ||
| if isUnidirectional { | ||
| self.unidirectionalStreams.incrementActiveStreams() | ||
| } else { | ||
|
|
@@ -4323,7 +4326,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, | |
|
|
||
| func deliverInboundAbortedEvent(stream: QUICStreamInstance, error: NetworkError?) { | ||
| guard let streamID = stream.streamID, | ||
| let _ = knownFlows[streamID] | ||
| let _ = knownFlows[streamID.value] | ||
| else { | ||
| log.error("Cannot deliver inbound aborted event: no flow for stream \(stream.streamID?.value ?? 0)") | ||
| return | ||
|
|
@@ -4333,7 +4336,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, | |
|
|
||
| func handleStreamClose(stream: QUICStreamInstance, error: NetworkError?) { | ||
| guard let streamID = stream.streamID, | ||
| let flowID = knownFlows[streamID] | ||
| let flowID = knownFlows[streamID.value] | ||
| else { | ||
| return | ||
| } | ||
|
|
@@ -4347,7 +4350,7 @@ public final class QUICConnection: ManyToManyApplicationStreamProtocol, | |
| } | ||
| stream.closed = true | ||
| deliverDisconnectedEvent(flow: flowID, error: error) | ||
| knownFlows.removeValue(forKey: streamID) | ||
| knownFlows.removeValue(forKey: streamID.value) | ||
| log.datapath("closed stream \(streamID.value)") | ||
|
|
||
| if let streamID = stream.streamID { | ||
|
|
@@ -4559,7 +4562,7 @@ extension QUICConnection { | |
| return false | ||
| } | ||
|
|
||
| let knownFlowID = knownFlows[streamID] | ||
| let knownFlowID = knownFlows[streamID.value] | ||
| if knownFlowID == nil { | ||
| let inboundStreamResult = createInboundStreams(streamID: streamID) | ||
| if frame.isFinal && inboundStreamResult.checkZombie { | ||
|
|
@@ -4577,7 +4580,7 @@ extension QUICConnection { | |
| return false | ||
| } | ||
| } | ||
| guard let flowID = knownFlowID ?? knownFlows[streamID] else { | ||
| guard let flowID = knownFlowID ?? knownFlows[streamID.value] else { | ||
| frame.frame.finalize(success: true) | ||
| return true | ||
| } | ||
|
|
@@ -4678,7 +4681,7 @@ extension QUICConnection { | |
| } | ||
|
|
||
| // 2. Lookup stream | ||
| let knownFlowID = knownFlows[streamID] | ||
| let knownFlowID = knownFlows[streamID.value] | ||
|
|
||
| // 3. If new stream | ||
| if knownFlowID == nil { | ||
|
|
@@ -5218,7 +5221,7 @@ extension QUICConnection { | |
|
|
||
| log.debug("Updating flow \(flowID.debugDescription) for key \(streamID)") | ||
|
|
||
| knownFlows[streamID] = flowID | ||
| knownFlows[streamID.value] = flowID | ||
| if !stream.unidirectional { | ||
| self.bidirectionalStreams.incrementActiveStreams() | ||
| stream.receiveState.change(logIDString: stream.logPrefix, to: .receive) | ||
|
|
@@ -5425,7 +5428,7 @@ extension QUICConnection { | |
| let newFlowIdentifier = newStream.identifier | ||
| multiplexedFlows[newFlowIdentifier] = newStream | ||
|
|
||
| knownFlows[newStreamID] = newFlowIdentifier | ||
| knownFlows[newStreamID.value] = newFlowIdentifier | ||
| newStream.setup( | ||
| streamID: newStreamID, | ||
| logPrefixer: logPrefixer | ||
|
|
@@ -5755,7 +5758,7 @@ extension QUICConnection { | |
| } | ||
|
|
||
| func streamFromStreamID(_ streamID: QUICStreamID) -> QUICStreamInstance? { | ||
| let knownFlowID = knownFlows[streamID] | ||
| let knownFlowID = knownFlows[streamID.value] | ||
| guard let flowID = knownFlowID else { | ||
| return nil | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| //===----------------------------------------------------------------------===// | ||
| // | ||
| // This source file is part of the Swift open source project | ||
| // | ||
| // Copyright (c) 2026 Apple Inc. and the Swift project authors | ||
| // Licensed under Apache License v2.0 | ||
| // | ||
| // See LICENSE.txt for license information | ||
| // See CONTRIBUTORS.txt for the list of Swift project authors | ||
| // | ||
| // SPDX-License-Identifier: Apache-2.0 | ||
| // | ||
| //===----------------------------------------------------------------------===// | ||
|
|
||
| import XCTest | ||
| @_spi(Essentials) @_spi(ProtocolProvider) @testable import SwiftNetwork | ||
|
|
||
| @available(Network 0.1.0, *) | ||
| final class SwiftNetworkTransportParametersTests: XCTestCase { | ||
|
Contributor
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. Shouldn't this test be part of the QUICTests instead? |
||
| func testParameterTypesHaveContiguousIndices() { | ||
| // Each value should contribute a unique index, the order doesn't matter. Check | ||
| // against 'allCases' as that's guaranteed to have one unique index per element. | ||
| let indices = TransportParameterTypes.allCases.map { $0.index }.sorted() | ||
| XCTAssertEqual(Array(TransportParameterTypes.allCases.indices), indices) | ||
| } | ||
| } | ||
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.
I would love to get @lorentey's opinion on the use of this particular API since it's underscored...
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.
This is a great use of
_rawHashValue! Forwarding to UInt64's implementation still ensures strong hashing, and marking the case in the two lower bits helps reduce collisions in the original implementation. 👍