Skip to content

Commit 563cc89

Browse files
scheglovCommit Queue
authored andcommitted
Fine. Optimize linked bundle reuse with an API signature shortcut
When fine-grained dependencies are enabled, reusing a cached linked bundle requires validating that all its requirements are still satisfied. While cheaper than relinking, this validation is still not free. Many common code modifications, such as changing a function body, do not affect the transitive API signature of a library cycle. In such scenarios, if the bundle's requirements were already validated against this signature, they will remain valid. This change introduces an optimization to avoid redundant validation. The `LinkedBundleEntry` now caches the last transitive API signature for which it was successfully validated. If a request is made for a cycle with a matching signature, the expensive requirements check is skipped, and the bundle is reused directly. Additionally, the driver events related to bundle reuse have been refactored. Several "CannotReuse" events are consolidated into a single `CheckLinkedBundleRequirements` event, simplifying event tracking. Similar to https://dart-review.googlesource.com/c/sdk/+/447520 Change-Id: I2165bccf4fee5cd01963f0ca4b432919105b91a2 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/447820 Commit-Queue: Konstantin Shcheglov <[email protected]> Reviewed-by: Paul Berry <[email protected]>
1 parent c0e9ac3 commit 563cc89

File tree

4 files changed

+556
-527
lines changed

4 files changed

+556
-527
lines changed

pkg/analyzer/lib/src/dart/analysis/driver_event.dart

Lines changed: 9 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,6 @@ final class AnalyzeFile extends AnalysisDriverEvent {
2828
AnalyzeFile({required this.file, required this.library});
2929
}
3030

31-
/// The event that we were not able to reuse the existing linked bundle
32-
/// for [cycle], because found the [failure].
33-
///
34-
/// Currently this means that the whole bundle will be linked.
35-
final class CannotReuseLinkedBundle extends AnalysisDriverEvent {
36-
final LinkedElementFactory elementFactory;
37-
final LibraryCycle cycle;
38-
final RequirementFailure failure;
39-
40-
CannotReuseLinkedBundle({
41-
required this.elementFactory,
42-
required this.cycle,
43-
required this.failure,
44-
});
45-
}
46-
4731
/// The event that we checked requirements of the library diagnostics.
4832
/// This is much cheaper than computing the result again, but not free.
4933
final class CheckLibraryDiagnosticsRequirements extends AnalysisDriverEvent {
@@ -56,15 +40,13 @@ final class CheckLibraryDiagnosticsRequirements extends AnalysisDriverEvent {
5640
});
5741
}
5842

59-
/// The event that we were not able to reuse the existing analysis results
60-
/// for [library], because found the [failure].
61-
///
62-
/// Currently this means that the whole library will be analyzed.
63-
final class GetErrorsCannotReuse extends AnalysisDriverEvent {
64-
final LibraryFileKind library;
65-
final RequirementFailure failure;
43+
/// The event that we checked requirements of the linked bundle.
44+
/// This is much cheaper than relinking it, but not free.
45+
final class CheckLinkedBundleRequirements extends AnalysisDriverEvent {
46+
final LibraryCycle cycle;
47+
final RequirementFailure? failure;
6648

67-
GetErrorsCannotReuse({required this.library, required this.failure});
49+
CheckLinkedBundleRequirements({required this.cycle, required this.failure});
6850
}
6951

7052
final class GetErrorsFromBytes extends AnalysisDriverEvent {
@@ -88,21 +70,9 @@ final class LinkLibraryCycle extends AnalysisDriverEvent {
8870
});
8971
}
9072

91-
/// The event that we were not able to reuse the existing analysis results
92-
/// for [library], because found the [failure].
93-
///
94-
/// Currently this means that the whole library will be analyzed.
95-
final class ProduceErrorsCannotReuse extends AnalysisDriverEvent {
96-
final LibraryFileKind library;
97-
final RequirementFailure failure;
98-
99-
ProduceErrorsCannotReuse({required this.library, required this.failure});
100-
}
101-
102-
/// The event that the existing summary bundle for [cycle] were reused,
103-
/// because its requirements are still satisfied.
104-
final class ReuseLinkLibraryCycleBundle extends AnalysisDriverEvent {
73+
/// The event that the existing summary bundle for [cycle] was reused.
74+
final class ReuseLinkedBundle extends AnalysisDriverEvent {
10575
final LibraryCycle cycle;
10676

107-
ReuseLinkLibraryCycleBundle({required this.cycle});
77+
ReuseLinkedBundle({required this.cycle});
10878
}

pkg/analyzer/lib/src/dart/analysis/library_context.dart

Lines changed: 108 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,77 @@ class LibraryContext {
195195
}
196196
}
197197

