| // 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<Object?> _makeTestBuffer(int size) { |
| final List<Object?> answer = <Object?>[]; |
| for (int i = 0; i < size; ++i) { |
| switch (i % 9) { |
| case 0: |
| answer.add(1); |
| break; |
| case 1: |
| answer.add(math.pow(2, 65)); |
| break; |
| case 2: |
| answer.add(1234.0); |
| break; |
| case 3: |
| answer.add(null); |
| break; |
| case 4: |
| answer.add(<int>[1234]); |
| break; |
| case 5: |
| answer.add(<String, int>{'hello': 1234}); |
| break; |
| case 6: |
| answer.add('this is a test'); |
| break; |
| case 7: |
| answer.add(true); |
| break; |
| case 8: |
| answer.add(Uint8List(64)); |
| break; |
| } |
| } |
| return answer; |
| } |
| |
| Future<double> _runBasicStandardSmall( |
| BasicMessageChannel<Object?> 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<Object?> basicStandard, |
| _Counter counter, |
| int count, |
| Completer<int> 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<double> _runBasicStandardParallel( |
| BasicMessageChannel<Object?> basicStandard, |
| int count, |
| Object? payload, |
| int parallel, |
| ) async { |
| final Stopwatch watch = Stopwatch(); |
| final Completer<int> completer = Completer<int>(); |
| 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<double> _runBasicStandardLarge( |
| BasicMessageChannel<Object?> basicStandard, |
| List<Object?> largeBuffer, |
| int count, |
| ) async { |
| int size = 0; |
| final Stopwatch watch = Stopwatch(); |
| watch.start(); |
| for (int i = 0; i < count; ++i) { |
| final List<Object?>? result = |
| await basicStandard.send(largeBuffer) as List<Object?>?; |
| // 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<double> _runBasicBinary( |
| BasicMessageChannel<ByteData> 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<void> _runTest({ |
| required Future<double> Function(int) test, |
| required BasicMessageChannel<Object?> 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<void> _runTests() async { |
| if (kDebugMode) { |
| throw Exception( |
| "Must be run in profile mode! Use 'flutter run --profile'.", |
| ); |
| } |
| |
| const BasicMessageChannel<Object?> resetChannel = |
| BasicMessageChannel<Object?>( |
| 'dev.flutter.echo.reset', |
| StandardMessageCodec(), |
| ); |
| const BasicMessageChannel<Object?> basicStandard = |
| BasicMessageChannel<Object?>( |
| 'dev.flutter.echo.basic.standard', |
| StandardMessageCodec(), |
| ); |
| const BasicMessageChannel<ByteData> basicBinary = |
| BasicMessageChannel<ByteData>( |
| '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<Object?> 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<Object?> backgroundStandard = |
| BasicMessageChannel<Object?>( |
| '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(); |
| } |
| |
| class _BenchmarkWidget extends StatefulWidget { |
| const _BenchmarkWidget(this.tests); |
| |
| final Future<void> 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)); |
| } |