From d410ae7001042701a132026a4b2219268dcfca96 Mon Sep 17 00:00:00 2001 From: yoshikazu Date: Mon, 27 Jul 2026 15:38:17 +0000 Subject: [PATCH] Add concurrency helper --- Sources/Photochk/Core/Concurrency.swift | 42 +++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 Sources/Photochk/Core/Concurrency.swift diff --git a/Sources/Photochk/Core/Concurrency.swift b/Sources/Photochk/Core/Concurrency.swift new file mode 100644 index 0000000..b282689 --- /dev/null +++ b/Sources/Photochk/Core/Concurrency.swift @@ -0,0 +1,42 @@ +import Foundation + +/// 同時実行数を抑えつつ並列に変換し、入力と同じ順序で結果を返す。 +/// +/// 画像デコードや Vision の推論は CPU を使い切るので、無制限に投げるより +/// コア数程度に抑えたほうが速く、メモリも安定する。 +func mapConcurrently( + _ items: [Item], + limit: Int = max(2, ProcessInfo.processInfo.activeProcessorCount), + onProgress: (@Sendable (Int) -> Void)? = nil, + _ transform: @escaping @Sendable (Item) async -> Output +) async throws -> [Output] { + guard !items.isEmpty else { return [] } + + var results = [Output?](repeating: nil, count: items.count) + + try await withThrowingTaskGroup(of: (Int, Output).self) { group in + var next = 0 + let initial = min(limit, items.count) + while next < initial { + let index = next + group.addTask { (index, await transform(items[index])) } + next += 1 + } + + var finished = 0 + while let (index, value) = try await group.next() { + results[index] = value + finished += 1 + onProgress?(finished) + try Task.checkCancellation() + + if next < items.count { + let index = next + group.addTask { (index, await transform(items[index])) } + next += 1 + } + } + } + + return results.compactMap { $0 } +}