198+
/// Returns a previously linked bundle entry for [cycle] if present and
199+
/// reusable; otherwise returns `null`. Always returns the last known
200+
/// [LibraryManifest]s from the stored entry (if any) so they can be reused
201+
/// to preserve item versions during relinking.
202+
_LinkedBundleEntryResult _getLinkedBundleEntry({
203+
required LibraryCycle cycle,
204+
required OperationPerformanceImpl performance,
205+
}) {
206+
return performance.run('getLinkedBundleEntry', (performance) {
207+
var entry = performance.run('bundleProvider.get', (performance) {
208+
return linkedBundleProvider.get(
209+
key: cycle.linkedKey,
210+
performance: performance,
211+
);
212+
});
213+
214+
// Nothing cached at all.
215+
if (entry == null) {
216+
return _LinkedBundleEntryResult(entry: null, libraryManifests: {});
217+
}
218+
219+
// If we don't track fine dependencies, any hit is good enough.
220+
// The key already depends on the transitive API signature.
221+
if (!withFineDependencies) {
222+
return _LinkedBundleEntryResult(entry: entry, libraryManifests: {});
223+
}
224+
225+
// If anything changed in the API signature, relink the cycle.
226+
// But keep previous manifests to reuse item IDs.
227+
if (entry.nonTransitiveApiSignature != cycle.nonTransitiveApiSignature) {
228+
return _LinkedBundleEntryResult(
229+
entry: null,
230+
libraryManifests: entry.libraryManifests,
231+
);
232+
}
233+
234+
// Already validated for this transitive state? Skip.
235+
if (entry.isValidated(cycle.apiSignature)) {
236+
return _LinkedBundleEntryResult(
237+
entry: entry,
238+
libraryManifests: entry.libraryManifests,
239+
);
240+
}
241+
242+
var failure = performance.run('checkRequirements', (performance) {
243+
return entry.requirements.isSatisfied(
244+
elementFactory: elementFactory,
245+
libraryManifests: elementFactory.libraryManifests,
246+
);
247+
});
248+
249+
eventsController?.add(
250+
CheckLinkedBundleRequirements(cycle: cycle, failure: failure),
251+
);
252+
253+
if (failure != null) {
254+
return _LinkedBundleEntryResult(
255+
entry: null,
256+
libraryManifests: entry.libraryManifests,
257+
);
258+
}
259+
260+
entry.addValidated(cycle.apiSignature);
261+
262+
return _LinkedBundleEntryResult(
263+
entry: entry,
264+
libraryManifests: entry.libraryManifests,
265+
);
266+
});
267+
}
268+
198269
/// Recursively load the linked bundle for [cycle], link if not available.
199270
///
200271
/// Uses the same [performance] during recursion, so has single aggregate
@@ -220,46 +291,11 @@ class LibraryContext {
220291
}
221292
}
222293

223-
var bundleEntry = performance.run('bundleProvider.get', (performance) {
224-
return linkedBundleProvider.get(
225-
key: cycle.linkedKey,
226-
performance: performance,
227-
);
228-
});
229-
230-
var inputLibraryManifests = <Uri, LibraryManifest>{};
231-
if (withFineDependencies && bundleEntry != null) {
232-
var isSatisfied = performance.run('libraryContext(isSatisfied)', (
233-
performance,
234-
) {
235-
inputLibraryManifests = bundleEntry!.libraryManifests;
236-
// If anything change in the API signature, relink the cycle.
237-
// But use previous manifests to reuse item versions.
238-
if (bundleEntry.apiSignature != cycle.nonTransitiveApiSignature) {
239-
return false;
240-
} else {
241-
var requirements = bundleEntry.requirements;
242-
var failure = requirements.isSatisfied(
243-
elementFactory: elementFactory,
244-
libraryManifests: elementFactory.libraryManifests,
245-
);
246-
if (failure != null) {
247-
eventsController?.add(
248-
CannotReuseLinkedBundle(
249-
elementFactory: elementFactory,
250-
cycle: cycle,
251-
failure: failure,
252-
),
253-
);
254-
return false;
255-
}
256-
}
257-
return true;
258-
});
259-
if (!isSatisfied) {
260-
bundleEntry = null;
261-
}
262-
}
294+
var bundleLookup = _getLinkedBundleEntry(
295+
cycle: cycle,
296+
performance: performance,
297+
);
298+
var bundleEntry = bundleLookup.entry;
263299

