Skip to content

Commit

Permalink
Use proper String unsafeUninitializedCapacity initializer. (#263)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
Lukasa authored Nov 28, 2020
1 parent 57976bc commit c61955a
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 18 deletions.
35 changes: 27 additions & 8 deletions Sources/NIOHPACK/HuffmanCoding.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -220,7 +220,7 @@ extension ByteBuffer {
throw HuffmanDecodeError.InvalidState()
}

initializedCapacity = offset
return offset
}


Expand All @@ -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<UInt8>, _ 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<UInt8>) throws -> Int) rethrows {
let buffer = UnsafeMutableBufferPointer<UInt8>.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<UInt8>) 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<UInt8>) throws -> Int) rethrows {
try self.init(backportUnsafeUninitializedCapacity: capacity, initializingUTF8With: initializer)
}
}
#endif
20 changes: 10 additions & 10 deletions docker/docker-compose.1804.53.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

0 comments on commit c61955a

Please sign in to comment.