Extend HeapSnapshotGraph API to support incoming references in package:vm_service

Currently the HeapSnapshotGraph represents the objects in the Dart VM
heap and how they relate to each other. They effectively form a directed
graph with a specific root node.

The API does allow traversing the graph by following an object's
outgoing references.

Though in certain situations it can be very useful to know the set of
objects referencing a given object (i.e. incoming references).

This CL adds such support, thereby allowing traversing edges in both
directions: Finding what an object references and who references an
object.

When loading a snapshot we therefore have to also compute the reverse
edges and store them. To offset any additional computation time and
memory consumption, we optimize the existing implementation by not using
growable List<int> objects for outgoing edges but rather typed-data
views into the existing Uint32List of edges.

Due to this optimization we make loading of snapshot faster and smaller,
in a simple benchmark we seem to get:

  * JIT: 10% faster, 40% less RAM
  * AOT: 30% faster, 40% less RAM

despite the additional compute & data structures of this API.

TEST=vm/dart{,_2}/heap_snapshot_referencees_test

Change-Id: Ief30bfb58c70364744eeb7f69967dd1f72ece807
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/260524
Reviewed-by: Tess Strickland <sstrickl@google.com>
Commit-Queue: Martin Kustermann <kustermann@google.com>
This commit is contained in:
Martin Kustermann 2022-09-23 10:11:41 +00:00 committed by Commit Bot
parent affc3392a0
commit 6b1e9bbdfe
3 changed files with 140 additions and 9 deletions

View file

