Skip to content

Commit 093221a

Browse files
scheglovCommit Queue
authored andcommitted
Fine. Use library manifest handles; discard instances to save memory
Replace in-memory `LibraryManifest` objects with lightweight `LibraryManifestHandle`s that carry a requirements hash and (optionally) serialized bytes. Handles defer deserialization, can discard the live instance, and rehydrate on demand. This reduces heap usage without sacrificing correctness. Key changes: - Introduce `LibraryManifestHandle` with `hashForRequirements`, lazy `instance` materialization, and `discardInstance()`. - Persist and load handles in the linked bundle cache entry; cache now stores the manifest hash + bytes instead of full objects. - Change `LibraryElementImpl.manifest` to `LibraryManifestHandle?` and update call sites to use `.instance` or `.hashForRequirements` as appropriate. - Update `BundleReader`, `LibraryContext`, and manifest builder to pass and produce handles; builder emits handles via `LibraryManifestHandle.fromInstance(...)`. - Add `LinkedElementFactory.discardLibraryManifestInstances()` and call it from the scheduler when idle (with fine dependencies enabled) to aggressively free manifest instances. - Bump `AnalysisDriver.DATA_VERSION` from 567 to 568 to invalidate old cached entries that used plain manifests. Impact: - Peak memory drops from ~785 MB to ~652 MB (−133 MB, −17.0%) with fine-grained dependencies enabled. - Compared to the ~512 MB baseline without fine-grained dependencies, the new peak is +140 MB (+27.3%). Rationale: By storing only a digest and bytes most of the time, we keep manifests off-heap until their full contents are needed, while preserving stable IDs and compatibility across relinks. Change-Id: Ibbfd0159d73ecca93835b7ee447cc05ec8eefb09 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/452260 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent 1c13cd2 commit 093221a

File tree

9 files changed

+167
-90
lines changed

9 files changed

+167
-90
lines changed

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ testFineAfterLibraryAnalyzerHook;
106106
// TODO(scheglov): Clean up the list of implicitly analyzed files.
107107
class AnalysisDriver {
108108
/// The version of data format, should be incremented on every format change.
109-
static const int DATA_VERSION = 567;
109+
static const int DATA_VERSION = 568;
110110

111111
/// The number of exception contexts allowed to write. Once this field is
112112
/// zero, we stop writing any new exception contexts in this process.
@@ -2637,6 +2637,8 @@ class AnalysisDriverScheduler {
26372637
if (priority == AnalysisDriverPriority.nothing) {
26382638
if (driver.withFineDependencies) {
26392639
driver.currentSession.clearHierarchies();
2640+
driver.libraryContext.elementFactory
2641+
.discardLibraryManifestInstances();
26402642
}
26412643
}
26422644
}

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

Lines changed: 16 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import 'package:analyzer/src/summary2/link.dart';
3333
import 'package:analyzer/src/summary2/linked_element_factory.dart';
3434
import 'package:analyzer/src/summary2/reference.dart';
3535
import 'package:analyzer/src/util/performance/operation_performance.dart';
36+
import 'package:analyzer/src/utilities/extensions/collection.dart';
3637
import 'package:collection/collection.dart';
3738

3839
/// Context information necessary to analyze one or more libraries within an
@@ -242,12 +243,15 @@ class LibraryContext {
242243
});
243244
newLinkedBytes = linkResult.resolutionBytes;
244245

