Abstract out the used set implementation in heapsnapshot analysis tool.

Doing so will allow using a more efficient (both memory-wise
and perf-wise) set implementation.

TEST=ci

Change-Id: Ia64ad5785bab6dba668c5fc3e2dcfcaf482d8b83
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/262424
Commit-Queue: Tess Strickland <sstrickl@google.com>
Reviewed-by: Tess Strickland <sstrickl@google.com>
This commit is contained in:
Martin Kustermann 2022-10-04 09:58:19 +00:00 committed by Commit Queue
parent 3931dc6b08
commit 756379442e
6 changed files with 76 additions and 66 deletions

View file

@ -7,6 +7,8 @@ import 'dart:typed_data';
import 'package:vm_service/vm_service.dart';
import 'format.dart';
import 'intset.dart';
export 'intset.dart';
const int _invalidIdx = 0;
const int _rootObjectIdx = 1;
@ -14,7 +16,7 @@ const int _rootObjectIdx = 1;
class Analysis {
final HeapSnapshotGraph graph;
late final reachableObjects = transitiveGraph(<int>{_rootObjectIdx});
late final reachableObjects = transitiveGraph(roots);
late final Uint32List _retainers = _calculateRetainers();
@ -66,13 +68,13 @@ class Analysis {
Analysis(this.graph);
/// The roots from which alive data can be discovered.
Set<int> get roots => <int>{_rootObjectIdx};
final IntSet roots = IntSet()..add(_rootObjectIdx);
/// Calculates retaining paths for all objects in [objs].
///
/// All retaining paths will have the object itself plus at most [depth]
/// retainers in it.
List<DedupedUint32List> retainingPathsOf(Set<int> objs, int depth) {
List<DedupedUint32List> retainingPathsOf(IntSet objs, int depth) {
final paths = <DedupedUint32List, int>{};
for (var oId in objs) {
final rpath = _retainingPathOf(oId, depth);
@ -143,7 +145,7 @@ class Analysis {
///
/// The classes are sored by sum of shallow-size of objects of a class if
/// [sortBySize] is true and by number of objects per-class otherwise.
HeapStats generateObjectStats(Set<int> objects, {bool sortBySize = true}) {
HeapStats generateObjectStats(IntSet objects, {bool sortBySize = true}) {
final graphObjects = graph.objects;
final numCids = graph.classes.length;
@ -184,7 +186,7 @@ class Analysis {
///
/// The returned [HeapData]s are sorted by cumulative size if
/// [sortBySize] is true and by number of objects otherwise.
HeapDataStats generateDataStats(Set<int> objects, {bool sortBySize = true}) {
HeapDataStats generateDataStats(IntSet objects, {bool sortBySize = true}) {
final graphObjects = graph.objects;
final klasses = graph.classes;
final counts = <HeapData, int>{};
@ -212,8 +214,8 @@ class Analysis {
}
/// Calculates the set of objects transitively reachable by [roots].
Set<int> transitiveGraph(Set<int> roots, [TraverseFilter? tfilter = null]) {
final reachable = <int>{};
IntSet transitiveGraph(IntSet roots, [TraverseFilter? tfilter = null]) {
final reachable = IntSet();
final worklist = <int>[];
final objects = graph.objects;
@ -221,7 +223,7 @@ class Analysis {
reachable.addAll(roots);
worklist.addAll(roots);
final weakProperties = <int>{};
final weakProperties = IntSet();
while (worklist.isNotEmpty) {
while (worklist.isNotEmpty) {
@ -287,9 +289,8 @@ class Analysis {
}
/// Calculates the set of objects that transitively can reach [oids].
Set<int> reverseTransitiveGraph(Set<int> oids,
[TraverseFilter? tfilter = null]) {
final reachable = <int>{};
IntSet reverseTransitiveGraph(IntSet oids, [TraverseFilter? tfilter = null]) {
final reachable = IntSet();
final worklist = <int>[];
final objects = graph.objects;
@ -354,8 +355,8 @@ class Analysis {
}
// Only keep those in [toFilter] that have references from [from].
Set<int> filterObjectsReferencedBy(Set<int> toFilter, Set<int> from) {
final result = <int>{};
IntSet filterObjectsReferencedBy(IntSet toFilter, IntSet from) {
final result = IntSet();
final objects = graph.objects;
for (final fromId in from) {
@ -372,11 +373,11 @@ class Analysis {
}
/// Returns set of cids that are matching the provided [patterns].
Set<int> findClassIdsMatching(Iterable<String> patterns) {
IntSet findClassIdsMatching(Iterable<String> patterns) {
final regexPatterns = patterns.map((p) => RegExp(p)).toList();
final classes = graph.classes;
final cids = <int>{};
final cids = IntSet();
for (final klass in classes) {
if (regexPatterns.any((pattern) =>
pattern.hasMatch(klass.name) ||
@ -391,13 +392,13 @@ class Analysis {
TraverseFilter? parseTraverseFilter(List<String> patterns) {
if (patterns.isEmpty) return null;
final aset = <int>{};
final naset = <int>{};
final aset = IntSet();
final naset = IntSet();
int bits = 0;
final fmap = <int, Set<int>>{};
final nfmap = <int, Set<int>>{};
final fmap = <int, IntSet>{};
final nfmap = <int, IntSet>{};
for (String pattern in patterns) {
final bool isNegated = pattern.startsWith('^');
if (isNegated) {
@ -418,7 +419,7 @@ class Analysis {
for (final field in klass.fields) {
if (fieldNameRegexp.hasMatch(field.name)) {
(isNegated ? nfmap : fmap)
.putIfAbsent(cid, _buildSet)
.putIfAbsent(cid, IntSet.new)
.add(field.index);
}
}
@ -443,21 +444,20 @@ class Analysis {
}
/// Returns set of objects from [objectIds] whose class id is in [cids].
Set<int> filterByClassId(Set<int> objectIds, Set<int> cids) {
IntSet filterByClassId(IntSet objectIds, IntSet cids) {
return filter(objectIds, (object) => cids.contains(object.classId));
}
/// Returns set of objects from [objectIds] whose class id is in [cids].
Set<int> filterByClassPatterns(Set<int> objectIds, List<String> patterns) {
IntSet filterByClassPatterns(IntSet objectIds, List<String> patterns) {
final tfilter = parseTraverseFilter(patterns);
if (tfilter == null) return objectIds;
return filter(objectIds, tfilter._shouldFilterObject);
}
/// Returns set of objects from [objectIds] whose class id is in [cids].
Set<int> filter(
Set<int> objectIds, bool Function(HeapSnapshotObject) filter) {
final result = <int>{};
IntSet filter(IntSet objectIds, bool Function(HeapSnapshotObject) filter) {
final result = IntSet();
final objects = graph.objects;
objectIds.forEach((int objId) {
if (filter(objects[objId])) {
@ -468,11 +468,11 @@ class Analysis {
}
/// Returns users of [objs].
Set<int> findUsers(Set<int> objs, List<String> patterns) {
IntSet findUsers(IntSet objs, List<String> patterns) {
final tfilter = parseTraverseFilter(patterns);
final objects = graph.objects;
final result = <int>{};
final result = IntSet();
for (final objId in objs) {
final object = objects[objId];
final referrers = object.referrers;
@ -496,11 +496,11 @@ class Analysis {
}
/// Returns references of [objs].
Set<int> findReferences(Set<int> objs, List<String> patterns) {
IntSet findReferences(IntSet objs, List<String> patterns) {
final tfilter = parseTraverseFilter(patterns);
final objects = graph.objects;
final result = <int>{};
final result = IntSet();
for (final objId in objs) {
final object = objects[objId];
final references = object.references;
@ -625,7 +625,7 @@ class Analysis {
}
@pragma('vm:prefer-inline')
bool hasMoreThanOneAlive(Set<int> reachableObjects, Uint32List list) {
bool hasMoreThanOneAlive(IntSet reachableObjects, Uint32List list) {
int count = 0;
for (int i = 0; i < list.length; ++i) {
if (reachableObjects.contains(list[i])) {
@ -662,9 +662,9 @@ class Analysis {
Uint32List _calculateRetainers() {
final retainers = Uint32List(graph.objects.length);
var worklist = {_rootObjectIdx};
var worklist = IntSet()..add(_rootObjectIdx);
while (!worklist.isEmpty) {
final next = <int>{};
final next = IntSet();
for (final objId in worklist) {
final object = graph.objects[objId];
@ -717,11 +717,11 @@ class TraverseFilter {
final int _bits;
final Set<int>? _allowed;
final Set<int>? _disallowed;
final IntSet? _allowed;
final IntSet? _disallowed;
final Map<int, Set<int>>? _followMap;
final Map<int, Set<int>>? _notFollowMap;
final Map<int, IntSet>? _followMap;
final Map<int, IntSet>? _notFollowMap;
const TraverseFilter._(this._patterns, this._bits, this._allowed,
this._disallowed, this._followMap, this._notFollowMap);
@ -735,8 +735,8 @@ class TraverseFilter {
sb.writeln(
'The traverse filter expression "${_patterns.join(' ')}" matches:\n');
final ca = _allowed ?? const {};
final cna = _disallowed ?? const {};
final ca = _allowed ?? IntSet();
final cna = _disallowed ?? IntSet();
final klasses = graph.classes.toList()
..sort((a, b) => a.name.compareTo(b.name));
@ -747,8 +747,8 @@ class TraverseFilter {
final posEdge = [];
final negEdge = [];
final f = _followMap?[cid] ?? const {};
final nf = _notFollowMap?[cid] ?? const {};
final f = _followMap?[cid] ?? IntSet();
final nf = _notFollowMap?[cid] ?? IntSet();
for (final field in klass.fields) {
final fieldIndex = field.index;
if (f.contains(fieldIndex)) {
@ -974,5 +974,3 @@ enum _Arch {
arch64,
arch64c,
}
Set<int> _buildSet() => <int>{};

View file

@ -241,7 +241,7 @@ class InfoCommand extends SnapshotCommand {
state.output.print('Known named sets:');
final table = Table();
state.namedSets.forEach((String name, Set<int> oids) {
state.namedSets.forEach((String name, IntSet oids) {
table.addRow([name, '{#${oids.length}}']);
});
state.output.print(indent(' ', table.asString));

View file

@ -10,7 +10,7 @@ import 'analysis.dart';
import 'completion.dart';
abstract class SetExpression {
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output);
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output);
}
class FilterExpression extends SetExpression {
@ -19,7 +19,7 @@ class FilterExpression extends SetExpression {
FilterExpression(this.expr, this.patterns);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final oids = expr.evaluate(namedSets, analysis, output);
if (oids == null) return null;
@ -54,7 +54,7 @@ class DFilterExpression extends SetExpression {
DFilterExpression(this.expr, this.patterns);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final oids = expr.evaluate(namedSets, analysis, output);
if (oids == null) return null;
final predicates = patterns.map((String pattern) {
@ -129,7 +129,7 @@ class MinusExpression extends SetExpression {
MinusExpression(this.expr, this.operands);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final result = expr.evaluate(namedSets, analysis, output)?.toSet();
if (result == null) return null;
@ -148,8 +148,8 @@ class OrExpression extends SetExpression {
OrExpression(this.exprs);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final result = <int>{};
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final result = IntSet();
for (int i = 0; i < exprs.length; ++i) {
final oids = exprs[i].evaluate(namedSets, analysis, output);
if (oids == null) return null;
@ -165,11 +165,11 @@ class AndExpression extends SetExpression {
AndExpression(this.expr, this.operands);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final nullableResult = expr.evaluate(namedSets, analysis, output)?.toSet();
if (nullableResult == null) return null;
Set<int> result = nullableResult;
IntSet result = nullableResult;
for (int i = 0; i < operands.length; ++i) {
final oids = operands[i].evaluate(namedSets, analysis, output);
if (oids == null) return null;
@ -187,13 +187,13 @@ class SampleExpression extends SetExpression {
SampleExpression(this.expr, this.count);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final oids = expr.evaluate(namedSets, analysis, output);
if (oids == null) return null;
if (oids.isEmpty) return oids;
final result = <int>{};
final result = IntSet();
final l = oids.toList();
while (result.length < count && result.length < oids.length) {
result.add(l[_random.nextInt(oids.length)]);
@ -209,7 +209,7 @@ class ClosureExpression extends SetExpression {
ClosureExpression(this.expr, this.patterns);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final roots = expr.evaluate(namedSets, analysis, output);
if (roots == null) return null;
@ -231,7 +231,7 @@ class UserClosureExpression extends SetExpression {
UserClosureExpression(this.expr, this.patterns);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final roots = expr.evaluate(namedSets, analysis, output);
if (roots == null) return null;
@ -246,7 +246,7 @@ class FollowExpression extends SetExpression {
FollowExpression(this.objs, this.patterns);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final oids = objs.evaluate(namedSets, analysis, output);
if (oids == null) return null;
@ -260,7 +260,7 @@ class UserFollowExpression extends SetExpression {
UserFollowExpression(this.objs, this.patterns);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final oids = objs.evaluate(namedSets, analysis, output);
if (oids == null) return null;
@ -273,7 +273,7 @@ class NamedExpression extends SetExpression {
NamedExpression(this.name);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final oids = namedSets.getSet(name);
if (oids == null) {
output.printError('"$name" does not refer to a command or named set.');
@ -289,7 +289,7 @@ class SetNameExpression extends SetExpression {
SetNameExpression(this.name, this.expr);
Set<int>? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
IntSet? evaluate(NamedSets namedSets, Analysis analysis, Output output) {
final oids = expr.evaluate(namedSets, analysis, output);
if (oids == null) return null;
@ -298,7 +298,7 @@ class SetNameExpression extends SetExpression {
}
}
Set<int>? parseAndEvaluate(
IntSet? parseAndEvaluate(
NamedSets namedSets, Analysis analysis, String text, Output output) {
final sexpr = parseExpression(text, output, namedSets.names.toSet());
if (sexpr == null) return null;
@ -611,18 +611,18 @@ List<String> _parsePatterns(_TokenIterator tokens, Output output) {
}
class NamedSets {
final Map<String, Set<int>> _namedSets = {};
final Map<String, IntSet> _namedSets = {};
int _varIndex = 0;
List<String> get names => _namedSets.keys.toList();
String nameSet(Set<int> oids, [String? id]) {
String nameSet(IntSet oids, [String? id]) {
id ??= _generateName();
_namedSets[id] = oids;
return id;
}
Set<int>? getSet(String name) => _namedSets[name];
IntSet? getSet(String name) => _namedSets[name];
bool hasSetName(String name) => _namedSets.containsKey(name);
@ -634,7 +634,7 @@ class NamedSets {
_namedSets.removeWhere((name, _) => cond(name));
}
void forEach(void Function(String, Set<int>) fun) {
void forEach(void Function(String, IntSet) fun) {
_namedSets.forEach(fun);
}

View file

@ -0,0 +1,12 @@
// 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.
// The default set implementation is based on a Uint32List+List where both are
// linear in the number of entries. That means we consume on 64-bit VMs at
// least 12 bytes per entry.
//
// We should consider making a more memory efficient hash set implementation
// that uses Int32List and utilizing the fact that we never store negative
// numbers in it.
typedef IntSet = Set<int>;

View file

@ -7,7 +7,7 @@ repository: https://github.com/dart-lang/sdk/tree/main/pkg/heapsnapshot
publish_to: none
environment:
sdk: '>=2.12.0 <3.0.0'
sdk: '>=2.15.0 <3.0.0'
dependencies:
args: ^2.0.0

View file

@ -12,7 +12,7 @@ import 'package:test/test.dart';
class FakeAnalysis implements Analysis {
@override
Set<int> get roots => <int>{1};
final IntSet roots = IntSet();
@override
dynamic noSuchMethod(Invocation i) {}