From c61955a870076665a5311c04d1014c723f0eb176 Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Sat, 28 Nov 2020 15:56:31 +0000 Subject: [PATCH] Use proper String unsafeUninitializedCapacity initializer. (#263) Motivation: A while ago we shimmed out the String unsafeUninitializedCapacity initializer because while it was on the road to being implemented, it hadn't actually been implemented. Now it has! On Apple platforms, the initializer is guarded behind an availability guard. We need to add that guard here. This means we need to keep hold of the backport for a good long time, until we raise our minimum Apple platform support to a new enough version. Happily, Linux on 5.3 should just see a net win here. Modifications: - Updated to enable us to use the proper initializer. Result: Fewer allocations when screwing around with HPACK. --- Sources/NIOHPACK/HuffmanCoding.swift | 35 +++++++++++++++++++++------- docker/docker-compose.1804.53.yaml | 20 ++++++++-------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/Sources/NIOHPACK/HuffmanCoding.swift b/Sources/NIOHPACK/HuffmanCoding.swift index dbac6e90..e209d4cc 100644 --- a/Sources/NIOHPACK/HuffmanCoding.swift +++ b/Sources/NIOHPACK/HuffmanCoding.swift @@ -187,7 +187,7 @@ extension ByteBuffer { // We have a rough heuristic here, which is that the maximal compression efficiency of the huffman table is 2x. let capacity = length * 2 - let decoded = try String(unsafeUninitializedCapacity: capacity) { (backingStorage, initializedCapacity) in + let decoded = try String(customUnsafeUninitializedCapacity: capacity) { backingStorage in var state: UInt8 = 0 var offset = 0 var acceptable = false @@ -220,7 +220,7 @@ extension ByteBuffer { throw HuffmanDecodeError.InvalidState() } - initializedCapacity = offset + return offset } @@ -241,23 +241,42 @@ extension ByteBuffer { } } - extension String { /// This is a backport of a proposed String initializer that will allow writing directly into an uninitialized String's backing memory. /// This feature will be useful when decoding Huffman-encoded HPACK strings. /// - /// As this API does not currently exist we fake it out by using a pointer and accepting the extra copy. - init(unsafeUninitializedCapacity capacity: Int, - initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer, _ initializedCount: inout Int) throws -> Void) rethrows { + /// As this API does not exist prior to 5.3 on Linux, or on older Apple platforms, we fake it out with a pointer and accept the extra copy. + init(backportUnsafeUninitializedCapacity capacity: Int, + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int) rethrows { let buffer = UnsafeMutableBufferPointer.allocate(capacity: capacity) defer { buffer.deallocate() } - var initializedCount = 0 - try initializer(buffer, &initializedCount) + + let initializedCount = try initializer(buffer) precondition(initializedCount <= capacity, "Overran buffer in initializer!") self = String(decoding: UnsafeMutableBufferPointer(start: buffer.baseAddress!, count: initializedCount), as: UTF8.self) } } + +#if compiler(>=5.3) +extension String { + init(customUnsafeUninitializedCapacity capacity: Int, + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int) rethrows { + if #available(macOS 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) { + try self.init(unsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) + } else { + try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) + } + } +} +#else +extension String { + init(customUnsafeUninitializedCapacity capacity: Int, + initializingUTF8With initializer: (_ buffer: UnsafeMutableBufferPointer) throws -> Int) rethrows { + try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer) + } +} +#endif diff --git a/docker/docker-compose.1804.53.yaml b/docker/docker-compose.1804.53.yaml index 1453b8eb..fe9a4453 100644 --- a/docker/docker-compose.1804.53.yaml +++ b/docker/docker-compose.1804.53.yaml @@ -18,11 +18,11 @@ services: environment: - MAX_ALLOCS_ALLOWED_create_client_stream_channel=63000 - MAX_ALLOCS_ALLOWED_hpack_decoding=5050 - - MAX_ALLOCS_ALLOWED_client_server_request_response=320000 - - MAX_ALLOCS_ALLOWED_client_server_h1_request_response=367000 - - MAX_ALLOCS_ALLOWED_1k_requests_interleaved=61000 - - MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=60000 - - MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=434000 + - MAX_ALLOCS_ALLOWED_client_server_request_response=319000 + - MAX_ALLOCS_ALLOWED_client_server_h1_request_response=366000 + - MAX_ALLOCS_ALLOWED_1k_requests_interleaved=59000 + - MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=58000 + - MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=414000 performance-test: image: swift-nio-http2:18.04-5.3 @@ -35,11 +35,11 @@ services: environment: - MAX_ALLOCS_ALLOWED_create_client_stream_channel=63000 - MAX_ALLOCS_ALLOWED_hpack_decoding=5050 - - MAX_ALLOCS_ALLOWED_client_server_request_response=320000 - - MAX_ALLOCS_ALLOWED_client_server_h1_request_response=367000 - - MAX_ALLOCS_ALLOWED_1k_requests_interleaved=61000 - - MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=60000 - - MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=434000 + - MAX_ALLOCS_ALLOWED_client_server_request_response=319000 + - MAX_ALLOCS_ALLOWED_client_server_h1_request_response=366000 + - MAX_ALLOCS_ALLOWED_1k_requests_interleaved=59000 + - MAX_ALLOCS_ALLOWED_1k_requests_noninterleaved=58000 + - MAX_ALLOCS_ALLOWED_stream_teardown_100_concurrent=414000 shell: image: swift-nio-http2:18.04-5.3