Skip to content

Commit ab04d05

Browse files
authored
Reduce allocs in HTTP2StreamChannel (#449)
Motivation: The HTTP2StreamChannel stores two atomic booleans for whether the channel is active and writable. These are only accessed externally and access is relatively infrequent. Each atomic requires an allocation, instead we can just protect each with a single lock. Modifications: - Combine the two atomics into a single 'flags' struct protected by a locked value box Result: Fewer allocations
1 parent a2b4d28 commit ab04d05

File tree

5 files changed

+89
-79
lines changed

5 files changed

+89
-79
lines changed

Sources/NIOHTTP2/HTTP2StreamChannel.swift

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -169,8 +169,7 @@ final class HTTP2StreamChannel: Channel, ChannelCore, @unchecked Sendable {
169169
self.streamID = streamID
170170
self.multiplexer = multiplexer
171171
self.windowManager = InboundWindowManager(targetSize: Int32(targetWindowSize))
172-
self._isActiveAtomic = .init(false)
173-
self._isWritable = .init(true)
172+
self.flags = NIOLockedValueBox(Flags(isActive: false, isWritable: true))
174173
self.state = .idle
175174
self.streamDataType = streamDataType
176175
self.writabilityManager = StreamChannelFlowController(highWatermark: outboundBytesHighWatermark,
@@ -334,10 +333,18 @@ final class HTTP2StreamChannel: Channel, ChannelCore, @unchecked Sendable {
334333
}
335334
self.modifyingState { $0.networkActive() }
336335

337-
if self.writabilityManager.isWritable != self._isWritable.load(ordering: .relaxed) {
338-
// We have probably delayed telling the user that this channel isn't writable, but we should do
339-
// it now.
340-
self._isWritable.store(self.writabilityManager.isWritable, ordering: .relaxed)
336+
let writabilityChanged = self.flags.withLockedValue {
337+
if self.writabilityManager.isWritable != $0.isWritable {
338+
$0.isWritable.toggle()
339+
return true
340+
} else {
341+
return false
342+
}
343+
}
344+
345+
// We have probably delayed telling the user that this channel isn't writable, but we should do
346+
// it now.
347+
if writabilityChanged {
341348
self.pipeline.fireChannelWritabilityChanged()
342349
}
343350

@@ -433,22 +440,25 @@ final class HTTP2StreamChannel: Channel, ChannelCore, @unchecked Sendable {
433440
}
434441
}
435442

436-
public var isWritable: Bool {
437-
return self._isWritable.load(ordering: .relaxed)
443+
private struct Flags {
444+
var isActive: Bool
445+
var isWritable: Bool
438446
}
439447

440-
private let _isWritable: ManagedAtomic<Bool>
448+
private let flags: NIOLockedValueBox<Flags>
449+
450+
public var isWritable: Bool {
451+
self.flags.withLockedValue { $0.isWritable }
452+
}
441453

442454
private var _isActive: Bool {
443455
return self.state == .active || self.state == .closing || self.state == .localActive
444456
}
445457

446458
public var isActive: Bool {
447-
return self._isActiveAtomic.load(ordering: .relaxed)
459+
self.flags.withLockedValue { $0.isActive }
448460
}
449461

450-
private let _isActiveAtomic: ManagedAtomic<Bool>
451-
452462
public var _channelCore: ChannelCore {
453463
return self
454464
}
@@ -709,7 +719,7 @@ final class HTTP2StreamChannel: Channel, ChannelCore, @unchecked Sendable {
709719
}
710720

711721
private func changeWritability(to newWritability: Bool) {
712-
self._isWritable.store(newWritability, ordering: .relaxed)
722+
self.flags.withLockedValue { $0.isWritable = newWritability }
713723
self.pipeline.fireChannelWritabilityChanged()
714724
}
715725

@@ -915,7 +925,7 @@ internal extension HTTP2StreamChannel {
915925
// Do nothing here.
916926
return
917927
case .remoteActive, .active, .closing, .closingNeverActivated, .closed:
918-
self._isWritable.store(localValue, ordering: .relaxed)
928+
self.flags.withLockedValue { $0.isWritable = localValue }
919929
self.pipeline.fireChannelWritabilityChanged()
920930
}
921931
}
@@ -930,7 +940,7 @@ extension HTTP2StreamChannel {
930940
// A helper function used to ensure that state modification leads to changes in the channel active atomic.
931941
private func modifyingState<ReturnType>(_ closure: (inout StreamChannelState) throws -> ReturnType) rethrows -> ReturnType {
932942
defer {
933-
self._isActiveAtomic.store(self._isActive, ordering: .relaxed)
943+
self.flags.withLockedValue { $0.isActive = self._isActive }
934944
}
935945
return try closure(&self.state)
936946
}

docker/docker-compose.2204.510.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,27 @@ services:
2828
test:
2929
image: swift-nio-http2:22.04-5.10
3030
environment:
31-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=31150
32-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=30100
33-
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=37150
34-
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=36100
35-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=280050
36-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=265050
37-
- MAX_ALLOCS_ALLOWED_client_server_request_response=249050
38-
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=240050
39-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1194050
40-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=885050
41-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
42-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
43-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=37050
44-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=37050
31+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=30150
32+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=29100
33+
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=36150
34+
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=35100
35+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=278050
36+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=263050
37+
- MAX_ALLOCS_ALLOWED_client_server_request_response=247050
38+
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=238050
39+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1192050
40+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=883050
41+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=36050
42+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=36050
43+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=36050
44+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=36050
4545
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
4646
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace=200050
4747
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_long_string=300050
4848
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_short_string=200050
4949
- MAX_ALLOCS_ALLOWED_hpack_decoding=5050
50-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=272650
51-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=271750
50+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=262650
51+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=261750
5252
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error
5353

5454
shell:

docker/docker-compose.2204.58.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,27 @@ services:
2828
test:
2929
image: swift-nio-http2:22.04-5.8
3030
environment:
31-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=31150
32-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=30100
33-
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=37150
34-
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=36100
35-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=280050
36-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=265050
37-
- MAX_ALLOCS_ALLOWED_client_server_request_response=249050
38-
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=240050
39-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1194050
40-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=885050
41-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
42-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
43-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=37050
44-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=37050
31+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=30150
32+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=29100
33+
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=36150
34+
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=35100
35+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=278050
36+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=263050
37+
- MAX_ALLOCS_ALLOWED_client_server_request_response=247050
38+
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=238050
39+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1192050
40+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=883050
41+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=36050
42+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=36050
43+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=36050
44+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=36050
4545
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
4646
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace=200050
4747
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_long_string=300050
4848
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_short_string=200050
4949
- MAX_ALLOCS_ALLOWED_hpack_decoding=5050
50-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=272650
51-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=271750
50+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=262650
51+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=261750
5252
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error
5353

5454
shell:

docker/docker-compose.2204.59.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,27 +28,27 @@ services:
2828
test:
2929
image: swift-nio-http2:22.04-5.9
3030
environment:
31-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=31150
32-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=30100
33-
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=37150
34-
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=36100
35-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=280050
36-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=265050
37-
- MAX_ALLOCS_ALLOWED_client_server_request_response=249050
38-
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=240050
39-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1194050
40-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=885050
41-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
42-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
43-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=37050
44-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=37050
31+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=30150
32+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=29100
33+
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=36150
34+
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=35100
35+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=278050
36+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=263050
37+
- MAX_ALLOCS_ALLOWED_client_server_request_response=247050
38+
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=238050
39+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1192050
40+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=883050
41+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=36050
42+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=36050
43+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=36050
44+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=36050
4545
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
4646
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace=200050
4747
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_long_string=300050
4848
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_short_string=200050
4949
- MAX_ALLOCS_ALLOWED_hpack_decoding=5050
50-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=272650
51-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=271750
50+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=262650
51+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=261750
5252
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error
5353

5454
shell:

docker/docker-compose.2204.main.yaml

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,27 @@ services:
2727
test:
2828
image: swift-nio-http2:22.04-main
2929
environment:
30-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=31150
31-
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=30100
32-
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=37150
33-
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=36100
34-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=280050
35-
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=265050
36-
- MAX_ALLOCS_ALLOWED_client_server_request_response=249050
37-
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=240050
38-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1194050
39-
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=885050
40-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=37050
41-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=37050
42-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=37050
43-
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=37050
30+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_interleaved=30150
31+
- MAX_ALLOCS_ALLOWED_1k_requests_inline_noninterleaved=29100
32+
- MAX_ALLOCS_ALLOWED_1k_requests_interleaved=36150
33+
- MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=35100
34+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response=278050
35+
- MAX_ALLOCS_ALLOWED_client_server_h1_request_response_inline=263050
36+
- MAX_ALLOCS_ALLOWED_client_server_request_response=247050
37+
- MAX_ALLOCS_ALLOWED_client_server_request_response_inline=238050
38+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many=1192050
39+
- MAX_ALLOCS_ALLOWED_client_server_request_response_many_inline=883050
40+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel=36050
41+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline=36050
42+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_inline_no_promise_based_API=36050
43+
- MAX_ALLOCS_ALLOWED_create_client_stream_channel_no_promise_based_API=36050
4444
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form=200050
4545
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace=200050
4646
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_long_string=300050
4747
- MAX_ALLOCS_ALLOWED_get_100000_headers_canonical_form_trimming_whitespace_from_short_string=200050
4848
- MAX_ALLOCS_ALLOWED_hpack_decoding=5050
49-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=272650
50-
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=271750
49+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=262650
50+
- MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent_inline=261750
5151
- IMPORT_CHECK_ARG=--explicit-target-dependency-import-check error
5252

5353
shell:

0 commit comments

Comments
 (0)