7
7
/// and which symbols decreased in size.
8
8
library vm.snapshot.compare;
9
9
10
- import 'dart:convert' ;
11
10
import 'dart:io' ;
12
11
import 'dart:math' as math;
13
12
14
13
import 'package:args/command_runner.dart' ;
15
14
15
+ import 'package:vm/snapshot/instruction_sizes.dart' ;
16
+ import 'package:vm/snapshot/program_info.dart' ;
17
+
16
18
class CompareCommand extends Command <void > {
17
19
@override
18
20
final String name = 'compare' ;
@@ -32,10 +34,26 @@ Use --narrow flag to limit column widths.''';
32
34
super .invocation.replaceAll ('[arguments]' , '<old.json> <new.json>' );
33
35
34
36
CompareCommand () {
35
- argParser.addOption ('column-width' ,
36
- help: 'Truncate column content to the given width'
37
- ' (${AsciiTable .unlimitedWidth } means do not truncate).' ,
38
- defaultsTo: AsciiTable .unlimitedWidth.toString ());
37
+ argParser
38
+ ..addOption ('column-width' ,
39
+ help: 'Truncate column content to the given width'
40
+ ' (${AsciiTable .unlimitedWidth } means do not truncate).' ,
41
+ defaultsTo: AsciiTable .unlimitedWidth.toString ())
42
+ ..addOption ('granularity' ,
43
+ help: 'Choose the granularity of the output.' ,
44
+ allowed: ['method' , 'class' , 'library' , 'package' ],
45
+ defaultsTo: 'method' )
46
+ ..addFlag ('collapse-anonymous-closures' , help: '''
47
+ Collapse all anonymous closures from the same scope into a single entry.
48
+ When comparing size of AOT snapshots for two different versions of a
49
+ program there is no reliable way to precisely establish which two anonymous
50
+ closures are the same and should be compared in size - so
51
+ comparison might produce a noisy output. This option reduces confusion
52
+ by collapsing different anonymous closures within the same scope into a
53
+ single entry. Note that when comparing the same application compiled
54
+ with two different versions of an AOT compiler closures can be distinguished
55
+ precisely based on their source position (which is included in their name).
56
+ ''' );
39
57
}
40
58
41
59
@override
@@ -53,110 +71,103 @@ Use --narrow flag to limit column widths.''';
53
71
54
72
final oldJsonPath = _checkExists (argResults.rest[0 ]);
55
73
final newJsonPath = _checkExists (argResults.rest[1 ]);
56
- printComparison (oldJsonPath, newJsonPath, maxWidth: maxWidth);
74
+ printComparison (oldJsonPath, newJsonPath,
75
+ maxWidth: maxWidth,
76
+ granularity: _parseHistogramType (argResults['granularity' ]),
77
+ collapseAnonymousClosures: argResults['collapse-anonymous-closures' ]);
78
+ }
79
+
80
+ HistogramType _parseHistogramType (String value) {
81
+ switch (value) {
82
+ case 'method' :
83
+ return HistogramType .bySymbol;
84
+ case 'class' :
85
+ return HistogramType .byClass;
86
+ case 'library' :
87
+ return HistogramType .byLibrary;
88
+ case 'package' :
89
+ return HistogramType .byPackage;
90
+ }
57
91
}
58
92
59
- String _checkExists (String path) {
60
- if (! File (path).existsSync ()) {
93
+ File _checkExists (String path) {
94
+ final file = File (path);
95
+ if (! file.existsSync ()) {
61
96
usageException ('File $path does not exist!' );
62
97
}
63
- return path ;
98
+ return file ;
64
99
}
65
100
}
66
101
67
- void printComparison (String oldJsonPath, String newJsonPath,
68
- {int maxWidth: 0 }) {
69
- final oldSizes = loadSymbolSizes (oldJsonPath);
70
- final newSizes = loadSymbolSizes (newJsonPath);
71
-
102
+ void printComparison (File oldJson, File newJson,
103
+ {int maxWidth: 0 ,
104
+ bool collapseAnonymousClosures = false ,
105
+ HistogramType granularity = HistogramType .bySymbol}) async {
106
+ final oldSizes = await loadProgramInfo (oldJson,
107
+ collapseAnonymousClosures: collapseAnonymousClosures);
108
+ final newSizes = await loadProgramInfo (newJson,
109
+ collapseAnonymousClosures: collapseAnonymousClosures);
110
+ final diff = computeDiff (oldSizes, newSizes);
111
+
112
+ // Compute total sizes.
72
113
var totalOld = 0 ;
114
+ oldSizes.visit ((_, __, ___, size) {
115
+ totalOld += size;
116
+ });
117
+
73
118
var totalNew = 0 ;
119
+ newSizes.visit ((_, __, ___, size) {
120
+ totalNew += size;
121
+ });
122
+
74
123
var totalDiff = 0 ;
75
- final diffBySymbol = < String , int > {};
76
-
77
- // Process all symbols (from both old and new results) and compute the change
78
- // in size. If symbol is not present in the compilation assume its size to be
79
- // zero.
80
- for (var key in Set <String >()..addAll (newSizes.keys)..addAll (oldSizes.keys)) {
81
- final oldSize = oldSizes[key] ?? 0 ;
82
- final newSize = newSizes[key] ?? 0 ;
83
- final diff = newSize - oldSize;
84
- if (diff != 0 ) diffBySymbol[key] = diff;
85
- totalOld += oldSize;
86
- totalNew += newSize;
87
- totalDiff += diff;
88
- }
124
+ diff.visit ((_, __, ___, size) {
125
+ totalDiff += size.inBytes;
126
+ });
89
127
90
- // Compute the list of changed symbols sorted by difference (descending) .
91
- final changedSymbolsBySize = diffBySymbol.keys. toList ();
92
- changedSymbolsBySize. sort ((a, b ) => diffBySymbol[b] - diffBySymbol[a] );
128
+ // Compute histogram .
129
+ final histogram = SizesHistogram . from < SymbolDiff >(
130
+ diff, (diff ) => diff.inBytes, granularity );
93
131
94
132
// Now produce the report table.
95
133
const numLargerSymbolsToReport = 30 ;
96
134
const numSmallerSymbolsToReport = 10 ;
97
135
final table = AsciiTable (header: [
98
- Text .left ('Library' ),
99
- Text .left ('Method' ),
136
+ for (var col in histogram.bucketing.nameComponents) Text .left (col),
100
137
Text .right ('Diff (Bytes)' )
101
138
], maxWidth: maxWidth);
102
139
103
140
// Report [numLargerSymbolsToReport] symbols that increased in size most.
104
- for (var key in changedSymbolsBySize
105
- .where ((k) => diffBySymbol [k] > 0 )
141
+ for (var key in histogram.bySize
142
+ .where ((k) => histogram.buckets [k] > 0 )
106
143
.take (numLargerSymbolsToReport)) {
107
- final name = key.split (librarySeparator);
108
- table.addRow ([name[0 ], name[1 ], '+${diffBySymbol [key ]}' ]);
144
+ table.addRow ([
145
+ ...histogram.bucketing.namesFromBucket (key),
146
+ '+${histogram .buckets [key ]}'
147
+ ]);
109
148
}
110
149
table.addSeparator (Separator .Wave );
111
150
112
151
// Report [numSmallerSymbolsToReport] symbols that decreased in size most.
113
- for (var key in changedSymbolsBySize .reversed
114
- .where ((k) => diffBySymbol [k] < 0 )
152
+ for (var key in histogram.bySize .reversed
153
+ .where ((k) => histogram.buckets [k] < 0 )
115
154
.take (numSmallerSymbolsToReport)
116
155
.toList ()
117
156
.reversed) {
118
- final name = key.split (librarySeparator);
119
- table.addRow ([name[0 ], name[1 ], '${diffBySymbol [key ]}' ]);
157
+ table.addRow ([
158
+ ...histogram.bucketing.namesFromBucket (key),
159
+ '${histogram .buckets [key ]}'
160
+ ]);
120
161
}
121
162
table.addSeparator ();
122
163
123
164
table.render ();
124
- print ('Comparing ${oldJsonPath } (old) to ${newJsonPath } (new)' );
165
+ print ('Comparing ${oldJson . path } (old) to ${newJson . path } (new)' );
125
166
print ('Old : ${totalOld } bytes.' );
126
167
print ('New : ${totalNew } bytes.' );
127
168
print ('Change: ${totalDiff > 0 ? '+' : '' }${totalDiff } bytes.' );
128
169
}
129
170
130
- /// A combination of characters that is unlikely to occur in the symbol name.
131
- const String librarySeparator = ',' ;
132
-
133
- /// Load --print-instructions-sizes-to output as a mapping from symbol names
134
- /// to their sizes.
135
- ///
136
- /// Note: we produce a single symbol name from function name and library name
137
- /// by concatenating them with [librarySeparator] .
138
- Map <String , int > loadSymbolSizes (String name) {
139
- final symbols = jsonDecode (File (name).readAsStringSync ());
140
- final result = new Map <String , int >();
141
- final regexp = new RegExp (r"0x[a-fA-F0-9]+" );
142
- for (int i = 0 , n = symbols.length; i < n; i++ ) {
143
- final e = symbols[i];
144
- // Obtain a key by combining library and method name. Strip anything
145
- // after the library separator to make sure we can easily decode later.
146
- // For method names, also remove non-deterministic parts to avoid
147
- // reporting non-existing differences against the same layout.
148
- String lib = ((e['l' ] ?? '' ).split (librarySeparator))[0 ];
149
- String name = (e['n' ].split (librarySeparator))[0 ]
150
- .replaceAll ('[Optimized] ' , '' )
151
- .replaceAll (regexp, '' );
152
- String key = lib + librarySeparator + name;
153
- int val = e['s' ];
154
- result[key] =
155
- (result[key] ?? 0 ) + val; // add (key,val), accumulate if exists
156
- }
157
- return result;
158
- }
159
-
160
171
/// A row in the [AsciiTable] .
161
172
abstract class Row {
162
173
String render (List <int > widths, List <AlignmentDirection > alignments);
0 commit comments