Skip to content

Commit 5fae212

Browse files
[SwiftCaching] Improve swift caching batch build performance (#301)
Improve swift caching build performance, especially batch build performance by: * Use more accurate dependency tracking by requesting cache key querying jobs in parallel when requesting all the explicit module dependencies. This allows build system to plan cache key querying jobs in the gaps of explicit module build, thus the compilation job can start sooner. * Instead of issuing multiple key queries per batched job, issue one key querying job for multiple keys instead. This can dramatically reduce the number of jobs planned thus improves the efficiency. Also in the cache miss cases, if the first key hits the cache miss, there is no need to continue querying other keys since the job needs to be run anyway. rdar://146429825
1 parent 52eb2d2 commit 5fae212

5 files changed

+114
-109
lines changed

Sources/SWBTaskExecution/DynamicTaskSpecs/SwiftCachingDynamicTaskSpec.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ final class SwiftCachingKeyQueryDynamicTaskSpec: DynamicTaskSpec {
2323
type: self,
2424
payload: nil,
2525
forTarget: dynamicTask.target,
26-
ruleInfo: ["SwiftCachingKeyQuery", swiftCachingKeyQueryTaskKey.cacheKey],
27-
commandLine: ["builtin-swiftCachingKeyQuery", .literal(ByteString(encodingAsUTF8: swiftCachingKeyQueryTaskKey.cacheKey))],
26+
ruleInfo: ["SwiftCachingKeyQuery", swiftCachingKeyQueryTaskKey.cacheKeys.description],
27+
commandLine: ["builtin-swiftCachingKeyQuery", .literal(ByteString(encodingAsUTF8: swiftCachingKeyQueryTaskKey.cacheKeys.description))],
2828
environment: dynamicTask.environment,
2929
workingDirectory: dynamicTask.workingDirectory,
3030
showEnvironment: dynamicTask.showEnvironment,
31-
execDescription: "Swift caching query key \(swiftCachingKeyQueryTaskKey.cacheKey)",
31+
execDescription: "Swift caching query key \(swiftCachingKeyQueryTaskKey.cacheKeys)",
3232
priority: .network,
3333
isDynamic: true
3434
)
@@ -57,12 +57,12 @@ final class SwiftCachingMaterializeKeyDynamicTaskSpec: DynamicTaskSpec {
5757
type: self,
5858
payload: nil,
5959
forTarget: dynamicTask.target,
60-
ruleInfo: ["SwiftCachingKeyMaterializer", swiftCachingTaskKey.cacheKey],
61-
commandLine: ["builtin-swiftCachingKeyMaterializer", .literal(ByteString(encodingAsUTF8: swiftCachingTaskKey.cacheKey))],
60+
ruleInfo: ["SwiftCachingKeyMaterializer", swiftCachingTaskKey.cacheKeys.description],
61+
commandLine: ["builtin-swiftCachingKeyMaterializer", .literal(ByteString(encodingAsUTF8: swiftCachingTaskKey.cacheKeys.description))],
6262
environment: dynamicTask.environment,
6363
workingDirectory: dynamicTask.workingDirectory,
6464
showEnvironment: dynamicTask.showEnvironment,
65-
execDescription: "Swift caching materialize key \(swiftCachingTaskKey.cacheKey)",
65+
execDescription: "Swift caching materialize key \(swiftCachingTaskKey.cacheKeys)",
6666
priority: .network,
6767
isDynamic: true
6868
)

Sources/SWBTaskExecution/DynamicTaskSpecs/SwiftCachingTaskKeys.swift

+6-6
Original file line numberDiff line numberDiff line change
@@ -15,32 +15,32 @@ public import SWBUtil
1515

1616
public struct SwiftCachingKeyQueryTaskKey: Serializable, CustomDebugStringConvertible {
1717
let casOptions: CASOptions
18-
let cacheKey: String
18+
let cacheKeys: [String]
1919
let compilerLocation: LibSwiftDriver.CompilerLocation
2020

21-
init(casOptions: CASOptions, cacheKey: String, compilerLocation: LibSwiftDriver.CompilerLocation) {
21+
init(casOptions: CASOptions, cacheKeys: [String], compilerLocation: LibSwiftDriver.CompilerLocation) {
2222
self.casOptions = casOptions
23-
self.cacheKey = cacheKey
23+
self.cacheKeys = cacheKeys
2424
self.compilerLocation = compilerLocation
2525
}
2626

2727
public func serialize<T>(to serializer: T) where T : Serializer {
2828
serializer.serializeAggregate(3) {
2929
serializer.serialize(casOptions)
30-
serializer.serialize(cacheKey)
30+
serializer.serialize(cacheKeys)
3131
serializer.serialize(compilerLocation)
3232
}
3333
}
3434

3535
public init(from deserializer: any Deserializer) throws {
3636
try deserializer.beginAggregate(3)
3737
casOptions = try deserializer.deserialize()
38-
cacheKey = try deserializer.deserialize()
38+
cacheKeys = try deserializer.deserialize()
3939
compilerLocation = try deserializer.deserialize()
4040
}
4141

4242
public var debugDescription: String {
43-
"<SwiftCachingKeyQuery casOptions=\(casOptions) cacheKey=\(cacheKey) compilerLocation=\(compilerLocation)>"
43+
"<SwiftCachingKeyQuery casOptions=\(casOptions) cacheKey=\(cacheKeys) compilerLocation=\(compilerLocation)>"
4444
}
4545
}
4646

Sources/SWBTaskExecution/TaskActions/SwiftCachingKeyQueryTaskAction.swift

+9-3
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,15 @@ public final class SwiftCachingKeyQueryTaskAction: TaskAction {
4545

4646
do {
4747
// Request global query to get maximum search space that the database supports
48-
let cacheHit = try await cas.queryCacheKey(key.cacheKey, globally: true) != nil
49-
if key.casOptions.enableDiagnosticRemarks {
50-
outputDelegate.remark("cache key query \(cacheHit ? "hit" : "miss")")
48+
for cacheKey in key.cacheKeys {
49+
let cacheHit = try await cas.queryCacheKey(cacheKey, globally: true) != nil
50+
if key.casOptions.enableDiagnosticRemarks {
51+
outputDelegate.remark("cache key query \(cacheHit ? "hit" : "miss")")
52+
}
53+
guard cacheHit else {
54+
// return on first failure.
55+
return .succeeded
56+
}
5157
}
5258
} catch {
5359
guard !key.casOptions.enableStrictCASErrors else { throw error }

Sources/SWBTaskExecution/TaskActions/SwiftCachingMaterializeKeyTaskAction.swift

+50-39
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public final class SwiftCachingMaterializeKeyTaskAction: TaskAction {
4141
/// The action is in its initial state, and has not yet performed any work.
4242
case initial
4343
/// Waiting for the caching key query to finish.
44-
case waitingForKeyQuery(jobTaskIDBase: UInt, cas: SwiftCASDatabases, key: String)
44+
case waitingForKeyQuery(jobTaskIDBase: UInt, cas: SwiftCASDatabases, keys: [String])
4545
/// Waiting for the outputs to finish downloading.
4646
case waitingForOutputDownloads
4747
/// Not waiting for any other dependency
@@ -69,22 +69,22 @@ public final class SwiftCachingMaterializeKeyTaskAction: TaskAction {
6969
guard let cas = try swiftModuleDependencyGraph.getCASDatabases(casOptions: taskKey.casOptions, compilerLocation: taskKey.compilerLocation) else {
7070
throw StubError.error("unable to use CAS databases")
7171
}
72-
let jobTaskIDBase: UInt = 0
73-
if let cachedComp = try cas.queryLocalCacheKey(taskKey.cacheKey) {
74-
if try requestCompilationOutputs(cachedComp,
75-
dynamicExecutionDelegate: dynamicExecutionDelegate,
76-
cacheKey: taskKey.cacheKey,
77-
jobTaskIDBase: jobTaskIDBase) != 0 {
78-
state = .waitingForOutputDownloads
72+
var missingKeys: [String] = []
73+
var compOutputs: [SwiftCachedCompilation] = []
74+
try taskKey.cacheKeys.forEach { key in
75+
if let output = try cas.queryLocalCacheKey(key) {
76+
compOutputs.append(output)
7977
} else {
80-
state = .done
78+
missingKeys.append(key)
8179
}
82-
} else {
83-
let key = SwiftCachingKeyQueryTaskKey(casOptions: taskKey.casOptions, cacheKey: taskKey.cacheKey, compilerLocation: taskKey.compilerLocation)
80+
}
81+
82+
if !missingKeys.isEmpty {
83+
let key = SwiftCachingKeyQueryTaskKey(casOptions: taskKey.casOptions, cacheKeys: missingKeys, compilerLocation: taskKey.compilerLocation)
8484
dynamicExecutionDelegate.requestDynamicTask(
8585
toolIdentifier: SwiftCachingKeyQueryTaskAction.toolIdentifier,
8686
taskKey: .swiftCachingKeyQuery(key),
87-
taskID: jobTaskIDBase,
87+
taskID: 0,
8888
singleUse: true,
8989
workingDirectory: Path(""),
9090
environment: .init(),
@@ -93,7 +93,15 @@ public final class SwiftCachingMaterializeKeyTaskAction: TaskAction {
9393
showEnvironment: false,
9494
reason: .wasCompilationCachingQuery
9595
)
96-
state = .waitingForKeyQuery(jobTaskIDBase: jobTaskIDBase + 1, cas: cas, key: taskKey.cacheKey)
96+
state = .waitingForKeyQuery(jobTaskIDBase: 1, cas: cas, keys: taskKey.cacheKeys)
97+
} else {
98+
if try requestCompilationOutputs(compOutputs,
99+
dynamicExecutionDelegate: dynamicExecutionDelegate,
100+
jobTaskIDBase: 1) != 0 {
101+
state = .waitingForOutputDownloads
102+
} else {
103+
state = .done
104+
}
97105
}
98106
} catch {
99107
state = .executionError(error)
@@ -111,15 +119,17 @@ public final class SwiftCachingMaterializeKeyTaskAction: TaskAction {
111119
switch state {
112120
case .initial:
113121
state = .executionError(StubError.error("taskDependencyReady unexpectedly called in initial state"))
114-
case .waitingForKeyQuery(jobTaskIDBase: let jobTaskIDBase, cas: let cas, key: let key):
122+
case .waitingForKeyQuery(jobTaskIDBase: let jobTaskIDBase, cas: let cas, keys: let keys):
115123
do {
116-
guard let cachedComp = try cas.queryLocalCacheKey(key) else {
124+
let cachedComps = try keys.compactMap {
125+
try cas.queryLocalCacheKey($0)
126+
}
127+
guard cachedComps.count == keys.count else {
117128
state = .done
118129
return // compilation key not found.
119130
}
120-
if try requestCompilationOutputs(cachedComp,
131+
if try requestCompilationOutputs(cachedComps,
121132
dynamicExecutionDelegate: dynamicExecutionDelegate,
122-
cacheKey: key,
123133
jobTaskIDBase: jobTaskIDBase) != 0 {
124134
state = .waitingForOutputDownloads
125135
} else {
@@ -139,32 +149,33 @@ public final class SwiftCachingMaterializeKeyTaskAction: TaskAction {
139149
}
140150

141151
private func requestCompilationOutputs(
142-
_ cachedComp: SwiftCachedCompilation,
152+
_ cachedComps: [SwiftCachedCompilation],
143153
dynamicExecutionDelegate: any DynamicTaskExecutionDelegate,
144-
cacheKey: String,
145154
jobTaskIDBase: UInt
146155
) throws -> UInt {
147-
let requested = try cachedComp.getOutputs().reduce(into: UInt(0)) { (numRequested, output) in
148-
if output.isMaterialized { return }
149-
let outputMaterializeKey = SwiftCachingOutputMaterializerTaskKey(casOptions: taskKey.casOptions,
150-
casID: output.casID,
151-
outputKind: output.kindName,
152-
compilerLocation: taskKey.compilerLocation)
153-
dynamicExecutionDelegate.requestDynamicTask(
154-
toolIdentifier: SwiftCachingOutputMaterializerTaskAction.toolIdentifier,
155-
taskKey: .swiftCachingOutputMaterializer(outputMaterializeKey),
156-
taskID: jobTaskIDBase + numRequested,
157-
singleUse: true,
158-
workingDirectory: Path(""),
159-
environment: .init(),
160-
forTarget: nil,
161-
priority: .network,
162-
showEnvironment: false,
163-
reason: .wasCompilationCachingQuery
164-
)
165-
numRequested += 1
156+
let numRequested = try cachedComps.reduce(into: UInt(0)) { (numRequested, cachedComp) in
157+
try cachedComp.getOutputs().forEach { output in
158+
if output.isMaterialized { return }
159+
let outputMaterializeKey = SwiftCachingOutputMaterializerTaskKey(casOptions: taskKey.casOptions,
160+
casID: output.casID,
161+
outputKind: output.kindName,
162+
compilerLocation: taskKey.compilerLocation)
163+
dynamicExecutionDelegate.requestDynamicTask(
164+
toolIdentifier: SwiftCachingOutputMaterializerTaskAction.toolIdentifier,
165+
taskKey: .swiftCachingOutputMaterializer(outputMaterializeKey),
166+
taskID: jobTaskIDBase + numRequested,
167+
singleUse: true,
168+
workingDirectory: Path(""),
169+
environment: .init(),
170+
forTarget: nil,
171+
priority: .network,
172+
showEnvironment: false,
173+
reason: .wasCompilationCachingQuery
174+
)
175+
numRequested += 1
176+
}
166177
}
167-
return requested
178+
return numRequested
168179
}
169180

170181
public override func performTaskAction(

0 commit comments

Comments
 (0)