Description
The following little flutter program creates an isolate, and times how long the main thread locks while sending a 100MB of zero bytes to an isolate. It does this by having a timer run continuously on the main thread, timing how long since it last ran, and printing any time that it took more than 5ms between invocations ("since last checkin"). If the main thread is never locked, this should never print anything. At the same time, in a Future-mediated loop on the main thread, it sends a 100MB ByteData buffer of zeros to the isolate, then awaits a response, and prints the total round-trip time.
The isolate merely runs a Future-mediated loop that waits for a message, then sends a single byte back.
For flutter, what matters is primarily that the total overhead of sending something to a thread is small (small single-digits of milliseconds at most, ideally microseconds). Total round-trip time is only of academic interest so long as it's not measured in minutes.
import 'dart:async';
import 'dart:isolate';
import 'dart:typed_data';
// true = time the in-thread overhead for sending a big message out of the main thread
// false = time the in-thread overhead for receiving a big message into the main thread
const bool benchmarkSend = true;
const int toIsolateSize = benchmarkSend ? 100 * 1024 * 1024 : 1;
const int fromIsolateSize = benchmarkSend ? 1 : 100 * 1024 * 1024;
Future<Null> main() async {
Timer.run(idleTimer);
ReceivePort port = new ReceivePort();
StreamIterator<dynamic> inbox = new StreamIterator<dynamic>(port);
Isolate.spawn(isolateMain, port.sendPort);
await inbox.moveNext();
SendPort outbox = inbox.current;
Stopwatch workWatch = new Stopwatch();
ByteData data = new ByteData(toIsolateSize);
while (true) {
print('sending...');
workWatch.start();
outbox.send(data);
await inbox.moveNext();
workWatch.stop();
int time = workWatch.elapsedMilliseconds;
print('${time}ms for round-trip');
workWatch.reset();
}
}
Future<Null> isolateMain(SendPort outbox) async {
ReceivePort port = new ReceivePort();
StreamIterator<dynamic> inbox = new StreamIterator<dynamic>(port);
outbox.send(port.sendPort);
ByteData data = new ByteData(fromIsolateSize);
while (true) {
await inbox.moveNext();
outbox.send(data);
}
}
Stopwatch idleWatch = new Stopwatch();
void idleTimer() {
idleWatch.stop();
int time = idleWatch.elapsedMilliseconds;
if (time > 5)
print('${time}ms since last checkin');
idleWatch.reset();
idleWatch.start();
Timer.run(idleTimer);
}
Here is some representative output for this script running on a Pixel XL 2. The total overhead for sending 100MB to another isolate appears to be in the double-digit millisecond range, which means that sending 100MB to another isolate guarantees that the application will miss a frame.
I/flutter ( 3337): sending...
I/flutter ( 3337): 16ms since last checkin
I/flutter ( 3337): 127ms for round-trip
I/flutter ( 3337): sending...
I/flutter ( 3337): 15ms since last checkin
I/flutter ( 3337): 126ms for round-trip
I/flutter ( 3337): sending...
I/flutter ( 3337): 16ms since last checkin
I/flutter ( 3337): 130ms for round-trip
I/flutter ( 3337): sending...
I/flutter ( 3337): 16ms since last checkin
I/flutter ( 3337): 126ms for round-trip
I/flutter ( 3337): sending...
I/flutter ( 3337): 15ms since last checkin
I/flutter ( 3337): 124ms for round-trip
I/flutter ( 3337): sending...
I/flutter ( 3337): 16ms since last checkin
I/flutter ( 3337): 124ms for round-trip
I/flutter ( 3337): sending...
I/flutter ( 3337): 16ms since last checkin
I/flutter ( 3337): 122ms for round-trip
I/flutter ( 3337): sending...
See also #31959, which is a much more serious problem with receiving data taking double-digit milliseconds.