@ -195,7 +195,12 @@ class HeapSnapshotObject {
dynamic get data => _data;
/// A list of indices into [HeapSnapshotGraph.objects].
List<int> get references => _references;
Uint32List get references => Uint32List.sublistView(_graph._successors,
_graph._firstSuccessors[_oid], _graph._firstSuccessors[_oid + 1]);
/// A list of indices into [HeapSnapshotGraph.objects].
Uint32List get referrers => Uint32List.sublistView(_graph._predecessors,
_graph._firstPredecessors[_oid], _graph._firstPredecessors[_oid + 1]);
/// The identity hash code of this object.
///
@ -222,7 +227,6 @@ class HeapSnapshotObject {
int _shallowSize = -1;
int _identityHashCode = 0;
late final dynamic _data;
final List<int> _references = <int>[];
HeapSnapshotObject._sentinel(this._graph)
: _oid = 0,
@ -234,17 +238,17 @@ class HeapSnapshotObject {
_classId = reader.readUnsigned();
_shallowSize = reader.readUnsigned();
_data = _getNonReferenceData(reader);
_graph._firstSuccessors[_oid] = _graph._eid;
_populateReferences(reader);
}
void _populateReferences(_ReadStream reader) {
_graph._firstSuccessors[_oid] = _graph._eid;
final referencesCount = reader.readUnsigned();
for (int i = 0; i < referencesCount; ++i) {
int childOid = reader.readUnsigned();
_references.add(childOid);
_graph._successors[_graph._eid] = childOid;
_graph._eid++;
final currentOid = _graph._eid++;
final childOid = reader.readUnsigned();
_graph._successors[currentOid] = childOid;
_graph._predecessorCounts[childOid]++;
}
}
}
@ -308,8 +312,14 @@ class HeapSnapshotGraph {
final List<HeapSnapshotExternalProperty> _externalProperties =
<HeapSnapshotExternalProperty>[];
late Uint32List _firstSuccessors;
late Uint32List _successors;
late Uint32List _predecessorCounts;
late final Uint32List _firstSuccessors;
late final Uint32List _successors;
late final Uint32List _firstPredecessors;
late final Uint32List _predecessors;
int _eid = 0;
/// Requests a heap snapshot for a given isolate and builds a
@ -356,6 +366,8 @@ class HeapSnapshotGraph {
_populateObjects(reader);
_populateExternalProperties(reader);
_populateIdentityHashCodes(reader);
_calculatePredecessors();
}
void _populateClasses(_ReadStream reader) {
@ -370,8 +382,10 @@ class HeapSnapshotGraph {
void _populateObjects(_ReadStream reader) {
_referenceCount = reader.readUnsigned();
final objectCount = reader.readUnsigned();
_firstSuccessors = _newUint32Array(objectCount + 2);
_successors = _newUint32Array(_referenceCount);
_predecessorCounts = _newUint32Array(objectCount + 2);
_objects.add(HeapSnapshotObject._sentinel(this));
for (int i = 1; i <= objectCount; ++i) {
@ -380,6 +394,37 @@ class HeapSnapshotGraph {
_firstSuccessors[objectCount + 1] = _eid;
}
void _calculatePredecessors() {
final objectCount = _objects.length - 1;
_firstPredecessors = _newUint32Array(objectCount + 2);
_predecessors = _newUint32Array(_referenceCount);
_firstPredecessors[objectCount + 1] = _eid;
// We reuse the [_predecessorCounts] array and turn it into the
// write cursor array.
final predecessorCounts = _predecessorCounts;
_predecessorCounts = Uint32List(0);
int sum = 0;
int totalCount = _referenceCount;
for (int i = objectCount; i >= 0; --i) {
sum += predecessorCounts[i];
final firstPredecessor = totalCount - sum;
_firstPredecessors[i] = predecessorCounts[i] = firstPredecessor;
}
final predecessorWriteCursor = predecessorCounts;
for (int i = 1; i <= objectCount; ++i) {
final from = _firstSuccessors[i];
final to = _firstSuccessors[i + 1];
for (int j = from; j < to; ++j) {
final cursor = predecessorWriteCursor[_successors[j]]++;
_predecessors[cursor] = i;
}
}
}
void _populateExternalProperties(_ReadStream reader) {
final propertiesCount = reader.readUnsigned();
for (int i = 0; i < propertiesCount; ++i) {

View file

@ -0,0 +1,42 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:_internal';
import 'package:expect/expect.dart';
import 'package:path/path.dart' as path;
import 'heap_snapshot_test.dart';
import 'use_flag_test_helper.dart';
main() async {
if (const bool.fromEnvironment('dart.vm.product')) return;
await withTempDir('heap_snapshot_test', (String dir) async {
final file = path.join(dir, 'state1.heapsnapshot');
VMInternalsForTesting.writeHeapSnapshotToFile(file);
final graph = loadHeapSnapshotFromFile(file);
final reachable = findReachableObjects(graph);
for (int id = 0; id < graph.objects.length; ++id) {
final object = graph.objects[id];
// Ensure all `references` appear in `referrers`.
for (final rid in object.references) {
final users = graph.objects[rid].referrers;
if (!users.contains(id)) {
throw 'Object $id references $rid, but is not in referrers';
}
}
// Ensure all `referrers` appear in `references`.
for (final uid in object.referrers) {
final refs = graph.objects[uid].references;
if (!refs.contains(id)) {
throw 'Object $id is referenced by $uid, but is not in references.';
}
}
}
});
}

View file

@ -0,0 +1,44 @@
// Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart=2.9
import 'dart:_internal';
import 'package:expect/expect.dart';
import 'package:path/path.dart' as path;
import 'heap_snapshot_test.dart';
import 'use_flag_test_helper.dart';
main() async {
if (const bool.fromEnvironment('dart.vm.product')) return;
await withTempDir('heap_snapshot_test', (String dir) async {
final file = path.join(dir, 'state1.heapsnapshot');
VMInternalsForTesting.writeHeapSnapshotToFile(file);
final graph = loadHeapSnapshotFromFile(file);
final reachable = findReachableObjects(graph);
for (int id = 0; id < graph.objects.length; ++id) {
final object = graph.objects[id];
// Ensure all `references` appear in `referrers`.
for (final rid in object.references) {
final users = graph.objects[rid].referrers;
if (!users.contains(id)) {
throw 'Object $id references $rid, but is not in referrers';
}
}
// Ensure all `referrers` appear in `references`.
for (final uid in object.referrers) {
final refs = graph.objects[uid].references;
if (!refs.contains(id)) {
throw 'Object $id is referenced by $uid, but is not in references.';
}
}
}
});
}