Skip to content

Commit 74b606f

Browse files
aamCommit Bot
authored and
Commit Bot
committed
[benchmark] Add IsolateSendExitLatency benchmark.
This measures latency induced by one isolate send-and-exiting on concurrently running isolates. The benchmark report format mimics EventLoopLatency benchmark, in a way it reports "runtime" as a latency. TEST=manual run of benchmarks Bug: #49050 Change-Id: I20642fd75bd24870658d553b0775f62083544bdb Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/246620 Reviewed-by: Martin Kustermann <[email protected]> Commit-Queue: Alexander Aprelev <[email protected]>
1 parent 23fe4b8 commit 74b606f

File tree

4 files changed

+362
-0
lines changed

4 files changed

+362
-0
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) 2022, 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+
// This test ensures that there are no long pauses when sending large objects
6+
// via exit/send.
7+
8+
import 'dart:async';
9+
import 'dart:typed_data';
10+
import 'dart:math' as math;
11+
import 'dart:isolate';
12+
13+
import 'latency.dart';
14+
15+
main() async {
16+
final statsFuture =
17+
measureEventLoopLatency(const Duration(milliseconds: 1), 4000, work: () {
18+
// Every 1 ms we allocate some objects which may trigger GC some time.
19+
for (int i = 0; i < 32; i++) {
20+
List.filled(32 * 1024 ~/ 8, null);
21+
}
22+
});
23+
24+
final result = await compute(() {
25+
final l = <dynamic>[];
26+
for (int i = 0; i < 10 * 1000 * 1000; ++i) {
27+
l.add(Object());
28+
}
29+
return l;
30+
});
31+
if (result.length != 10 * 1000 * 1000) throw 'failed';
32+
33+
final stats = await statsFuture;
34+
stats.report('IsolateSendExitLatency');
35+
}
36+
37+
Future<T> compute<T>(T Function() fun) {
38+
final rp = ReceivePort();
39+
final sp = rp.sendPort;
40+
Isolate.spawn((_) {
41+
final value = fun();
42+
Isolate.exit(sp, value);
43+
}, null);
44+
return rp.first.then((t) => t as T);
45+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) 2020, 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 'dart:async';
6+
import 'dart:io';
7+
import 'dart:math' as math;
8+
import 'dart:typed_data';
9+
10+
/// Measures event loop responsiveness.
11+
///
12+
/// Schedules new timer events, [tickDuration] in the future, and measures how
13+
/// long it takes for these events to actually arrive.
14+
///
15+
/// Runs [numberOfTicks] times before completing with [EventLoopLatencyStats].
16+
Future<EventLoopLatencyStats> measureEventLoopLatency(
17+
Duration tickDuration, int numberOfTicks,
18+
{void Function()? work}) {
19+
final completer = Completer<EventLoopLatencyStats>();
20+
21+
final tickDurationInUs = tickDuration.inMicroseconds;
22+
final buffer = _TickLatencies(numberOfTicks);
23+
final sw = Stopwatch()..start();
24+
int lastTimestamp = 0;
25+
26+
void trigger() {
27+
final int currentTimestamp = sw.elapsedMicroseconds;
28+
29+
// Every tick we missed to schedule we'll add with difference to when we
30+
// would've scheduled it and when we became responsive again.
31+
bool done = false;
32+
while (!done && lastTimestamp < (currentTimestamp - tickDurationInUs)) {
33+
done = !buffer.add(currentTimestamp - lastTimestamp - tickDurationInUs);
34+
lastTimestamp += tickDurationInUs;
35+
}
36+
37+
if (work != null) {
38+
work();
39+
}
40+
41+
if (!done) {
42+
lastTimestamp = currentTimestamp;
43+
Timer(tickDuration, trigger);
44+
} else {
45+
completer.complete(buffer.makeStats());
46+
}
47+
}
48+
49+
Timer(tickDuration, trigger);
50+
51+
return completer.future;
52+
}
53+
54+
/// Result of the event loop latency measurement.
55+
class EventLoopLatencyStats {
56+
/// Minimum latency between scheduling a tick and it's arrival (in ms).
57+
final double minLatency;
58+
59+
/// Average latency between scheduling a tick and it's arrival (in ms).
60+
final double avgLatency;
61+
62+
/// Maximum latency between scheduling a tick and it's arrival (in ms).
63+
final double maxLatency;
64+
65+
/// The 50th percentile (median) (in ms).
66+
final double percentile50th;
67+
68+
/// The 90th percentile (in ms).
69+
final double percentile90th;
70+
71+
/// The 95th percentile (in ms).
72+
final double percentile95th;
73+
74+
/// The 99th percentile (in ms).
75+
final double percentile99th;
76+
77+
EventLoopLatencyStats(
78+
this.minLatency,
79+
this.avgLatency,
80+
this.maxLatency,
81+
this.percentile50th,
82+
this.percentile90th,
83+
this.percentile95th,
84+
this.percentile99th);
85+
86+
void report(String name) {
87+
print('$name.Min(RunTimeRaw): $minLatency ms.');
88+
print('$name.Avg(RunTimeRaw): $avgLatency ms.');
89+
print('$name.Percentile50(RunTimeRaw): $percentile50th ms.');
90+
print('$name.Percentile90(RunTimeRaw): $percentile90th ms.');
91+
print('$name.Percentile95(RunTimeRaw): $percentile95th ms.');
92+
print('$name.Percentile99(RunTimeRaw): $percentile99th ms.');
93+
print('$name.Max(RunTimeRaw): $maxLatency ms.');
94+
}
95+
}
96+
97+
/// Accumulates tick latencies and makes statistics for it.
98+
class _TickLatencies {
99+
final Uint64List _timestamps;
100+
int _index = 0;
101+
102+
_TickLatencies(int numberOfTicks) : _timestamps = Uint64List(numberOfTicks);
103+
104+
/// Returns `true` while the buffer has not been filled yet.
105+
bool add(int latencyInUs) {
106+
_timestamps[_index++] = latencyInUs;
107+
return _index < _timestamps.length;
108+
}
109+
110+
EventLoopLatencyStats makeStats() {
111+
if (_index != _timestamps.length) {
112+
throw 'Buffer has not been fully filled yet.';
113+
}
114+
115+
_timestamps.sort();
116+
final length = _timestamps.length;
117+
final double avg = _timestamps.fold(0, (int a, int b) => a + b) / length;
118+
final int min = _timestamps.fold(0x7fffffffffffffff, math.min);
119+
final int max = _timestamps.fold(0, math.max);
120+
final percentile50th = _timestamps[50 * length ~/ 100];
121+
final percentile90th = _timestamps[90 * length ~/ 100];
122+
final percentile95th = _timestamps[95 * length ~/ 100];
123+
final percentile99th = _timestamps[99 * length ~/ 100];
124+
125+
return EventLoopLatencyStats(
126+
min / 1000,
127+
avg / 1000,
128+
max / 1000,
129+
percentile50th / 1000,
130+
percentile90th / 1000,
131+
percentile95th / 1000,
132+
percentile99th / 1000);
133+
}
134+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) 2022, 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+
// This test ensures that there are no long pauses when sending large objects
6+
// via exit/send.
7+
8+
// @dart=2.9
9+
10+
import 'dart:async';
11+
import 'dart:typed_data';
12+
import 'dart:math' as math;
13+
import 'dart:isolate';
14+
15+
import 'latency.dart';
16+
17+
main() async {
18+
final statsFuture =
19+
measureEventLoopLatency(const Duration(milliseconds: 1), 4000, work: () {
20+
// Every 1 ms we allocate some objects which may trigger GC some time.
21+
for (int i = 0; i < 32; i++) {
22+
List.filled(32 * 1024 ~/ 8, null);
23+
}
24+
});
25+
26+
final result = await compute(() {
27+
final l = <dynamic>[];
28+
for (int i = 0; i < 10 * 1000 * 1000; ++i) {
29+
l.add(Object());
30+
}
31+
return l;
32+
});
33+
if (result.length != 10 * 1000 * 1000) throw 'failed';
34+
35+
final stats = await statsFuture;
36+
stats.report('IsolateSendExitLatency');
37+
}
38+
39+
Future<T> compute<T>(T Function() fun) {
40+
final rp = ReceivePort();
41+
final sp = rp.sendPort;
42+
Isolate.spawn((_) {
43+
final value = fun();
44+
Isolate.exit(sp, value);
45+
}, null);
46+
return rp.first.then((t) => t as T);
47+
}
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Copyright (c) 2020, 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+
// @dart=2.9
6+
7+
import 'dart:async';
8+
import 'dart:io';
9+
import 'dart:math' as math;
10+
import 'dart:typed_data';
11+
12+
/// Measures event loop responsiveness.
13+
///
14+
/// Schedules new timer events, [tickDuration] in the future, and measures how
15+
/// long it takes for these events to actually arrive.
16+
///
17+
/// Runs [numberOfTicks] times before completing with [EventLoopLatencyStats].
18+
Future<EventLoopLatencyStats> measureEventLoopLatency(
19+
Duration tickDuration, int numberOfTicks,
20+
{void Function() work}) {
21+
final completer = Completer<EventLoopLatencyStats>();
22+
23+
final tickDurationInUs = tickDuration.inMicroseconds;
24+
final buffer = _TickLatencies(numberOfTicks);
25+
final sw = Stopwatch()..start();
26+
int lastTimestamp = 0;
27+
28+
void trigger() {
29+
final int currentTimestamp = sw.elapsedMicroseconds;
30+
31+
// Every tick we missed to schedule we'll add with difference to when we
32+
// would've scheduled it and when we became responsive again.
33+
bool done = false;
34+
while (!done && lastTimestamp < (currentTimestamp - tickDurationInUs)) {
35+
done = !buffer.add(currentTimestamp - lastTimestamp - tickDurationInUs);
36+
lastTimestamp += tickDurationInUs;
37+
}
38+
39+
if (work != null) {
40+
work();
41+
}
42+
43+
if (!done) {
44+
lastTimestamp = currentTimestamp;
45+
Timer(tickDuration, trigger);
46+
} else {
47+
completer.complete(buffer.makeStats());
48+
}
49+
}
50+
51+
Timer(tickDuration, trigger);
52+
53+
return completer.future;
54+
}
55+
56+
/// Result of the event loop latency measurement.
57+
class EventLoopLatencyStats {
58+
/// Minimum latency between scheduling a tick and it's arrival (in ms).
59+
final double minLatency;
60+
61+
/// Average latency between scheduling a tick and it's arrival (in ms).
62+
final double avgLatency;
63+
64+
/// Maximum latency between scheduling a tick and it's arrival (in ms).
65+
final double maxLatency;
66+
67+
/// The 50th percentile (median) (in ms).
68+
final double percentile50th;
69+
70+
/// The 90th percentile (in ms).
71+
final double percentile90th;
72+
73+
/// The 95th percentile (in ms).
74+
final double percentile95th;
75+
76+
/// The 99th percentile (in ms).
77+
final double percentile99th;
78+
79+
EventLoopLatencyStats(
80+
this.minLatency,
81+
this.avgLatency,
82+
this.maxLatency,
83+
this.percentile50th,
84+
this.percentile90th,
85+
this.percentile95th,
86+
this.percentile99th);
87+
88+
void report(String name) {
89+
print('$name.Min(RunTimeRaw): $minLatency ms.');
90+
print('$name.Avg(RunTimeRaw): $avgLatency ms.');
91+
print('$name.Percentile50(RunTimeRaw): $percentile50th ms.');
92+
print('$name.Percentile90(RunTimeRaw): $percentile90th ms.');
93+
print('$name.Percentile95(RunTimeRaw): $percentile95th ms.');
94+
print('$name.Percentile99(RunTimeRaw): $percentile99th ms.');
95+
print('$name.Max(RunTimeRaw): $maxLatency ms.');
96+
}
97+
}
98+
99+
/// Accumulates tick latencies and makes statistics for it.
100+
class _TickLatencies {
101+
final Uint64List _timestamps;
102+
int _index = 0;
103+
104+
_TickLatencies(int numberOfTicks) : _timestamps = Uint64List(numberOfTicks);
105+
106+
/// Returns `true` while the buffer has not been filled yet.
107+
bool add(int latencyInUs) {
108+
_timestamps[_index++] = latencyInUs;
109+
return _index < _timestamps.length;
110+
}
111+
112+
EventLoopLatencyStats makeStats() {
113+
if (_index != _timestamps.length) {
114+
throw 'Buffer has not been fully filled yet.';
115+
}
116+
117+
_timestamps.sort();
118+
final length = _timestamps.length;
119+
final double avg = _timestamps.fold(0, (int a, int b) => a + b) / length;
120+
final int min = _timestamps.fold(0x7fffffffffffffff, math.min);
121+
final int max = _timestamps.fold(0, math.max);
122+
final percentile50th = _timestamps[50 * length ~/ 100];
123+
final percentile90th = _timestamps[90 * length ~/ 100];
124+
final percentile95th = _timestamps[95 * length ~/ 100];
125+
final percentile99th = _timestamps[99 * length ~/ 100];
126+
127+
return EventLoopLatencyStats(
128+
min / 1000,
129+
avg / 1000,
130+
max / 1000,
131+
percentile50th / 1000,
132+
percentile90th / 1000,
133+
percentile95th / 1000,
134+
percentile99th / 1000);
135+
}
136+
}

0 commit comments

Comments
 (0)