// Copyright 2014 The Flutter Authors. 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:math' as math; import 'package:flutter/foundation.dart' show kDebugMode; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:microbenchmarks/common.dart'; List _makeTestBuffer(int size) { return [ for (int i = 0; i < size; i++) switch (i % 9) { 0 => 1, 1 => math.pow(2, 65), 2 => 1234.0, 3 => null, 4 => [1234], 5 => {'hello': 1234}, 6 => 'this is a test', 7 => true, _ => Uint8List(64), }, ]; } Future _runBasicStandardSmall(BasicMessageChannel basicStandard, int count) async { final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < count; ++i) { await basicStandard.send(1234); } watch.stop(); return watch.elapsedMicroseconds / count; } class _Counter { int count = 0; } void _runBasicStandardParallelRecurse( BasicMessageChannel basicStandard, _Counter counter, int count, Completer completer, Object? payload, ) { counter.count += 1; if (counter.count == count) { completer.complete(counter.count); } else if (counter.count < count) { basicStandard.send(payload).then((Object? result) { _runBasicStandardParallelRecurse(basicStandard, counter, count, completer, payload); }); } } Future _runBasicStandardParallel( BasicMessageChannel basicStandard, int count, Object? payload, int parallel, ) async { final Stopwatch watch = Stopwatch(); final Completer completer = Completer(); final _Counter counter = _Counter(); watch.start(); for (int i = 0; i < parallel; ++i) { basicStandard.send(payload).then((Object? result) { _runBasicStandardParallelRecurse(basicStandard, counter, count, completer, payload); }); } await completer.future; watch.stop(); return watch.elapsedMicroseconds / count; } Future _runBasicStandardLarge( BasicMessageChannel basicStandard, List largeBuffer, int count, ) async { int size = 0; final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < count; ++i) { final List? result = await basicStandard.send(largeBuffer) as List?; // This check should be tiny compared to the actual channel send/receive. size += (result == null) ? 0 : result.length; } watch.stop(); if (size != largeBuffer.length * count) { throw Exception("There is an error with the echo channel, the results don't add up: $size"); } return watch.elapsedMicroseconds / count; } Future _runBasicBinary( BasicMessageChannel basicBinary, ByteData buffer, int count, ) async { int size = 0; final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < count; ++i) { final ByteData? result = await basicBinary.send(buffer); // This check should be tiny compared to the actual channel send/receive. size += (result == null) ? 0 : result.lengthInBytes; } watch.stop(); if (size != buffer.lengthInBytes * count) { throw Exception("There is an error with the echo channel, the results don't add up: $size"); } return watch.elapsedMicroseconds / count; } Future _runTest({ required Future Function(int) test, required BasicMessageChannel resetChannel, required BenchmarkResultPrinter printer, required String description, required String name, required int numMessages, }) async { print('running $name'); resetChannel.send(true); // Prime test. await test(1); printer.addResult( description: description, value: await test(numMessages), unit: 'µs', name: name, ); } Future _runTests() async { if (kDebugMode) { throw Exception("Must be run in profile mode! Use 'flutter run --profile'."); } const BasicMessageChannel resetChannel = BasicMessageChannel( 'dev.flutter.echo.reset', StandardMessageCodec(), ); const BasicMessageChannel basicStandard = BasicMessageChannel( 'dev.flutter.echo.basic.standard', StandardMessageCodec(), ); const BasicMessageChannel basicBinary = BasicMessageChannel( 'dev.flutter.echo.basic.binary', BinaryCodec(), ); /// WARNING: Don't change the following line of code, it will invalidate /// `Large` tests. Instead make a different test. The size of largeBuffer /// serialized is 14214 bytes. final List largeBuffer = _makeTestBuffer(1000); final ByteData largeBufferBytes = const StandardMessageCodec().encodeMessage(largeBuffer)!; final ByteData oneMB = ByteData(1024 * 1024); const int numMessages = 2500; final BenchmarkResultPrinter printer = BenchmarkResultPrinter(); await _runTest( test: (int x) => _runBasicStandardSmall(basicStandard, x), resetChannel: resetChannel, printer: printer, description: 'BasicMessageChannel/StandardMessageCodec/Flutter->Host/Small', name: 'platform_channel_basic_standard_2host_small', numMessages: numMessages, ); await _runTest( test: (int x) => _runBasicStandardLarge(basicStandard, largeBuffer, x), resetChannel: resetChannel, printer: printer, description: 'BasicMessageChannel/StandardMessageCodec/Flutter->Host/Large', name: 'platform_channel_basic_standard_2host_large', numMessages: numMessages, ); await _runTest( test: (int x) => _runBasicBinary(basicBinary, largeBufferBytes, x), resetChannel: resetChannel, printer: printer, description: 'BasicMessageChannel/BinaryCodec/Flutter->Host/Large', name: 'platform_channel_basic_binary_2host_large', numMessages: numMessages, ); await _runTest( test: (int x) => _runBasicBinary(basicBinary, oneMB, x), resetChannel: resetChannel, printer: printer, description: 'BasicMessageChannel/BinaryCodec/Flutter->Host/1MB', name: 'platform_channel_basic_binary_2host_1MB', numMessages: numMessages, ); await _runTest( test: (int x) => _runBasicStandardParallel(basicStandard, x, 1234, 3), resetChannel: resetChannel, printer: printer, description: 'BasicMessageChannel/StandardMessageCodec/Flutter->Host/SmallParallel3', name: 'platform_channel_basic_standard_2host_small_parallel_3', numMessages: numMessages, ); // Background platform channels aren't yet implemented for iOS. const BasicMessageChannel backgroundStandard = BasicMessageChannel( 'dev.flutter.echo.background.standard', StandardMessageCodec(), ); await _runTest( test: (int x) => _runBasicStandardSmall(backgroundStandard, x), resetChannel: resetChannel, printer: printer, description: 'BasicMessageChannel/StandardMessageCodec/Flutter->Host (background)/Small', name: 'platform_channel_basic_standard_2hostbackground_small', numMessages: numMessages, ); await _runTest( test: (int x) => _runBasicStandardParallel(backgroundStandard, x, 1234, 3), resetChannel: resetChannel, printer: printer, description: 'BasicMessageChannel/StandardMessageCodec/Flutter->Host (background)/SmallParallel3', name: 'platform_channel_basic_standard_2hostbackground_small_parallel_3', numMessages: numMessages, ); printer.printToStdout(); // Signal the end of our benchmark print('\n\n╡ ••• Done ••• ╞\n\n'); } class _BenchmarkWidget extends StatefulWidget { const _BenchmarkWidget(this.tests); final Future Function() tests; @override _BenchmarkWidgetState createState() => _BenchmarkWidgetState(); } class _BenchmarkWidgetState extends State<_BenchmarkWidget> { @override void initState() { widget.tests(); super.initState(); } @override Widget build(BuildContext context) => Container(); } void main() { runApp(const _BenchmarkWidget(_runTests)); }