| // Copyright (c) 2019, 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:async'; | |
| import 'dart:isolate'; | |
| import 'dart:math'; | |
| import '../../../pkg/vm/bin/gen_kernel.dart' as gen_kernel; | |
| class SpawnLatency { | |
| SpawnLatency(this.name); | |
| Future<ResultMessageLatency> run() async { | |
| final completerResult = Completer(); | |
| final receivePort = ReceivePort()..listen(completerResult.complete); | |
| final isolateExitedCompleter = Completer<DateTime>(); | |
| final onExitReceivePort = ReceivePort() | |
| ..listen((_) { | |
| isolateExitedCompleter.complete(DateTime.now()); | |
| }); | |
| final beforeSpawn = DateTime.now(); | |
| await Isolate.spawn( | |
| isolateCompiler, | |
| StartMessageLatency(receivePort.sendPort, beforeSpawn), | |
| onExit: onExitReceivePort.sendPort, | |
| onError: onExitReceivePort.sendPort, | |
| ); | |
| final afterSpawn = DateTime.now(); | |
| final ResultMessageLatency result = await completerResult.future; | |
| receivePort.close(); | |
| final DateTime isolateExited = await isolateExitedCompleter.future; | |
| result.timeToExitUs = isolateExited.difference(beforeSpawn).inMicroseconds; | |
| result.timeToIsolateSpawnUs = afterSpawn | |
| .difference(beforeSpawn) | |
| .inMicroseconds; | |
| onExitReceivePort.close(); | |
| return result; | |
| } | |
| Future<AggregatedResultMessageLatency> measureFor(int minimumMillis) async { | |
| final minimumMicros = minimumMillis * 1000; | |
| final watch = Stopwatch()..start(); | |
| final Metric toAfterIsolateSpawnUs = LatencyMetric('${name}ToAfterSpawn'); | |
| final Metric toStartRunningCodeUs = LatencyMetric('${name}ToStartRunning'); | |
| final Metric toFinishRunningCodeUs = LatencyMetric( | |
| '${name}ToFinishRunning', | |
| ); | |
| final Metric toExitUs = LatencyMetric('${name}ToExit'); | |
| while (watch.elapsedMicroseconds < minimumMicros) { | |
| final result = await run(); | |
| toAfterIsolateSpawnUs.add(result.timeToIsolateSpawnUs); | |
| toStartRunningCodeUs.add(result.timeToStartRunningCodeUs); | |
| toFinishRunningCodeUs.add(result.timeToFinishRunningCodeUs); | |
| toExitUs.add(result.timeToExitUs); | |
| } | |
| return AggregatedResultMessageLatency( | |
| toAfterIsolateSpawnUs, | |
| toStartRunningCodeUs, | |
| toFinishRunningCodeUs, | |
| toExitUs, | |
| ); | |
| } | |
| Future<AggregatedResultMessageLatency> measure() async { | |
| await measureFor(500); // warm-up | |
| return measureFor(4000); // actual measurement | |
| } | |
| Future<void> report() async { | |
| final result = await measure(); | |
| print(result); | |
| } | |
| final String name; | |
| late RawReceivePort receivePort; | |
| } | |
| class Metric { | |
| Metric({required this.prefix, required this.suffix}); | |
| void add(int value) { | |
| if (value > max) { | |
| max = value; | |
| } | |
| sum += value; | |
| sumOfSquares += value * value; | |
| count++; | |
| } | |
| double _average() => sum / count; | |
| double _rms() => sqrt(sumOfSquares / count); | |
| @override | |
| String toString() => | |
| '$prefix): ${_average()}$suffix\n' | |
| '${prefix}Max): $max$suffix\n' | |
| '${prefix}RMS): ${_rms()}$suffix'; | |
| final String prefix; | |
| final String suffix; | |
| int max = 0; | |
| double sum = 0; | |
| double sumOfSquares = 0; | |
| int count = 0; | |
| } | |
| class LatencyMetric extends Metric { | |
| LatencyMetric(String name) : super(prefix: '$name(Latency', suffix: ' us.'); | |
| } | |
| class StartMessageLatency { | |
| StartMessageLatency(this.sendPort, this.spawned); | |
| final SendPort sendPort; | |
| final DateTime spawned; | |
| } | |
| class ResultMessageLatency { | |
| ResultMessageLatency({ | |
| required this.timeToStartRunningCodeUs, | |
| required this.timeToFinishRunningCodeUs, | |
| }); | |
| final int timeToStartRunningCodeUs; | |
| final int timeToFinishRunningCodeUs; | |
| late int timeToIsolateSpawnUs; | |
| late int timeToExitUs; | |
| } | |
| class AggregatedResultMessageLatency { | |
| AggregatedResultMessageLatency( | |
| this.toAfterIsolateSpawnUs, | |
| this.toStartRunningCodeUs, | |
| this.toFinishRunningCodeUs, | |
| this.toExitUs, | |
| ); | |
| @override | |
| String toString() => | |
| '''$toAfterIsolateSpawnUs | |
| $toStartRunningCodeUs | |
| $toFinishRunningCodeUs | |
| $toExitUs'''; | |
| final Metric toAfterIsolateSpawnUs; | |
| final Metric toStartRunningCodeUs; | |
| final Metric toFinishRunningCodeUs; | |
| final Metric toExitUs; | |
| } | |
| Future<void> isolateCompiler(StartMessageLatency start) async { | |
| final timeRunningCodeUs = DateTime.now(); | |
| await runZoned( | |
| () => gen_kernel.compile(<String>[ | |
| 'benchmarks/IsolateSpawn/dart/helloworld.dart', | |
| 'benchmarks/IsolateSpawn/dart/helloworld.dart.dill', | |
| ]), | |
| zoneSpecification: ZoneSpecification( | |
| print: (Zone self, ZoneDelegate parent, Zone zone, String line) {}, | |
| ), | |
| ); | |
| final timeFinishRunningCodeUs = DateTime.now(); | |
| start.sendPort.send( | |
| ResultMessageLatency( | |
| timeToStartRunningCodeUs: timeRunningCodeUs | |
| .difference(start.spawned) | |
| .inMicroseconds, | |
| timeToFinishRunningCodeUs: timeFinishRunningCodeUs | |
| .difference(start.spawned) | |
| .inMicroseconds, | |
| ), | |
| ); | |
| } | |
| Future<void> main() async { | |
| await SpawnLatency('IsolateSpawn.Dart2JS').report(); | |
| } |