From bf41ad1595fa273de1f70ee8e3f51948fd47266d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mirza=20U=C4=8Danbarli=C4=87?= <56406159+supersonicbyte@users.noreply.github.com> Date: Mon, 20 Jan 2025 17:39:21 +0100 Subject: [PATCH] Improve HTTPHeaders description performance (#3063) ### Motivation: As outlined in this [issue](https://github.com/apple/swift-nio/issues/2930) by @weissi, the current performance of calling `description` on `HTTPHeaders` is undermined by the dynamism of Array. ### Modifications: The proposed solution replaces the implementation of `description` to iterate over the items and print them out manually. This provides a faster solution since we bypass the cost of calling into `description` of an Array. ### Result: A more performant implementation of `HTTPHeaders` description. --------- Co-authored-by: Johannes Weiss Co-authored-by: Cory Benfield --- .../tests_01_http/test_07_headers_work.sh | 4 ++-- Sources/NIOHTTP1/HTTPTypes.swift | 5 ++++- Sources/NIOPerformanceTester/main.swift | 11 ++++++++++ Tests/NIOHTTP1Tests/HTTPHeadersTest.swift | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/IntegrationTests/tests_01_http/test_07_headers_work.sh b/IntegrationTests/tests_01_http/test_07_headers_work.sh index 6e3dc3365b..6389929d92 100644 --- a/IntegrationTests/tests_01_http/test_07_headers_work.sh +++ b/IntegrationTests/tests_01_http/test_07_headers_work.sh @@ -19,8 +19,8 @@ source defines.sh token=$(create_token) start_server "$token" do_curl "$token" -H "foo: bar" --http1.0 \ - "http://foobar.com/dynamic/info" > "${tmp:?"tmp variable not set"}/out" -if ! grep -q '("foo", "bar")' "$tmp/out"; then + "http://foobar.com/dynamic/info" >"${tmp:?"tmp variable not set"}/out" +if ! grep -q 'foo: bar' "$tmp/out"; then fail "couldn't find header in response" fi stop_server "$token" diff --git a/Sources/NIOHTTP1/HTTPTypes.swift b/Sources/NIOHTTP1/HTTPTypes.swift index 321b494d6d..92579c9ec7 100644 --- a/Sources/NIOHTTP1/HTTPTypes.swift +++ b/Sources/NIOHTTP1/HTTPTypes.swift @@ -313,7 +313,10 @@ public struct HTTPHeaders: CustomStringConvertible, ExpressibleByDictionaryLiter internal var keepAliveState: KeepAliveState = .unknown public var description: String { - self.headers.description + self.headers.lazy.map { + "\($0.0): \($0.1)" + } + .joined(separator: "; ") } internal var names: [String] { diff --git a/Sources/NIOPerformanceTester/main.swift b/Sources/NIOPerformanceTester/main.swift index 13fc75222e..48b84d3523 100644 --- a/Sources/NIOPerformanceTester/main.swift +++ b/Sources/NIOPerformanceTester/main.swift @@ -276,6 +276,17 @@ measureAndPrint(desc: "http_headers_canonical_form_trimming_whitespace_from_long return count } +measureAndPrint(desc: "http_headers_description_100k") { + let headers = HTTPHeaders(Array(repeating: ("String", "String"), count: 100)) + + for _ in 0..<100_000 { + let str = headers.description + precondition(str.utf8.count > 100) + } + + return 0 +} + measureAndPrint(desc: "bytebuffer_write_12MB_short_string_literals") { let bufferSize = 12 * 1024 * 1024 var buffer = ByteBufferAllocator().buffer(capacity: bufferSize) diff --git a/Tests/NIOHTTP1Tests/HTTPHeadersTest.swift b/Tests/NIOHTTP1Tests/HTTPHeadersTest.swift index bd424f77dc..4971af97a8 100644 --- a/Tests/NIOHTTP1Tests/HTTPHeadersTest.swift +++ b/Tests/NIOHTTP1Tests/HTTPHeadersTest.swift @@ -400,4 +400,26 @@ class HTTPHeadersTest: XCTestCase { headers.reserveCapacity(4) XCTAssertEqual(headers.capacity, 4) } + + func testHTTPHeadersDescription() { + let originalHeaders = [ + ("User-Agent", "1"), + ("host", "2"), + ("X-SOMETHING", "3"), + ("SET-COOKIE", "foo=bar"), + ("Set-Cookie", "buz=cux"), + ] + + let headers = HTTPHeaders(originalHeaders) + + let expectedOutput = """ + User-Agent: 1; \ + host: 2; \ + X-SOMETHING: 3; \ + SET-COOKIE: foo=bar; \ + Set-Cookie: buz=cux + """ + + XCTAssertEqual(expectedOutput, headers.description) + } }