diff --git a/Sources/Hub/Downloader.swift b/Sources/Hub/Downloader.swift index 173c2dca..65182999 100644 --- a/Sources/Hub/Downloader.swift +++ b/Sources/Hub/Downloader.swift @@ -263,16 +263,30 @@ 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) + let batchSize = 16384 // 16 kB + var byteBatch = ContiguousArray() + byteBatch.reserveCapacity(batchSize) + for try await byte in asyncBytes { - buffer.append(byte) + byteBatch.append(byte) + + // Append batch 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 +304,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 +465,8 @@ 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) - } - } + for continuation in continuations.values { + continuation.yield(state) } } }