245-
var newLibraryManifests = <Uri, LibraryManifest>{};
246+
var newLibraryManifests = <Uri, LibraryManifestHandle>{};
246247
performance.run('computeManifests', (performance) {
248+
var inputManifests = performance.run('inputManifests', (_) {
249+
return probe.libraryManifests.mapValue((h) => h.instance);
250+
});
247251
newLibraryManifests = LibraryManifestBuilder(
248252
elementFactory: elementFactory,
249253
inputLibraries: cycle.libraries,
250-
inputManifests: probe.libraryManifests,
254+
inputManifests: inputManifests,
251255
).computeManifests(performance: performance);
252256
elementFactory.libraryManifests.addAll(newLibraryManifests);
253257
});
@@ -493,7 +497,7 @@ class _LinkedBundleCacheEntry {
493497
///
494498
/// If we have to relink libraries, we will match new elements against
495499
/// these old manifests, and reuse IDs for not affected elements.
496-
final Map<Uri, LibraryManifest> libraryManifests;
500+
final Map<Uri, LibraryManifestHandle> libraryManifests;
497501

498502
/// The serialized libraries, for [BundleReader].
499503
final Uint8List linkedBytes;
@@ -548,27 +552,15 @@ class _LinkedBundleCacheEntry {
548552
var reader = BinaryReader(bytes);
549553
reader.initFromTableTrailer();
550554

551-
var nonTransitiveApiSignature = reader.readStringUtf8();
552-
var requirementsDigest = RequirementsManifestDigest.read(reader);
553-
var requirementsBytes = reader.readUint8List();
554-
555-
var libraryManifests = performance.run('readLibraryManifests', (
556-
performance,
557-
) {
558-
return reader.readMap(
559-
readKey: () => reader.readUri(),
560-
readValue: () => LibraryManifest.read(reader),
561-
);
562-
});
563-
564-
var linkedBytes = reader.readUint8List();
565-
566555
var result = _LinkedBundleCacheEntry(
567-
nonTransitiveApiSignature: nonTransitiveApiSignature,
568-
requirementsDigest: requirementsDigest,
569-
requirementsBytes: requirementsBytes,
570-
libraryManifests: libraryManifests,
571-
linkedBytes: linkedBytes,
556+
nonTransitiveApiSignature: reader.readStringUtf8(),
557+
requirementsDigest: RequirementsManifestDigest.read(reader),
558+
requirementsBytes: reader.readUint8List(),
559+
libraryManifests: reader.readMap(
560+
readKey: () => reader.readUri(),
561+
readValue: () => LibraryManifestHandle.read(reader),
562+
),
563+
linkedBytes: reader.readUint8List(),
572564
);
573565

574566
// We have copies of all data.
@@ -579,7 +571,7 @@ class _LinkedBundleCacheEntry {
579571
}
580572

581573
class _LinkedBundleProbeResult {
582-
final Map<Uri, LibraryManifest> libraryManifests;
574+
final Map<Uri, LibraryManifestHandle> libraryManifests;
583575
final Uint8List? linkedBytes;
584576

585577
_LinkedBundleProbeResult({

pkg/analyzer/lib/src/dart/element/element.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6036,7 +6036,7 @@ class LibraryElementImpl extends ElementImpl
60366036

60376037
/// With fine-grained dependencies, the manifest of the library.
60386038
@trackedInternal
6039-
LibraryManifest? manifest;
6039+
LibraryManifestHandle? manifest;
60406040

60416041
@trackedInternal
60426042
LibraryElementRequirementState requirementState =

pkg/analyzer/lib/src/fine/library_manifest.dart

Lines changed: 94 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -277,9 +277,6 @@ class LibraryManifestBuilder {
277277
/// items for declared elements that don't have items in this map.
278278
final Map<Element, ManifestItem> declaredItems = Map.identity();
279279

280-
/// The new manifests for libraries.
281-
final Map<Uri, LibraryManifest> newManifests = {};
282-
283280
LibraryManifestBuilder({
284281
required this.elementFactory,
285282
required this.inputLibraries,
@@ -292,7 +289,7 @@ class LibraryManifestBuilder {
292289
.toList(growable: false);
293290
}
294291

295-
Map<Uri, LibraryManifest> computeManifests({
292+
Map<Uri, LibraryManifestHandle> computeManifests({
296293
required OperationPerformanceImpl performance,
297294
}) {
298295
performance.getDataInt('libraryCount').add(inputLibraries.length);
@@ -310,7 +307,10 @@ class LibraryManifestBuilder {
310307

311308
assert(_assertSerialization());
312309

313-
return newManifests;
310+
return {
311+
for (var libraryElement in libraryElements)
312+
libraryElement.uri: libraryElement.manifest!,
313+
};
314314
}
315315

316316
void _addClass({
@@ -429,7 +429,7 @@ class LibraryManifestBuilder {
429429

430430
void _addExportedExtensions() {
431431
for (var libraryElement in libraryElements) {
432-
var manifest = libraryElement.manifest!;
432+
var manifest = libraryElement.manifest!.instance;
433433

434434
var extensionIds = <ManifestItemId>{};
435435

@@ -440,7 +440,8 @@ class LibraryManifestBuilder {
440440
.whereType<ExtensionElementImpl>();
441441
for (var extensionElement in exportedExtensionElements) {
442442
var extensionName = extensionElement.lookupName?.asLookupName;
443-
var extensionLibraryManifest = extensionElement.library.manifest!;
443+
var extensionLibraryManifest =
444+
extensionElement.library.manifest!.instance;
444445
var extensionItem =
445446
extensionLibraryManifest.declaredExtensions[extensionName]!;
446447
extensionIds.add(extensionItem.id);
@@ -707,8 +708,7 @@ class LibraryManifestBuilder {
707708

708709
void _addReExports() {
709710
for (var libraryElement in libraryElements) {
710-
var libraryUri = libraryElement.uri;
711-
var manifest = newManifests[libraryUri]!;
711+
var manifest = libraryElement.manifest!.instance;
712712

713713
for (var exported in libraryElement.exportedReferences) {
714714
// We want only re-exports, skip declared.
@@ -731,7 +731,7 @@ class LibraryManifestBuilder {
731731

732732
// Every library has a manifest at this point.
733733
// We already set manifests for the current cycle.
734-
var elementLibraryManifest = elementLibrary.manifest!;
734+
var elementLibraryManifest = elementLibrary.manifest!.instance;
735735

736736
// We use the manifest of the library that declares this element.
737737
// So, the element must be declared in the manifest.
@@ -828,27 +828,29 @@ class LibraryManifestBuilder {
828828
/// Assert that every manifest can be serialized, and when deserialized
829829
/// results in the same manifest.
830830
bool _assertSerialization() {
831-
newManifests.forEach((uri, manifest) {
831+
for (var libraryElement in libraryElements) {
832+
var uri = libraryElement.uri;
833+
var manifest = libraryElement.manifest!.instance;
834+
832835
var bytes = manifest.toBytes();
833836
var readManifest = LibraryManifest.fromBytes(bytes);
834837
var readBytes = readManifest.toBytes();
835838

836839
if (!const ListEquality<int>().equals(bytes, readBytes)) {
837840
throw StateError('Library manifest bytes are different: $uri');
838841
}
839-
});
842+
}
840843

841844
return true;
842845
}
843846

844847
/// Fill `result` with new library manifests.
845848
/// We reuse existing items when they fully match.
846849
/// We build new items for mismatched elements.
847-
Map<Uri, LibraryManifest> _buildManifests() {
850+
void _buildManifests() {
848851
var encodingContext = EncodeContext(elementFactory: elementFactory);
849852

850853
for (var libraryElement in libraryElements) {
851-
var libraryUri = libraryElement.uri;
852854
var newClassItems = <LookupName, ClassItem>{};
853855
var newEnumItems = <LookupName, EnumItem>{};
854856
var newExtensionItems = <LookupName, ExtensionItem>{};
@@ -975,19 +977,16 @@ class LibraryManifestBuilder {
975977
exportedExtensions: ManifestItemIdList([]),
976978
hashForRequirements: Hash.empty,
977979
);
978-
libraryElement.manifest = newManifest;
979-
newManifests[libraryUri] = newManifest;
980+
libraryElement.manifest = LibraryManifestHandle.fromInstance(newManifest);
980981
}
981982

982983
_fillInterfaceElementsInterface();
983984
_addClassTypeAliasConstructors();
984-
985-
return newManifests;
986985
}
987986

988987
void _computeHashForRequirements() {
989988
for (var libraryElement in libraryElements) {
990-
var manifest = libraryElement.manifest!;
989+
var manifest = libraryElement.manifest!.instance;
991990
var builder = ApiSignature();
992991

993992
List<MapEntry<LookupName, T>> sortedMapEntries<T>(
@@ -1097,7 +1096,7 @@ class LibraryManifestBuilder {
10971096

10981097
for (var libraryElement in libraryElements) {
10991098
var libraryUri = libraryElement.uri;
1100-
var manifest = newManifests[libraryUri]!;
1099+
var manifest = libraryElement.manifest!.instance;
11011100
manifest._fillExportMap();
11021101

11031102
var inputManifest = _getInputManifest(libraryUri);
@@ -1391,6 +1390,81 @@ class LibraryManifestBuilder {
13911390
}
13921391
}
13931392

1393+
/// Handle for [LibraryManifest.hashForRequirements] or [instance].
1394+
///
1395+
/// Most [RequirementsManifest] can be checked using just [hashForRequirements],
1396+
/// so we don't even need to read the whole [instance] from bytes. But we can,
1397+
/// if we have to check for details of the manifest.
1398+
///
1399+
/// This class has several states:
1400+
/// 1. Has incomplete [_instance], during building.
1401+
/// 2. Has complete [_instance], no bytes, after building.
1402+
/// 3. Has [_instance], has bytes, after building and write.
1403+
/// 4. No [_instance], has bytes, after [discardInstance] or `read`.
1404+
/// 5. Has [instance], has bytes, after [instance] read it.
1405+
class LibraryManifestHandle {
1406+
Hash? _hashForRequirements;
1407+
Uint8List? _bytes;
1408+
LibraryManifest? _instance;
1409+
1410+
LibraryManifestHandle({
1411+
required Hash? hashForRequirements,
1412+
required Uint8List? bytes,
1413+
required LibraryManifest? instance,
1414+
}) : _hashForRequirements = hashForRequirements,
1415+
_bytes = bytes,
1416+
_instance = instance;
1417+
1418+
factory LibraryManifestHandle.fromInstance(LibraryManifest instance) {
1419+
// Note, we don't convert instance to bytes here.
1420+
// The instance is not finished yet in the builder.
1421+
return LibraryManifestHandle(
1422+
bytes: null,
1423+
hashForRequirements: null,
1424+
instance: instance,
1425+
);
1426+
}
1427+
1428+
factory LibraryManifestHandle.read(BinaryReader reader) {
1429+
return LibraryManifestHandle(
1430+
hashForRequirements: Hash.read(reader),
1431+
bytes: reader.readUint8List(),
1432+
instance: null,
1433+
);
1434+
}
1435+
1436+
Hash get hashForRequirements {
1437+
return _hashForRequirements ?? _instance!.hashForRequirements;
1438+
}
1439+
1440+
LibraryManifest get instance {
1441+
return _instance ??= LibraryManifest.fromBytes(_bytes!);
1442+
}
1443+
1444+
void discardInstance() {
1445+
if (_instance case var instance?) {
1446+
_hashForRequirements ??= instance.hashForRequirements;
1447+
_bytes ??= instance.toBytes();
1448+
}
1449+
_instance = null;
1450+
}
1451+
1452+
void write(BinaryWriter writer) {
1453+
var hashForRequirements =
1454+
_hashForRequirements ?? _instance?.hashForRequirements;
1455+
if (hashForRequirements == null) {
1456+
throw StateError('Missing hashForRequirements');
1457+
}
1458+
hashForRequirements.write(writer);
1459+
1460+
var bytes = _bytes ?? _instance?.toBytes();
1461+
if (bytes == null) {
1462+
throw StateError('Missing bytes');
1463+
}
1464+
writer.writeUint8List(bytes);
1465+
}
1466+
}
1467+
13941468
/// Compares structures of [library] children against [manifest].
13951469
class _LibraryMatch {
13961470
final LibraryElementImpl library;

pkg/analyzer/lib/src/fine/manifest_context.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -365,7 +365,7 @@ extension LinkedElementFactoryExtension on LinkedElementFactory {
365365
var libraryUri = topLevelElement.library!.uri;
366366

367367
// Prepare the external library manifest.
368-
var manifest = libraryManifests[libraryUri];
368+
var manifest = libraryManifests[libraryUri]?.instance;
369369
if (manifest == null) {
370370
return null;
371371
}

0 commit comments

Comments
 (0)