Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .jules/bolt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 2026-04-05 - Optimize ProcessMemoryScanner concurrency with sliding window
**Learning:** In Swift structured concurrency, when processing high-volume tasks using `withTaskGroup`, static chunking limits throughput due to tail latency (waiting for the slowest task in a chunk). A sliding window with an iterator maintains maximum concurrent execution limits continuously.
**Action:** Always use an iterator-based sliding window in `withTaskGroup` instead of static chunking for processing large collections to avoid tail latency bottlenecks.
38 changes: 22 additions & 16 deletions Sources/Cacheout/Memory/ProcessMemoryScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,29 +97,35 @@ actor ProcessMemoryScanner {
///
/// Returns the collected entries and the count of EPERM failures.
private func scanPIDs(_ pids: [pid_t]) async -> (entries: [ProcessEntryDTO], epermCount: Int) {
// Chunk PIDs to cap concurrency at maxConcurrency.
let chunks = stride(from: 0, to: pids.count, by: maxConcurrency).map {
Array(pids[$0..<min($0 + maxConcurrency, pids.count)])
}

var allEntries: [ProcessEntryDTO] = []
var totalEperm = 0

for chunk in chunks {
await withTaskGroup(of: ScanPIDResult.self) { group in
for pid in chunk {
// Use a sliding window approach for maximum continuous concurrency.
// This avoids tail-latency waiting that occurs with static chunking.
await withTaskGroup(of: ScanPIDResult.self) { group in
var iterator = pids.makeIterator()

for _ in 0..<maxConcurrency {
if let pid = iterator.next() {
group.addTask { [self] in
self.scanSinglePID(pid)
}
}
for await result in group {
switch result {
case .success(let entry):
allEntries.append(entry)
case .eperm:
totalEperm += 1
case .otherError:
break
}

for await result in group {
switch result {
case .success(let entry):
allEntries.append(entry)
case .eperm:
totalEperm += 1
case .otherError:
break
}

if let nextPid = iterator.next() {
group.addTask { [self] in
self.scanSinglePID(nextPid)
}
}
}
Expand Down