mirror of
https://github.com/dart-lang/sdk
synced 2024-10-03 00:09:20 +00:00
[vm/concurrency] Add EventLoopLatencyJson benchmarks
Issue https://github.com/dart-lang/sdk/issues/36097 Change-Id: Ic364e7dccc15bc14832ce61e45ba6c0e241dbbc3 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/162509 Commit-Queue: Martin Kustermann <kustermann@google.com> Reviewed-by: Alexander Aprelev <aam@google.com>
This commit is contained in:
parent
371476bf84
commit
2935fecccb
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2020, 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:isolate';
|
||||
|
||||
import 'json_benchmark.dart';
|
||||
import 'latency.dart';
|
||||
|
||||
main() async {
|
||||
// Start GC pressure from helper isolate.
|
||||
final exitPort = ReceivePort();
|
||||
final exitFuture = exitPort.first;
|
||||
final isolate =
|
||||
await Isolate.spawn(runSplay, null, onExit: exitPort.sendPort);
|
||||
|
||||
// Measure event loop latency.
|
||||
const tickDuration = const Duration(milliseconds: 1);
|
||||
const numberOfTicks = 8 * 1000; // min 8 seconds.
|
||||
final EventLoopLatencyStats stats =
|
||||
await measureEventLoopLatency(tickDuration, numberOfTicks);
|
||||
|
||||
// Kill isolate & wait until it's dead.
|
||||
isolate.kill(priority: Isolate.immediate);
|
||||
await exitFuture;
|
||||
|
||||
// Report event loop latency statistics.
|
||||
stats.report('EventLoopLatencyJson');
|
||||
}
|
||||
|
||||
void runSplay(dynamic msg) async {
|
||||
while (true) {
|
||||
JsonRoundTripBenchmark().run();
|
||||
}
|
||||
}
|
47
benchmarks/EventLoopLatencyJson/dart/json_benchmark.dart
Normal file
47
benchmarks/EventLoopLatencyJson/dart/json_benchmark.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2020, 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:math';
|
||||
import 'dart:convert';
|
||||
|
||||
class JsonRoundTripBenchmark {
|
||||
void run() {
|
||||
final res = json.decode(jsonData);
|
||||
final out = json.encode(res);
|
||||
if (out[0] != jsonData[0]) {
|
||||
throw 'json conversion error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Builds around 4.5 MB of json data - big enough so the decoded object graph
|
||||
// does not fit into new space.
|
||||
final String jsonData = () {
|
||||
final rnd = Random(42);
|
||||
dynamic buildTree(int depth) {
|
||||
final int coin = rnd.nextInt(1000);
|
||||
if (depth == 0) {
|
||||
if (coin % 2 == 0) return coin;
|
||||
return 'foo-$coin';
|
||||
}
|
||||
|
||||
if (coin % 2 == 0) {
|
||||
final map = <String, dynamic>{};
|
||||
final int length = rnd.nextInt(18);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
map['bar-$i'] = buildTree(depth - 1);
|
||||
}
|
||||
return map;
|
||||
} else {
|
||||
final list = <dynamic>[];
|
||||
final int length = rnd.nextInt(18);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
list.add(buildTree(depth - 1));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
return json.encode({'data': buildTree(6)});
|
||||
}();
|
135
benchmarks/EventLoopLatencyJson/dart/latency.dart
Normal file
135
benchmarks/EventLoopLatencyJson/dart/latency.dart
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2020, 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:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// Measures event loop responsiveness.
|
||||
///
|
||||
/// Schedules new timer events, [tickDuration] in the future, and measures how
|
||||
/// long it takes for these events to actually arrive.
|
||||
///
|
||||
/// Runs [numberOfTicks] times before completing with [EventLoopLatencyStats].
|
||||
Future<EventLoopLatencyStats> measureEventLoopLatency(
|
||||
Duration tickDuration, int numberOfTicks) {
|
||||
final completer = Completer<EventLoopLatencyStats>();
|
||||
|
||||
final tickDurationInUs = tickDuration.inMicroseconds;
|
||||
final buffer = _TickLatencies(numberOfTicks);
|
||||
final sw = Stopwatch()..start();
|
||||
int lastTimestamp = 0;
|
||||
|
||||
void trigger() {
|
||||
final int currentTimestamp = sw.elapsedMicroseconds;
|
||||
|
||||
// Every tick we missed to schedule we'll add with difference to when we
|
||||
// would've scheduled it and when we became responsive again.
|
||||
bool done = false;
|
||||
while (!done && lastTimestamp < (currentTimestamp - tickDurationInUs)) {
|
||||
done = !buffer.add(currentTimestamp - lastTimestamp - tickDurationInUs);
|
||||
lastTimestamp += tickDurationInUs;
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
lastTimestamp = currentTimestamp;
|
||||
Timer(tickDuration, trigger);
|
||||
} else {
|
||||
completer.complete(buffer.makeStats());
|
||||
}
|
||||
}
|
||||
|
||||
Timer(tickDuration, trigger);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Result of the event loop latency measurement.
|
||||
class EventLoopLatencyStats {
|
||||
/// Minimum latency between scheduling a tick and it's arrival (in ms).
|
||||
final double minLatency;
|
||||
|
||||
/// Average latency between scheduling a tick and it's arrival (in ms).
|
||||
final double avgLatency;
|
||||
|
||||
/// Maximum latency between scheduling a tick and it's arrival (in ms).
|
||||
final double maxLatency;
|
||||
|
||||
/// The 50th percentile (median) (in ms).
|
||||
final double percentile50th;
|
||||
|
||||
/// The 90th percentile (in ms).
|
||||
final double percentile90th;
|
||||
|
||||
/// The 95th percentile (in ms).
|
||||
final double percentile95th;
|
||||
|
||||
/// The 99th percentile (in ms).
|
||||
final double percentile99th;
|
||||
|
||||
/// The maximum RSS of the process.
|
||||
final int maxRss;
|
||||
|
||||
EventLoopLatencyStats(
|
||||
this.minLatency,
|
||||
this.avgLatency,
|
||||
this.maxLatency,
|
||||
this.percentile50th,
|
||||
this.percentile90th,
|
||||
this.percentile95th,
|
||||
this.percentile99th,
|
||||
this.maxRss);
|
||||
|
||||
void report(String name) {
|
||||
print('$name.Min(RunTime): $minLatency ms.');
|
||||
print('$name.Avg(RunTime): $avgLatency ms.');
|
||||
print('$name.Percentile50(RunTime): $percentile50th ms.');
|
||||
print('$name.Percentile90(RunTime): $percentile90th ms.');
|
||||
print('$name.Percentile95(RunTime): $percentile95th ms.');
|
||||
print('$name.Percentile99(RunTime): $percentile99th ms.');
|
||||
print('$name.Max(RunTime): $maxLatency ms.');
|
||||
print('$name.MaxRss(MemoryUse): $maxRss');
|
||||
}
|
||||
}
|
||||
|
||||
/// Accumulates tick latencies and makes statistics for it.
|
||||
class _TickLatencies {
|
||||
final Uint64List _timestamps;
|
||||
int _index = 0;
|
||||
|
||||
_TickLatencies(int numberOfTicks) : _timestamps = Uint64List(numberOfTicks);
|
||||
|
||||
/// Returns `true` while the buffer has not been filled yet.
|
||||
bool add(int latencyInUs) {
|
||||
_timestamps[_index++] = latencyInUs;
|
||||
return _index < _timestamps.length;
|
||||
}
|
||||
|
||||
EventLoopLatencyStats makeStats() {
|
||||
if (_index != _timestamps.length) {
|
||||
throw 'Buffer has not been fully filled yet.';
|
||||
}
|
||||
|
||||
_timestamps.sort();
|
||||
final length = _timestamps.length;
|
||||
final double avg = _timestamps.fold(0, (int a, int b) => a + b) / length;
|
||||
final int min = _timestamps.fold(0x7fffffffffffffff, math.min);
|
||||
final int max = _timestamps.fold(0, math.max);
|
||||
final percentile50th = _timestamps[50 * length ~/ 100];
|
||||
final percentile90th = _timestamps[90 * length ~/ 100];
|
||||
final percentile95th = _timestamps[95 * length ~/ 100];
|
||||
final percentile99th = _timestamps[99 * length ~/ 100];
|
||||
|
||||
return EventLoopLatencyStats(
|
||||
min / 1000,
|
||||
avg / 1000,
|
||||
max / 1000,
|
||||
percentile50th / 1000,
|
||||
percentile90th / 1000,
|
||||
percentile95th / 1000,
|
||||
percentile99th / 1000,
|
||||
ProcessInfo.maxRss);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright (c) 2020, 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:isolate';
|
||||
|
||||
import 'json_benchmark.dart';
|
||||
import 'latency.dart';
|
||||
|
||||
main() async {
|
||||
// Start GC pressure from helper isolate.
|
||||
final exitPort = ReceivePort();
|
||||
final exitFuture = exitPort.first;
|
||||
final isolate =
|
||||
await Isolate.spawn(runSplay, null, onExit: exitPort.sendPort);
|
||||
|
||||
// Measure event loop latency.
|
||||
const tickDuration = const Duration(milliseconds: 1);
|
||||
const numberOfTicks = 8 * 1000; // min 8 seconds.
|
||||
final EventLoopLatencyStats stats =
|
||||
await measureEventLoopLatency(tickDuration, numberOfTicks);
|
||||
|
||||
// Kill isolate & wait until it's dead.
|
||||
isolate.kill(priority: Isolate.immediate);
|
||||
await exitFuture;
|
||||
|
||||
// Report event loop latency statistics.
|
||||
stats.report('EventLoopLatencyJson');
|
||||
}
|
||||
|
||||
void runSplay(dynamic msg) async {
|
||||
while (true) {
|
||||
JsonRoundTripBenchmark().run();
|
||||
}
|
||||
}
|
47
benchmarks/EventLoopLatencyJson/dart2/json_benchmark.dart
Normal file
47
benchmarks/EventLoopLatencyJson/dart2/json_benchmark.dart
Normal file
|
@ -0,0 +1,47 @@
|
|||
// Copyright (c) 2020, 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:math';
|
||||
import 'dart:convert';
|
||||
|
||||
class JsonRoundTripBenchmark {
|
||||
void run() {
|
||||
final res = json.decode(jsonData);
|
||||
final out = json.encode(res);
|
||||
if (out[0] != jsonData[0]) {
|
||||
throw 'json conversion error';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Builds around 4.5 MB of json data - big enough so the decoded object graph
|
||||
// does not fit into new space.
|
||||
final String jsonData = () {
|
||||
final rnd = Random(42);
|
||||
dynamic buildTree(int depth) {
|
||||
final int coin = rnd.nextInt(1000);
|
||||
if (depth == 0) {
|
||||
if (coin % 2 == 0) return coin;
|
||||
return 'foo-$coin';
|
||||
}
|
||||
|
||||
if (coin % 2 == 0) {
|
||||
final map = <String, dynamic>{};
|
||||
final int length = rnd.nextInt(18);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
map['bar-$i'] = buildTree(depth - 1);
|
||||
}
|
||||
return map;
|
||||
} else {
|
||||
final list = <dynamic>[];
|
||||
final int length = rnd.nextInt(18);
|
||||
for (int i = 0; i < length; ++i) {
|
||||
list.add(buildTree(depth - 1));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
return json.encode({'data': buildTree(6)});
|
||||
}();
|
135
benchmarks/EventLoopLatencyJson/dart2/latency.dart
Normal file
135
benchmarks/EventLoopLatencyJson/dart2/latency.dart
Normal file
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) 2020, 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:io';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:typed_data';
|
||||
|
||||
/// Measures event loop responsiveness.
|
||||
///
|
||||
/// Schedules new timer events, [tickDuration] in the future, and measures how
|
||||
/// long it takes for these events to actually arrive.
|
||||
///
|
||||
/// Runs [numberOfTicks] times before completing with [EventLoopLatencyStats].
|
||||
Future<EventLoopLatencyStats> measureEventLoopLatency(
|
||||
Duration tickDuration, int numberOfTicks) {
|
||||
final completer = Completer<EventLoopLatencyStats>();
|
||||
|
||||
final tickDurationInUs = tickDuration.inMicroseconds;
|
||||
final buffer = _TickLatencies(numberOfTicks);
|
||||
final sw = Stopwatch()..start();
|
||||
int lastTimestamp = 0;
|
||||
|
||||
void trigger() {
|
||||
final int currentTimestamp = sw.elapsedMicroseconds;
|
||||
|
||||
// Every tick we missed to schedule we'll add with difference to when we
|
||||
// would've scheduled it and when we became responsive again.
|
||||
bool done = false;
|
||||
while (!done && lastTimestamp < (currentTimestamp - tickDurationInUs)) {
|
||||
done = !buffer.add(currentTimestamp - lastTimestamp - tickDurationInUs);
|
||||
lastTimestamp += tickDurationInUs;
|
||||
}
|
||||
|
||||
if (!done) {
|
||||
lastTimestamp = currentTimestamp;
|
||||
Timer(tickDuration, trigger);
|
||||
} else {
|
||||
completer.complete(buffer.makeStats());
|
||||
}
|
||||
}
|
||||
|
||||
Timer(tickDuration, trigger);
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// Result of the event loop latency measurement.
|
||||
class EventLoopLatencyStats {
|
||||
/// Minimum latency between scheduling a tick and it's arrival (in ms).
|
||||
final double minLatency;
|
||||
|
||||
/// Average latency between scheduling a tick and it's arrival (in ms).
|
||||
final double avgLatency;
|
||||
|
||||
/// Maximum latency between scheduling a tick and it's arrival (in ms).
|
||||
final double maxLatency;
|
||||
|
||||
/// The 50th percentile (median) (in ms).
|
||||
final double percentile50th;
|
||||
|
||||
/// The 90th percentile (in ms).
|
||||
final double percentile90th;
|
||||
|
||||
/// The 95th percentile (in ms).
|
||||
final double percentile95th;
|
||||
|
||||
/// The 99th percentile (in ms).
|
||||
final double percentile99th;
|
||||
|
||||
/// The maximum RSS of the process.
|
||||
final int maxRss;
|
||||
|
||||
EventLoopLatencyStats(
|
||||
this.minLatency,
|
||||
this.avgLatency,
|
||||
this.maxLatency,
|
||||
this.percentile50th,
|
||||
this.percentile90th,
|
||||
this.percentile95th,
|
||||
this.percentile99th,
|
||||
this.maxRss);
|
||||
|
||||
void report(String name) {
|
||||
print('$name.Min(RunTime): $minLatency ms.');
|
||||
print('$name.Avg(RunTime): $avgLatency ms.');
|
||||
print('$name.Percentile50(RunTime): $percentile50th ms.');
|
||||
print('$name.Percentile90(RunTime): $percentile90th ms.');
|
||||
print('$name.Percentile95(RunTime): $percentile95th ms.');
|
||||
print('$name.Percentile99(RunTime): $percentile99th ms.');
|
||||
print('$name.Max(RunTime): $maxLatency ms.');
|
||||
print('$name.MaxRss(MemoryUse): $maxRss');
|
||||
}
|
||||
}
|
||||
|
||||
/// Accumulates tick latencies and makes statistics for it.
|
||||
class _TickLatencies {
|
||||
final Uint64List _timestamps;
|
||||
int _index = 0;
|
||||
|
||||
_TickLatencies(int numberOfTicks) : _timestamps = Uint64List(numberOfTicks);
|
||||
|
||||
/// Returns `true` while the buffer has not been filled yet.
|
||||
bool add(int latencyInUs) {
|
||||
_timestamps[_index++] = latencyInUs;
|
||||
return _index < _timestamps.length;
|
||||
}
|
||||
|
||||
EventLoopLatencyStats makeStats() {
|
||||
if (_index != _timestamps.length) {
|
||||
throw 'Buffer has not been fully filled yet.';
|
||||
}
|
||||
|
||||
_timestamps.sort();
|
||||
final length = _timestamps.length;
|
||||
final double avg = _timestamps.fold(0, (int a, int b) => a + b) / length;
|
||||
final int min = _timestamps.fold(0x7fffffffffffffff, math.min);
|
||||
final int max = _timestamps.fold(0, math.max);
|
||||
final percentile50th = _timestamps[50 * length ~/ 100];
|
||||
final percentile90th = _timestamps[90 * length ~/ 100];
|
||||
final percentile95th = _timestamps[95 * length ~/ 100];
|
||||
final percentile99th = _timestamps[99 * length ~/ 100];
|
||||
|
||||
return EventLoopLatencyStats(
|
||||
min / 1000,
|
||||
avg / 1000,
|
||||
max / 1000,
|
||||
percentile50th / 1000,
|
||||
percentile90th / 1000,
|
||||
percentile95th / 1000,
|
||||
percentile99th / 1000,
|
||||
ProcessInfo.maxRss);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue