Skip to content

Commit 0340bb1

Browse files
authored
Retrieve resolved package versions in parallel (#8203)
Retrieve resolved package versions in parallel to improve package resolve performance ### Motivation: When running `swift package resolve`, resolved package versions are retrieved sequentially. This is not a big problem when using source control resolution as in that case, the deep clones are [done in parallel](https://github.com/swiftlang/swift-package-manager/blob/0401a2ae55077cfd1f4c0acd43ae0a1a56ab21ef/Sources/Workspace/Workspace%2BDependencies.swift#L367) when SwiftPM tries to resolve the required versions. However, when resolving packages using registry, the download of the source archive happens after the required version is resolved. That happens sequentially: https://github.com/swiftlang/swift-package-manager/blob/0401a2ae55077cfd1f4c0acd43ae0a1a56ab21ef/Sources/Workspace/Workspace%2BDependencies.swift#L422 ### Modifications: Similarly to [resolving package containers](https://github.com/swiftlang/swift-package-manager/blob/0401a2ae55077cfd1f4c0acd43ae0a1a56ab21ef/Sources/Workspace/Workspace%2BDependencies.swift#L367), I updated the retrieval of resolved package versions to be executed in parallel as well. ### Result: Below, you can find the found performance improvements for registry-driven resolution based on my (admittedly limited) performance measurements. For testing, I used the following `Package.swift`: ```swift // swift-tools-version: 6.0 import PackageDescription let package = Package( name: "Library", dependencies: [ .package(url: "https://github.com/pointfreeco/swift-composable-architecture", exact: "1.15.1"), .package(url: "https://github.com/apple/swift-syntax.git", exact: "509.0.1"), .package(url: "https://github.com/apple/swift-algorithms.git", exact: "1.2.0"), .package(url: "https://github.com/apple/swift-numerics.git", exact: "1.0.2"), .package(url: "https://github.com/apple/swift-async-algorithms.git", exact: "1.0.1"), ] ) ``` #### Without changes from this PR ``` hyperfine --prepare 'rm -rf ~/.swiftpm .build && swift package purge-cache' --runs 5 --warmup 1 'swift package --replace-scm-with-registry resolve' Benchmark 1: swift package --replace-scm-with-registry resolve Time (mean ± σ): 27.671 s ± 4.137 s [User: 8.897 s, System: 3.273 s] Range (min … max): 24.793 s … 34.946 s 5 runs ``` #### With changes from this PR ``` hyperfine --prepare 'rm -rf ~/.swiftpm .build && swift package purge-cache' --runs 5 --warmup 1 '/Users/marekfort/Developer/swift-package-manager/.build/release/swift-package --replace-scm-with-registry resolve' Benchmark 1: /Users/marekfort/Developer/swift-package-manager/.build/release/swift-package --replace-scm-with-registry resolve Time (mean ± σ): 11.927 s ± 0.223 s [User: 8.832 s, System: 3.411 s] Range (min … max): 11.705 s … 12.232 s 5 runs ``` The improvements of the mean resolve time are over 60 %! If you suspect I tested anything wrong, please, let me know. I also tested source control-based resolution, there the difference is less start as the deep clones are already happening in parallel on `main`. Given the small differences, they very well might be based on inconsistent network performance. #### Without changes from this PR ``` hyperfine --prepare 'rm -rf ~/.swiftpm .build && swift package purge-cache' --runs 5 --warmup 1 'swift package resolve' Benchmark 1: swift package resolve Time (mean ± σ): 19.957 s ± 2.606 s [User: 21.070 s, System: 5.866 s] Range (min … max): 17.617 s … 24.409 s 5 runs ``` #### With changes from this PR ``` hyperfine --prepare 'rm -rf ~/.swiftpm .build && swift package purge-cache' --runs 5 --warmup 1 '/Users/marekfort/Developer/swift-package-manager/.build/release/swift-package resolve' Benchmark 1: /Users/marekfort/Developer/swift-package-manager/.build/release/swift-package resolve Time (mean ± σ): 17.914 s ± 0.867 s [User: 20.967 s, System: 6.409 s] Range (min … max): 17.093 s … 19.092 s 5 runs ``` The mean time is better by over 10 %, but as mentioned, the changes might be based on inconsistent network conditions. Note also that with the changes from this PR, the registry resolution is significantly faster than when using source control. This is what one would expect as registry resolution is downloading a whole lot less data. One side-effect of this PR is that since we fetch packages in parallel, the output itself is not sequential as well. See the below example output. #### Without changes from this PR ``` swift package resolve --replace-scm-with-registry Fetching https://github.com/swiftlang/swift-syntax Fetched https://github.com/swiftlang/swift-syntax from cache (2.89s) Fetching pointfreeco.swift-composable-architecture Fetched pointfreeco.swift-composable-architecture from cache (2.21s) Fetching pointfreeco.swift-concurrency-extras Fetched pointfreeco.swift-concurrency-extras from cache (0.43s) Fetching pointfreeco.swift-navigation Fetched pointfreeco.swift-navigation from cache (0.49s) Fetching apple.swift-async-algorithms Fetched apple.swift-async-algorithms from cache (0.55s) Fetching pointfreeco.xctest-dynamic-overlay Fetched pointfreeco.xctest-dynamic-overlay from cache (0.46s) ... ``` #### With changes from this PR ``` Fetching https://github.com/swiftlang/swift-syntax Fetched https://github.com/swiftlang/swift-syntax from cache (2.89s) Fetching apple.swift-numerics Fetching pointfreeco.swift-identified-collections Fetching pointfreeco.swift-navigation Fetching pointfreeco.xctest-dynamic-overlay Fetching pointfreeco.swift-case-paths Fetching pointfreeco.swift-perception .... Fetching pointfreeco.combine-schedulers Fetched pointfreeco.swift-perception from cache (0.76s) Fetched pointfreeco.swift-navigation from cache (0.88s) Fetched apple.swift-async-algorithms from cache (0.89s) Fetched pointfreeco.swift-dependencies from cache (1.10s) Fetched pointfreeco.swift-case-paths from cache (1.11s) Fetched pointfreeco.combine-schedulers from cache (1.38s) ... ``` I personally find the output ok, but let me know what you think about it.
1 parent 99a6e97 commit 0340bb1

File tree

1 file changed

+25
-20
lines changed

1 file changed

+25
-20
lines changed

Sources/Workspace/Workspace+Dependencies.swift

Lines changed: 25 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -426,26 +426,31 @@ extension Workspace {
426426
}
427427

428428
// Retrieve the required resolved packages.
429-
for resolvedPackage in requiredResolvedPackages {
430-
await observabilityScope.makeChildScope(
431-
description: "retrieving resolved package versions for dependencies",
432-
metadata: resolvedPackage.packageRef.diagnosticsMetadata
433-
).trap {
434-
switch resolvedPackage.packageRef.kind {
435-
case .localSourceControl, .remoteSourceControl:
436-
_ = try await self.checkoutRepository(
437-
package: resolvedPackage.packageRef,
438-
at: resolvedPackage.state,
439-
observabilityScope: observabilityScope
440-
)
441-
case .registry:
442-
_ = try await self.downloadRegistryArchive(
443-
package: resolvedPackage.packageRef,
444-
at: resolvedPackage.state,
445-
observabilityScope: observabilityScope
446-
)
447-
default:
448-
throw InternalError("invalid resolved package type \(resolvedPackage.packageRef.kind)")
429+
await withThrowingTaskGroup(of: Void.self) { taskGroup in
430+
for resolvedPackage in requiredResolvedPackages {
431+
let observabilityScope = observabilityScope.makeChildScope(
432+
description: "retrieving resolved package versions for dependencies",
433+
metadata: resolvedPackage.packageRef.diagnosticsMetadata
434+
)
435+
taskGroup.addTask {
436+
await observabilityScope.trap {
437+
switch resolvedPackage.packageRef.kind {
438+
case .localSourceControl, .remoteSourceControl:
439+
_ = try await self.checkoutRepository(
440+
package: resolvedPackage.packageRef,
441+
at: resolvedPackage.state,
442+
observabilityScope: observabilityScope
443+
)
444+
case .registry:
445+
_ = try await self.downloadRegistryArchive(
446+
package: resolvedPackage.packageRef,
447+
at: resolvedPackage.state,
448+
observabilityScope: observabilityScope
449+
)
450+
default:
451+
throw InternalError("invalid resolved package type \(resolvedPackage.packageRef.kind)")
452+
}
453+
}
449454
}
450455
}
451456
}

0 commit comments

Comments
 (0)