Skip to content

Commit 9338d3c

Browse files
scheglovCommit Queue
authored andcommitted
Fine. Script to run change, analyze, compare with / without fine-grained dependencies.
Paths are intentionally hardcoded, this is not a general purpose tool, but a special purpose helper for a limited set of developers. Change-Id: I102457eb4fdc8eb131c5552b4ac07abb37fe4dac Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/455240 Reviewed-by: Paul Berry <[email protected]> Commit-Queue: Konstantin Shcheglov <[email protected]>
1 parent f702e9b commit 9338d3c

File tree

1 file changed

+288
-0
lines changed

1 file changed

+288
-0
lines changed
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/file_system/overlay_file_system.dart';
6+
import 'package:analyzer/file_system/physical_file_system.dart';
7+
import 'package:analyzer/src/dart/analysis/analysis_context_collection.dart';
8+
import 'package:analyzer/src/dart/analysis/byte_store.dart';
9+
import 'package:analyzer/src/dart/analysis/driver.dart';
10+
import 'package:analyzer/src/dart/analysis/file_content_cache.dart';
11+
import 'package:analyzer/src/util/performance/operation_performance.dart';
12+
import 'package:collection/collection.dart';
13+
import 'package:linter/src/rules.dart';
14+
15+
void main() async {
16+
const repeatCount = 2;
17+
const planRepeatCount = 2;
18+
var planCollection = planCollectionFlutter;
19+
20+
registerLintRules();
21+
22+
var updateIndex = 0;
23+
for (var withFine in List.filled(repeatCount, [false, true]).flattened) {
24+
var resourceProvider = OverlayResourceProvider(
25+
PhysicalResourceProvider.INSTANCE,
26+
);
27+
28+
var collection = AnalysisContextCollectionImpl(
29+
resourceProvider: resourceProvider,
30+
sdkPath: Paths.sdkRun,
31+
includedPaths: planCollection.includedPaths,
32+
byteStore: MemoryByteStore(),
33+
fileContentCache: FileContentCache(resourceProvider),
34+
withFineDependencies: withFine,
35+
);
36+
37+
// Add all Dart files.
38+
for (var analysisContext in collection.contexts) {
39+
for (var path in analysisContext.contextRoot.analyzedFiles()) {
40+
if (path.endsWith('.dart')) {
41+
analysisContext.driver.addFile(path);
42+
}
43+
}
44+
}
45+
46+
var initialAnalysisTimer = Stopwatch()..start();
47+
collection.scheduler.resetAccumulatedPerformance();
48+
await collection.scheduler.waitForIdle();
49+
await pumpEventQueue();
50+
initialAnalysisTimer.stop();
51+
(withFine
52+
? planCollection.initialAnalysisWithFineTrue
53+
: planCollection.initialAnalysisWithFineFalse)
54+
.add(initialAnalysisTimer.elapsed);
55+
56+
{
57+
print('\n' * 3);
58+
print(
59+
'[withFine: $withFine] Initial analysis, '
60+
'${initialAnalysisTimer.elapsedMilliseconds} ms',
61+
);
62+
print('-' * 64);
63+
64+
var buffer = StringBuffer();
65+
collection.scheduler.accumulatedPerformance.write(buffer: buffer);
66+
print(buffer.toString().trim());
67+
}
68+
69+
for (var plan in planCollection.plans) {
70+
var targetPath = plan.filePath;
71+
var targetCode = PhysicalResourceProvider.INSTANCE
72+
.getFile(targetPath)
73+
.readAsStringSync();
74+
for (var i = 0; i < planRepeatCount; i++) {
75+
// Update.
76+
var replacement = plan.replacementTemplate.replaceAll(
77+
'#[UI]',
78+
'${updateIndex++}',
79+
);
80+
resourceProvider.setOverlay(
81+
targetPath,
82+
content: targetCode.replaceAll(plan.searchText, replacement),
83+
modificationStamp: 0,
84+
);
85+
for (var analysisContext in collection.contexts) {
86+
analysisContext.changeFile(targetPath);
87+
}
88+
89+
// Measure.
90+
var timer = Stopwatch()..start();
91+
collection.scheduler.resetAccumulatedPerformance();
92+
await collection.scheduler.waitForIdle();
93+
await pumpEventQueue();
94+
{
95+
print('\n' * 3);
96+
print('[withFine: $withFine][$i] $targetPath');
97+
print(' searchText: ${plan.searchText}');
98+
print(' replacement: $replacement');
99+
var elapsed = timer.elapsed;
100+
print(' timer: ${elapsed.inMilliseconds} ms');
101+
print('-' * 64);
102+
103+
(withFine ? plan.withFineTrue : plan.withFineFalse).add(elapsed);
104+
105+
var buffer = StringBuffer();
106+
collection.scheduler.accumulatedPerformance.write(buffer: buffer);
107+
print(buffer.toString().trim());
108+
}
109+
110+
// Revert.
111+
{
112+
resourceProvider.setOverlay(
113+
targetPath,
114+
content: targetCode,
115+
modificationStamp: 1,
116+
);
117+
for (var analysisContext in collection.contexts) {
118+
analysisContext.changeFile(targetPath);
119+
}
120+
await collection.scheduler.waitForIdle();
121+
await pumpEventQueue();
122+
print('-' * 32);
123+
print('[reverted][waitForIdle]');
124+
}
125+
}
126+
}
127+
}
128+
129+
print('\n' * 3);
130+
print('${'-' * 64} results');
131+
_printDurations(
132+
'Initial analysis',
133+
planCollection.initialAnalysisWithFineFalse,
134+
planCollection.initialAnalysisWithFineTrue,
135+
);
136+
print('');
137+
138+
for (var plan in planCollection.plans) {
139+
_printDurations(plan.filePath, plan.withFineFalse, plan.withFineTrue);
140+
print('');
141+
}
142+
print('\n' * 2);
143+
}
144+
145+
final planCollectionAnalyzer = PlanCollection(
146+
includedPaths: [Paths.sdkAnalyzer],
147+
plans: [
148+
Plan(
149+
filePath: '${Paths.sdkAnalyzer}/lib/src/fine/library_manifest.dart',
150+
searchText: 'computeManifests({',
151+
replacementTemplate: 'computeManifests#[UI]({',
152+
),
153+
],
154+
);
155+
156+
final planCollectionFlutter = PlanCollection(
157+
includedPaths: [Paths.flutterPackage],
158+
plans: [
159+
Plan(
160+
filePath:
161+
'${Paths.flutterPackage}/lib/src/foundation/memory_allocations.dart',
162+
searchText: 'dispatchObjectEvent(ObjectEvent event) {',
163+
replacementTemplate: 'dispatchObjectEvent#[UI](ObjectEvent event) {',
164+
),
165+
Plan(
166+
filePath: '${Paths.flutterPackage}/lib/src/painting/image_cache.dart',
167+
searchText: 'containsKey(Object key) {',
168+
replacementTemplate: 'containsKey#[UI](Object key) {',
169+
),
170+
Plan(
171+
filePath: '${Paths.flutterPackage}/lib/src/widgets/banner.dart',
172+
searchText: 'shouldRepaint(BannerPainter oldDelegate) {',
173+
replacementTemplate: 'shouldRepaint#[UI](BannerPainter oldDelegate) {',
174+
),
175+
],
176+
);
177+
178+
Future pumpEventQueue([int times = 5000]) {
179+
if (times == 0) return Future.value();
180+
return Future.delayed(Duration.zero, () => pumpEventQueue(times - 1));
181+
}
182+
183+
String _formatFineDelta(
184+
Durations fineFalseDurations,
185+
Durations fineTrueDurations,
186+
) {
187+
var baseMs = fineFalseDurations.best.inMilliseconds;
188+
var fineMs = fineTrueDurations.best.inMilliseconds;
189+
190+
if (baseMs == 0 && fineMs == 0) {
191+
return ' fine-grained: undefined (time $baseMs → $fineMs ms)';
192+
}
193+
194+
var deltaMs = fineMs - baseMs;
195+
if (deltaMs == 0) {
196+
return ' fine-grained: no change (time $baseMs → $fineMs ms)';
197+
}
198+
199+
var percent = (deltaMs / baseMs) * 100;
200+
var percentStr = '${percent >= 0 ? '+' : ''}${percent.toStringAsFixed(1)}%';
201+
var timeStr = 'time $baseMs → $fineMs ms';
202+
203+
if (fineMs < baseMs) {
204+
var ratio = baseMs / fineMs;
205+
return ' fine-grained: ${ratio.toStringAsFixed(1)}× faster '
206+
'($timeStr; $percentStr)';
207+
} else {
208+
var ratio = fineMs / baseMs;
209+
return ' fine-grained: ${ratio.toStringAsFixed(1)}× slower '
210+
'($timeStr; $percentStr)';
211+
}
212+
}
213+
214+
void _printDurations(
215+
String title,
216+
Durations fineFalseDurations,
217+
Durations fineTrueDurations,
218+
) {
219+
print(title);
220+
print(fineFalseDurations.format('[withFine: false]'));
221+
print(fineTrueDurations.format('[withFine: true ]'));
222+
print(_formatFineDelta(fineFalseDurations, fineTrueDurations));
223+
}
224+
225+
class Durations {
226+
final List<Duration> values = [];
227+
228+
Duration get best {
229+
if (values.isEmpty) {
230+
return Duration.zero;
231+
}
232+
return values.min;
233+
}
234+
235+
void add(Duration value) {
236+
values.add(value);
237+
}
238+
239+
String format(String title) {
240+
return ' $title, '
241+
'best: ${best.inMilliseconds} ms, '
242+
'all: ${values.map((e) => e.inMilliseconds).toList()}';
243+
}
244+
}
245+
246+
class Paths {
247+
static const sdkRun = '/Users/scheglov/Applications/dart-sdk';
248+
249+
static const sdkRepo = '/Users/scheglov/Source/Dart/sdk.git/sdk';
250+
static const sdkAnalyzer = '$sdkRepo/pkg/analyzer';
251+
static const sdkAnalysisServer = '$sdkRepo/pkg/analysis_server';
252+
static const sdkLinter = '$sdkRepo/pkg/linter';
253+
254+
static const flutterRepo = '/Users/scheglov/Source/flutter';
255+
static const flutterPackage = '$flutterRepo/packages/flutter';
256+
}
257+
258+
class Plan {
259+
final String filePath;
260+
final String searchText;
261+
final String replacementTemplate;
262+
263+
final Durations withFineFalse = Durations();
264+
final Durations withFineTrue = Durations();
265+
266+
Plan({
267+
required this.filePath,
268+
required this.searchText,
269+
required this.replacementTemplate,
270+
});
271+
}
272+
273+
class PlanCollection {
274+
final List<String> includedPaths;
275+
final List<Plan> plans;
276+
277+
final Durations initialAnalysisWithFineFalse = Durations();
278+
final Durations initialAnalysisWithFineTrue = Durations();
279+
280+
PlanCollection({required this.includedPaths, required this.plans});
281+
}
282+
283+
extension AnalysisDriverSchedulerPerformance on AnalysisDriverScheduler {
284+
/// Reset the accumulated scheduler performance to a fresh operation.
285+
void resetAccumulatedPerformance() {
286+
accumulatedPerformance = OperationPerformanceImpl('<scheduler>');
287+
}
288+
}

0 commit comments

Comments
 (0)