264300
if (bundleEntry == null) {
265301
testData?.linkedCycles.add(
@@ -287,7 +323,7 @@ class LibraryContext {
287323
newLibraryManifests = LibraryManifestBuilder(
288324
elementFactory: elementFactory,
289325
inputLibraries: cycle.libraries,
290-
inputManifests: inputLibraryManifests,
326+
inputManifests: bundleLookup.libraryManifests,
291327
).computeManifests(performance: performance);
292328
elementFactory.libraryManifests.addAll(newLibraryManifests);
293329
});
@@ -301,11 +337,13 @@ class LibraryContext {
301337
assert(requirements.assertSerialization());
302338

303339
bundleEntry = LinkedBundleEntry(
304-
apiSignature: cycle.nonTransitiveApiSignature,
340+
nonTransitiveApiSignature: cycle.nonTransitiveApiSignature,
305341
libraryManifests: newLibraryManifests,
306342
requirements: requirements,
307343
linkedBytes: linkedBytes,
308344
);
345+
bundleEntry.addValidated(cycle.apiSignature);
346+
309347
performance.run('bundleProvider.put', (performance) {
310348
linkedBundleProvider.put(
311349
key: cycle.linkedKey,
@@ -333,7 +371,7 @@ class LibraryContext {
333371
linkedBytes = linkResult.resolutionBytes;
334372

335373
bundleEntry = LinkedBundleEntry(
336-
apiSignature: cycle.nonTransitiveApiSignature,
374+
nonTransitiveApiSignature: cycle.nonTransitiveApiSignature,
337375
libraryManifests: {},
338376
requirements: RequirementsManifest(),
339377
linkedBytes: linkedBytes,
@@ -366,7 +404,7 @@ class LibraryContext {
366404
performance.getDataInt('bytesGet').add(linkedBytes.length);
367405
performance.getDataInt('libraryLoadCount').add(cycle.libraries.length);
368406
// TODO(scheglov): Take / clear parsed units in files.
369-
eventsController?.add(ReuseLinkLibraryCycleBundle(cycle: cycle));
407+
eventsController?.add(ReuseLinkedBundle(cycle: cycle));
370408
var bundleReader = performance.run('bundleReader', (performance) {
371409
return BundleReader(
372410
elementFactory: elementFactory,
@@ -436,7 +474,7 @@ class LibraryCycleTestData {
436474
/// The entry in [LinkedBundleProvider].
437475
class LinkedBundleEntry {
438476
/// See [LibraryCycle.nonTransitiveApiSignature].
439-
final String apiSignature;
477+
final String nonTransitiveApiSignature;
440478

441479
/// The manifests of libraries in [linkedBytes].
442480
///
@@ -451,15 +489,26 @@ class LinkedBundleEntry {
451489
/// Without fine-grained dependencies, the requirements are empty.
452490
final RequirementsManifest requirements;
453491

492+
/// Last transitive API signature for which [requirements] were verified.
493+
String? _validatedApiSignature;
494+
454495
/// The serialized libraries, for [BundleReader].
455496
final Uint8List linkedBytes;
456497

457498
LinkedBundleEntry({
458-
required this.apiSignature,
499+
required this.nonTransitiveApiSignature,
459500
required this.libraryManifests,
460501
required this.requirements,
461502
required this.linkedBytes,
462503
});
504+
505+
void addValidated(String apiSignature) {
506+
_validatedApiSignature = apiSignature;
507+
}
508+
509+
bool isValidated(String apiSignature) {
510+
return _validatedApiSignature == apiSignature;
511+
}
463512
}
464513

465514
/// The cache of serialized libraries.
@@ -502,7 +551,7 @@ class LinkedBundleProvider {
502551

503552
performance.getDataInt('bytesLength').add(bytes.length);
504553
var reader = SummaryDataReader(bytes);
505-
var apiSignature = reader.readStringUtf8();
554+
var nonTransitiveApiSignature = reader.readStringUtf8();
506555
var libraryManifests = reader.readMap(
507556
readKey: () => reader.readUri(),
508557
readValue: () => LibraryManifest.read(reader),
@@ -511,7 +560,7 @@ class LinkedBundleProvider {
511560
var linkedBytes = reader.readUint8List();
512561

513562
var result = LinkedBundleEntry(
514-
apiSignature: apiSignature,
563+
nonTransitiveApiSignature: nonTransitiveApiSignature,
515564
libraryManifests: libraryManifests,
516565
requirements: requirements,
517566
linkedBytes: linkedBytes,
@@ -534,7 +583,7 @@ class LinkedBundleProvider {
534583
}) {
535584
var sink = BufferedSink();
536585

537-
sink.writeStringUtf8(entry.apiSignature);
586+
sink.writeStringUtf8(entry.nonTransitiveApiSignature);
538587
sink.writeMap(
539588
entry.libraryManifests,
540589
writeKey: (uri) => sink.writeUri(uri),
@@ -552,3 +601,13 @@ class LinkedBundleProvider {
552601
}
553602
}
554603
}
604+
605+
class _LinkedBundleEntryResult {
606+
final LinkedBundleEntry? entry;
607+
final Map<Uri, LibraryManifest> libraryManifests;
608+
609+
_LinkedBundleEntryResult({
610+
required this.entry,
611+
required this.libraryManifests,
612+
});
613+
}

0 commit comments

Comments
 (0)