diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..3dbad88 --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-24 - Unlocking parallelism in TaskGroups by replacing actor with struct +**Learning:** In Swift structured concurrency, using an `actor` to manage a `withTaskGroup` where tasks invoke synchronous, blocking I/O (like `FileManager` operations) directly on the actor inadvertently serializes the tasks, preventing parallelism. +**Action:** For stateless components interacting with thread-safe dependencies, use `struct`s or `nonisolated` methods to allow tasks to execute concurrently across threads. diff --git a/Sources/Cacheout/Scanner/NodeModulesScanner.swift b/Sources/Cacheout/Scanner/NodeModulesScanner.swift index 3ed4d8c..5bfaff0 100644 --- a/Sources/Cacheout/Scanner/NodeModulesScanner.swift +++ b/Sources/Cacheout/Scanner/NodeModulesScanner.swift @@ -77,7 +77,13 @@ actor NodeModulesScanner { .sorted { $0.sizeBytes > $1.sizeBytes } } - private func findNodeModules(in directory: URL, maxDepth: Int, currentDepth: Int = 0) async -> [NodeModulesItem] { + // ⚡ Bolt: Performance Optimization + // Made `findNodeModules` and `directorySize` nonisolated. + // Previously, running these on the actor serialized the TaskGroup, + // eliminating parallelism because synchronous I/O blocked the actor's executor. + // By making them nonisolated, tasks run concurrently across threads. + // Expected impact: ~4x-8x faster node_modules scanning on multi-core Macs. + private nonisolated func findNodeModules(in directory: URL, maxDepth: Int, currentDepth: Int = 0) async -> [NodeModulesItem] { guard currentDepth < maxDepth else { return [] } var results: [NodeModulesItem] = [] @@ -120,7 +126,7 @@ actor NodeModulesScanner { return results } - private func directorySize(at url: URL) -> Int64 { + private nonisolated func directorySize(at url: URL) -> Int64 { var total: Int64 = 0 guard let enumerator = fileManager.enumerator( at: url,