From 2d2eb2942b7b2f557b8bd67a2ebfb3ea1427c47d Mon Sep 17 00:00:00 2001 From: Guoye Zhang Date: Tue, 14 Nov 2023 16:44:03 -0800 Subject: [PATCH 1/2] Eliminate the allocation of single element arrays --- Sources/HTTPTypes/HTTPFields.swift | 93 +++++++++++++++++++----------- 1 file changed, 59 insertions(+), 34 deletions(-) diff --git a/Sources/HTTPTypes/HTTPFields.swift b/Sources/HTTPTypes/HTTPFields.swift index a714d22..8255c4c 100644 --- a/Sources/HTTPTypes/HTTPFields.swift +++ b/Sources/HTTPTypes/HTTPFields.swift @@ -172,10 +172,10 @@ public struct HTTPFields: Sendable, Hashable { /// semicolon. public subscript(name: HTTPField.Name) -> String? { get { - let values = self[values: name] - if !values.isEmpty { + let values = self.fields(for: name) + if values.first(where: { _ in true }) != nil { let separator = name == .cookie ? "; " : ", " - return values.joined(separator: separator) + return values.lazy.map(\.value).joined(separator: separator) } else { return nil } @@ -184,14 +184,14 @@ public struct HTTPFields: Sendable, Hashable { if let newValue { if #available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *), name == .cookie { - self[fields: name] = newValue.split(separator: "; ", omittingEmptySubsequences: false).map { + self.setFields(newValue.split(separator: "; ", omittingEmptySubsequences: false).lazy.map { HTTPField(name: name, value: String($0)) - } + }, for: name) } else { - self[values: name] = [newValue] + self.setFields(CollectionOfOne(HTTPField(name: name, value: newValue)), for: name) } } else { - self[values: name] = [] + self.setFields(EmptyCollection(), for: name) } } } @@ -199,47 +199,72 @@ public struct HTTPFields: Sendable, Hashable { /// Access the field values by name as an array of strings. The order of fields is preserved. public subscript(values name: HTTPField.Name) -> [String] { get { - self[fields: name].map(\.value) + self.fields(for: name).map(\.value) } set { - self[fields: name] = newValue.map { HTTPField(name: name, value: $0) } + self.setFields(newValue.lazy.map { HTTPField(name: name, value: $0) }, for: name) } } /// Access the fields by name as an array. The order of fields is preserved. public subscript(fields name: HTTPField.Name) -> [HTTPField] { get { - var fields = [HTTPField]() - var index = self._storage.ensureIndex[name.canonicalName]?.first ?? .max - while index != .max { - let (field, next) = self._storage.fields[Int(index)] - fields.append(field) - index = next - } - return fields + Array(self.fields(for: name)) } set { - if !isKnownUniquelyReferenced(&self._storage) { - self._storage = self._storage.copy() - } - var existingIndex = self._storage.ensureIndex[name.canonicalName]?.first ?? .max - var newFieldIterator = newValue.makeIterator() - var toDelete = [Int]() - while existingIndex != .max { - if let field = newFieldIterator.next() { - self._storage.fields[Int(existingIndex)].field = field - } else { - toDelete.append(Int(existingIndex)) + self.setFields(newValue, for: name) + } + } + + private func fields(for name: HTTPField.Name) -> some Sequence { + struct HTTPFieldSequence: Sequence { + let fields: [(field: HTTPField, next: UInt16)] + let index: UInt16 + + struct Iterator: IteratorProtocol { + let fields: [(field: HTTPField, next: UInt16)] + var index: UInt16 + + mutating func next() -> HTTPField? { + if self.index == .max { + return nil + } + let (field, next) = self.fields[Int(self.index)] + self.index = next + return field } - existingIndex = self._storage.fields[Int(existingIndex)].next } - if !toDelete.isEmpty { - self._storage.fields.remove(at: toDelete) - self._storage.index = nil + + func makeIterator() -> Iterator { + Iterator(fields: self.fields, index: self.index) } - while let field = newFieldIterator.next() { - self._storage.append(field: field) + } + + let index = self._storage.ensureIndex[name.canonicalName]?.first ?? .max + return HTTPFieldSequence(fields: self._storage.fields, index: index) + } + + private mutating func setFields(_ fieldSequence: some Sequence, for name: HTTPField.Name) { + if !isKnownUniquelyReferenced(&self._storage) { + self._storage = self._storage.copy() + } + var existingIndex = self._storage.ensureIndex[name.canonicalName]?.first ?? .max + var newFieldIterator = fieldSequence.makeIterator() + var toDelete = [Int]() + while existingIndex != .max { + if let field = newFieldIterator.next() { + self._storage.fields[Int(existingIndex)].field = field + } else { + toDelete.append(Int(existingIndex)) } + existingIndex = self._storage.fields[Int(existingIndex)].next + } + if !toDelete.isEmpty { + self._storage.fields.remove(at: toDelete) + self._storage.index = nil + } + while let field = newFieldIterator.next() { + self._storage.append(field: field) } } From e716d5d2bc2cc754e11560489278c917315f5f03 Mon Sep 17 00:00:00 2001 From: Guoye Zhang Date: Wed, 15 Nov 2023 16:56:35 -0800 Subject: [PATCH 2/2] Values -> fields --- Sources/HTTPTypes/HTTPFields.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/HTTPTypes/HTTPFields.swift b/Sources/HTTPTypes/HTTPFields.swift index 8255c4c..43b8282 100644 --- a/Sources/HTTPTypes/HTTPFields.swift +++ b/Sources/HTTPTypes/HTTPFields.swift @@ -172,10 +172,10 @@ public struct HTTPFields: Sendable, Hashable { /// semicolon. public subscript(name: HTTPField.Name) -> String? { get { - let values = self.fields(for: name) - if values.first(where: { _ in true }) != nil { + let fields = self.fields(for: name) + if fields.first(where: { _ in true }) != nil { let separator = name == .cookie ? "; " : ", " - return values.lazy.map(\.value).joined(separator: separator) + return fields.lazy.map(\.value).joined(separator: separator) } else { return nil }