// 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 'package:flutter/foundation.dart'; import '../common.dart'; const int _kNumIterations = 65536; const int _kNumWarmUp = 100; const int _kScale = 1000; void main() { assert(false, "Don't run benchmarks in debug mode! Use 'flutter run --release'."); // In the following benchmarks, we won't remove the listeners when we don't // want to measure removeListener because we know that everything will be // GC'ed in the end. // Not removing listeners would cause memory leaks in a real application. final BenchmarkResultPrinter printer = BenchmarkResultPrinter(); void runAddListenerBenchmark(int iteration, {bool addResult = true}) { const String name = 'add'; for (int listenerCount = 1; listenerCount <= 5; listenerCount += 1) { final List<_Notifier> notifiers = List<_Notifier>.generate( iteration, (_) => _Notifier(), growable: false, ); final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < iteration; i += 1) { for (int l = 0; l < listenerCount; l += 1) { notifiers[i].addListener(() {}); } } watch.stop(); final int elapsed = watch.elapsedMicroseconds; final double averagePerIteration = elapsed / iteration; if (addResult) { printer.addResult( description: '$name ($listenerCount listeners)', value: averagePerIteration * _kScale, unit: 'ns per iteration', name: '$name$listenerCount', ); } } } void runNotifyListenerBenchmark(int iteration, {bool addResult = true}) { const String name = 'notify'; for (int listenerCount = 0; listenerCount <= 5; listenerCount += 1) { final _Notifier notifier = _Notifier(); for (int i = 1; i <= listenerCount; i += 1) { notifier.addListener(() {}); } final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < iteration; i += 1) { notifier.notify(); } watch.stop(); final int elapsed = watch.elapsedMicroseconds; final double averagePerIteration = elapsed / iteration; if (addResult) { printer.addResult( description: '$name ($listenerCount listeners)', value: averagePerIteration * _kScale, unit: 'ns per iteration', name: '$name$listenerCount', ); } } } void runRemoveListenerBenchmark(int iteration, {bool addResult = true}) { const String name = 'remove'; final List<VoidCallback> listeners = <VoidCallback>[ () {}, () {}, () {}, () {}, () {}, ]; for (int listenerCount = 1; listenerCount <= 5; listenerCount += 1) { final List<_Notifier> notifiers = List<_Notifier>.generate( iteration, (_) { final _Notifier notifier = _Notifier(); for (int l = 0; l < listenerCount; l += 1) { notifier.addListener(listeners[l]); } return notifier; }, growable: false, ); final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < iteration; i += 1) { for (int l = 0; l < listenerCount; l += 1) { notifiers[i].removeListener(listeners[l]); } } watch.stop(); final int elapsed = watch.elapsedMicroseconds; final double averagePerIteration = elapsed / iteration; if (addResult) { printer.addResult( description: '$name ($listenerCount listeners)', value: averagePerIteration * _kScale, unit: 'ns per iteration', name: '$name$listenerCount', ); } } } void runRemoveListenerWhileNotifyingBenchmark(int iteration, {bool addResult = true}) { const String name = 'removeWhileNotify'; final List<VoidCallback> listeners = <VoidCallback>[ () {}, () {}, () {}, () {}, () {}, ]; for (int listenerCount = 1; listenerCount <= 5; listenerCount += 1) { final List<_Notifier> notifiers = List<_Notifier>.generate( iteration, (_) { final _Notifier notifier = _Notifier(); notifier.addListener(() { // This listener will remove all other listeners. So that only this // one is called and measured. for (int l = 0; l < listenerCount; l += 1) { notifier.removeListener(listeners[l]); } }); for (int l = 0; l < listenerCount; l += 1) { notifier.addListener(listeners[l]); } return notifier; }, growable: false, ); final Stopwatch watch = Stopwatch(); watch.start(); for (int i = 0; i < iteration; i += 1) { notifiers[i].notify(); } watch.stop(); final int elapsed = watch.elapsedMicroseconds; final double averagePerIteration = elapsed / iteration; if (addResult) { printer.addResult( description: '$name ($listenerCount listeners)', value: averagePerIteration * _kScale, unit: 'ns per iteration', name: '$name$listenerCount', ); } } } runAddListenerBenchmark(_kNumWarmUp, addResult: false); runAddListenerBenchmark(_kNumIterations); runNotifyListenerBenchmark(_kNumWarmUp, addResult: false); runNotifyListenerBenchmark(_kNumIterations); runRemoveListenerBenchmark(_kNumWarmUp, addResult: false); runRemoveListenerBenchmark(_kNumIterations); runRemoveListenerWhileNotifyingBenchmark(_kNumWarmUp, addResult: false); runRemoveListenerWhileNotifyingBenchmark(_kNumIterations); printer.printToStdout(); } class _Notifier extends ChangeNotifier { void notify() => notifyListeners(); }