From 1992cfe7790da69b9d42e9e238ac49e92d73e8d6 Mon Sep 17 00:00:00 2001 From: Danny Canter Date: Wed, 18 Jun 2025 16:11:46 -0700 Subject: [PATCH] Containerization: Reduce allocations for image subsystems (#152) Continue the allocations journey for anything that is in the codepaths for pulling images. This time there's a couple spots in archive and ext4 we can get rid of some copies. --- Sources/ContainerizationArchive/Reader.swift | 8 ++++---- .../EXT4Reader+Export.swift | 4 ++-- .../Client/RegistryClient.swift | 19 +++++++++++++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Sources/ContainerizationArchive/Reader.swift b/Sources/ContainerizationArchive/Reader.swift index 25abc53d..bd016fcb 100644 --- a/Sources/ContainerizationArchive/Reader.swift +++ b/Sources/ContainerizationArchive/Reader.swift @@ -101,9 +101,9 @@ extension ArchiveReader: Sequence { internal func readDataForEntry(_ entry: WriteEntry) -> Data { let bufferSize = Int(Swift.min(entry.size ?? 4096, 4096)) - var data = Data() + var entry = Data() + var part = Data(count: bufferSize) while true { - var part = Data(count: bufferSize) let c = part.withUnsafeMutableBytes { buffer in guard let baseAddress = buffer.baseAddress else { return 0 @@ -112,9 +112,9 @@ extension ArchiveReader: Sequence { } guard c > 0 else { break } part.count = c - data.append(part) + entry.append(part) } - return data + return entry } } diff --git a/Sources/ContainerizationEXT4/EXT4Reader+Export.swift b/Sources/ContainerizationEXT4/EXT4Reader+Export.swift index 28b21518..649225dc 100644 --- a/Sources/ContainerizationEXT4/EXT4Reader+Export.swift +++ b/Sources/ContainerizationEXT4/EXT4Reader+Export.swift @@ -130,14 +130,14 @@ extension EXT4.EXT4Reader { entry.fileType = .symbolicLink if size < 60 { let linkBytes = EXT4.tupleToArray(inode.block) - entry.symlinkTarget = String(data: Data(linkBytes), encoding: .utf8) ?? "" + entry.symlinkTarget = String(bytes: linkBytes, encoding: .utf8) ?? "" } else { if let block = item.blocks { try self.seek(block: block.start) guard let linkBytes = try self.handle.read(upToCount: Int(size)) else { throw EXT4.Error.couldNotReadBlock(block.start) } - entry.symlinkTarget = String(data: Data(linkBytes), encoding: .utf8) ?? "" + entry.symlinkTarget = String(bytes: linkBytes, encoding: .utf8) ?? "" } } try writer.writeEntry(entry: entry, data: nil) diff --git a/Sources/ContainerizationOCI/Client/RegistryClient.swift b/Sources/ContainerizationOCI/Client/RegistryClient.swift index 679155cb..59dc436e 100644 --- a/Sources/ContainerizationOCI/Client/RegistryClient.swift +++ b/Sources/ContainerizationOCI/Client/RegistryClient.swift @@ -260,12 +260,27 @@ public final class RegistryClient: ContentClient { } } + internal func requestBuffer( + components: URLComponents, + headers: [(String, String)]? = nil + ) async throws -> ByteBuffer { + try await request(components: components, method: .GET, headers: headers) { response in + guard response.status == .ok else { + let url = components.url?.absoluteString ?? "unknown" + let reason = await ErrorResponse.fromResponseBody(response.body)?.jsonString + throw Error.invalidStatus(url: url, response.status, reason: reason) + } + + return try await response.body.collect(upTo: self.bufferSize) + } + } + internal func requestJSON( components: URLComponents, headers: [(String, String)]? = nil ) async throws -> T { - let data = try await self.requestData(components: components, headers: headers) - return try JSONDecoder().decode(T.self, from: data) + let buffer = try await self.requestBuffer(components: components, headers: headers) + return try JSONDecoder().decode(T.self, from: buffer) } /// A minimal endpoint, mounted at /v2/ will provide version support information based on its response statuses.