From 2ac05c6679291a7a012667abded93c4e3de02159 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Sun, 23 Nov 2025 16:02:03 +0100 Subject: [PATCH 1/2] Reduce CPU usage during downloads --- Sources/Hub/Downloader.swift | 37 ++++++++++++++++++++++++++---------- 1 file changed, 27 insertions(+), 10 deletions(-) diff --git a/Sources/Hub/Downloader.swift b/Sources/Hub/Downloader.swift index 173c2dca..4251f29b 100644 --- a/Sources/Hub/Downloader.swift +++ b/Sources/Hub/Downloader.swift @@ -263,16 +263,31 @@ final class Downloader: NSObject, Sendable, ObservableObject { var newNumRetries = numRetries do { + // Batch collect bytes to reduce Data.append() overhead + // Use ContiguousArray for better performance (no NSArray bridging overhead) + // Larger batch size = fewer Data.append calls + let batchSize = 16384 // 16KB batches + var byteBatch = ContiguousArray() + byteBatch.reserveCapacity(batchSize) + for try await byte in asyncBytes { - buffer.append(byte) + byteBatch.append(byte) + + // When we've collected a batch, append to main buffer + if byteBatch.count >= batchSize { + buffer.append(contentsOf: byteBatch) + byteBatch.removeAll(keepingCapacity: true) + } + // When buffer is full, write to disk - if buffer.count == chunkSize { + if buffer.count >= chunkSize { if !buffer.isEmpty { // Filter out keep-alive chunks try tempFile.write(contentsOf: buffer) + let bytesWritten = buffer.count buffer.removeAll(keepingCapacity: true) - totalDownloadedLocal += chunkSize - await downloadResumeState.incDownloadedSize(chunkSize) + totalDownloadedLocal += bytesWritten + await downloadResumeState.incDownloadedSize(bytesWritten) newNumRetries = 5 guard let expectedSize = await downloadResumeState.expectedSize else { continue } let progress = expectedSize != 0 ? Double(totalDownloadedLocal) / Double(expectedSize) : 0 @@ -290,6 +305,11 @@ final class Downloader: NSObject, Sendable, ObservableObject { } } + // Flush remaining bytes from batch + if !byteBatch.isEmpty { + buffer.append(contentsOf: byteBatch) + } + if !buffer.isEmpty { try tempFile.write(contentsOf: buffer) totalDownloadedLocal += buffer.count @@ -446,12 +466,9 @@ actor Broadcaster { func broadcast(state: E) async { latestState = state - await withTaskGroup(of: Void.self) { group in - for continuation in continuations.values { - group.addTask { - continuation.yield(state) - } - } + // continuation.yield() is already async-safe, no need to wrap in Tasks + for continuation in continuations.values { + continuation.yield(state) } } } From f796af9b962629a77b28d02d17149a0be42273c6 Mon Sep 17 00:00:00 2001 From: Anthony DePasquale Date: Mon, 24 Nov 2025 13:33:27 +0100 Subject: [PATCH 2/2] Clean up comments --- Sources/Hub/Downloader.swift | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/Hub/Downloader.swift b/Sources/Hub/Downloader.swift index 4251f29b..65182999 100644 --- a/Sources/Hub/Downloader.swift +++ b/Sources/Hub/Downloader.swift @@ -265,15 +265,14 @@ final class Downloader: NSObject, Sendable, ObservableObject { do { // Batch collect bytes to reduce Data.append() overhead // Use ContiguousArray for better performance (no NSArray bridging overhead) - // Larger batch size = fewer Data.append calls - let batchSize = 16384 // 16KB batches + let batchSize = 16384 // 16 kB var byteBatch = ContiguousArray() byteBatch.reserveCapacity(batchSize) for try await byte in asyncBytes { byteBatch.append(byte) - // When we've collected a batch, append to main buffer + // Append batch to main buffer if byteBatch.count >= batchSize { buffer.append(contentsOf: byteBatch) byteBatch.removeAll(keepingCapacity: true) @@ -466,7 +465,6 @@ actor Broadcaster { func broadcast(state: E) async { latestState = state - // continuation.yield() is already async-safe, no need to wrap in Tasks for continuation in continuations.values { continuation.yield(state) }