mirror of
https://github.com/dart-lang/sdk
synced 2024-10-06 13:08:01 +00:00
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:
parent
3931dc6b08
commit
756379442e
|
@ -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>{};
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
12
runtime/tools/heapsnapshot/lib/src/intset.dart
Normal file
12
runtime/tools/heapsnapshot/lib/src/intset.dart
Normal 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>;
|
|
@ -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
|
||||
|
|
|
@ -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) {}
|
||||
|
|
Loading…
Reference in a new issue