From d60cc088774cbd82c43c21325f6a839c405a41ea Mon Sep 17 00:00:00 2001 From: knaeckeKami Date: Fri, 2 Apr 2021 21:04:04 +0200 Subject: [PATCH] Add removeListenerWhileNotifying benchmark for ChangeNotifier (#78161) --- .../lib/foundation/change_notifier_bench.dart | 250 +++++++++++------- 1 file changed, 160 insertions(+), 90 deletions(-) diff --git a/dev/benchmarks/microbenchmarks/lib/foundation/change_notifier_bench.dart b/dev/benchmarks/microbenchmarks/lib/foundation/change_notifier_bench.dart index 64dfdaa8d8..0ab7a400d1 100644 --- a/dev/benchmarks/microbenchmarks/lib/foundation/change_notifier_bench.dart +++ b/dev/benchmarks/microbenchmarks/lib/foundation/change_notifier_bench.dart @@ -6,108 +6,178 @@ import 'package:flutter/foundation.dart'; import '../common.dart'; -const int _kNumIterations = 1000; -const double _scale = 1000.0 / _kNumIterations; +const int _kNumIterations = 65536; const int _kNumWarmUp = 100; +const int _kScale = 1000; void main() { assert(false, "Don't run benchmarks in checked mode! Use 'flutter run --release'."); - void listener() {} - void listener2() {} - void listener3() {} - void listener4() {} - void listener5() {} + // 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. - // Warm up lap - for (int i = 0; i < _kNumWarmUp; i += 1) { - _Notifier() - ..addListener(listener) - ..addListener(listener2) - ..addListener(listener3) - ..addListener(listener4) - ..addListener(listener5) - ..notify() - ..removeListener(listener) - ..removeListener(listener2) - ..removeListener(listener3) - ..removeListener(listener4) - ..removeListener(listener5); - } - - final Stopwatch addListenerWatch = Stopwatch(); - final Stopwatch removeListenerWatch = Stopwatch(); - final Stopwatch notifyListenersWatch = Stopwatch(); final BenchmarkResultPrinter printer = BenchmarkResultPrinter(); - for (int listenersCount = 0; listenersCount <= 5; listenersCount++) { + 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, + ); - for (int j = 0; j < _kNumIterations; j += 1) { - final _Notifier notifier = _Notifier(); - addListenerWatch.start(); - - notifier.addListener(listener); - if (listenersCount > 1) - notifier.addListener(listener2); - if (listenersCount > 2) - notifier.addListener(listener3); - if (listenersCount > 3) - notifier.addListener(listener4); - if (listenersCount > 4) - notifier.addListener(listener5); - - addListenerWatch.stop(); - notifyListenersWatch.start(); - - notifier.notify(); - - notifyListenersWatch.stop(); - removeListenerWatch.start(); - - // Remove listeners in reverse order to evaluate the worse-case scenario: - // the listener removed is the last listener - if (listenersCount > 4) - notifier.removeListener(listener5); - if (listenersCount > 3) - notifier.removeListener(listener4); - if (listenersCount > 2) - notifier.removeListener(listener3); - if (listenersCount > 1) - notifier.removeListener(listener2); - notifier.removeListener(listener); - - removeListenerWatch.stop(); + 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', + ); } - - final int notifyListener = notifyListenersWatch.elapsedMicroseconds; - notifyListenersWatch.reset(); - final int addListenerElapsed = addListenerWatch.elapsedMicroseconds; - addListenerWatch.reset(); - final int removeListenerElapsed = removeListenerWatch.elapsedMicroseconds; - removeListenerWatch.reset(); - - printer.addResult( - description: 'addListener ($listenersCount listeners)', - value: addListenerElapsed * _scale, - unit: 'ns per iteration', - name: 'addListener${listenersCount}_iteration', - ); - - printer.addResult( - description: 'removeListener ($listenersCount listeners)', - value: removeListenerElapsed * _scale, - unit: 'ns per iteration', - name: 'removeListener${listenersCount}_iteration', - ); - - printer.addResult( - description: 'notifyListener ($listenersCount listeners)', - value: notifyListener * _scale, - unit: 'ns per iteration', - name: 'notifyListener${listenersCount}_iteration', - ); } + 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 listeners = [ + () {}, + () {}, + () {}, + () {}, + () {}, + ]; + 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 listeners = [ + () {}, + () {}, + () {}, + () {}, + () {}, + ]; + 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, addResult: true); + + runNotifyListenerBenchmark(_kNumWarmUp, addResult: false); + runNotifyListenerBenchmark(_kNumIterations, addResult: true); + + runRemoveListenerBenchmark(_kNumWarmUp, addResult: false); + runRemoveListenerBenchmark(_kNumIterations, addResult: true); + + runRemoveListenerWhileNotifyingBenchmark(_kNumWarmUp, addResult: false); + runRemoveListenerWhileNotifyingBenchmark(_kNumIterations, addResult: true); + printer.printToStdout(); }