Unverified Commit d95a1a70 authored by Yegor's avatar Yegor Committed by GitHub

add WidgetBuildRecorder for benchmarking building widgets (#51088)

parent 889e606c
// 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/material.dart';
import 'recorder.dart';
/// Measures how expensive it is to construct material checkboxes.
///
/// Creates a 10x10 grid of tristate checkboxes.
class BenchBuildMaterialCheckbox extends WidgetBuildRecorder {
BenchBuildMaterialCheckbox() : super(name: benchmarkName);
static const String benchmarkName = 'build_material_checkbox';
static bool _isChecked;
@override
Widget createWidget() {
return Column(
children: List<Widget>.generate(10, (int i) {
return _buildRow();
}),
);
}
Row _buildRow() {
if (_isChecked == null) {
_isChecked = true;
} else if (_isChecked) {
_isChecked = false;
} else {
_isChecked = null;
}
return Row(
children: List<Widget>.generate(10, (int i) {
return Expanded(
child: Checkbox(
value: _isChecked,
tristate: true,
onChanged: (bool newValue) {
// Intentionally empty.
},
),
);
}),
);
}
}
...@@ -183,7 +183,7 @@ abstract class RawRecorder extends Recorder { ...@@ -183,7 +183,7 @@ abstract class RawRecorder extends Recorder {
/// } /// }
/// } /// }
/// ``` /// ```
abstract class WidgetRecorder extends Recorder { abstract class WidgetRecorder extends Recorder implements _RecordingWidgetsBindingListener {
WidgetRecorder({@required String name}) : super._(name); WidgetRecorder({@required String name}) : super._(name);
/// Creates a widget to be benchmarked. /// Creates a widget to be benchmarked.
...@@ -198,10 +198,12 @@ abstract class WidgetRecorder extends Recorder { ...@@ -198,10 +198,12 @@ abstract class WidgetRecorder extends Recorder {
Stopwatch _drawFrameStopwatch; Stopwatch _drawFrameStopwatch;
@override
void _frameWillDraw() { void _frameWillDraw() {
_drawFrameStopwatch = Stopwatch()..start(); _drawFrameStopwatch = Stopwatch()..start();
} }
@override
void _frameDidDraw() { void _frameDidDraw() {
_frames.add(FrameMetrics._( _frames.add(FrameMetrics._(
drawFrameDuration: _drawFrameStopwatch.elapsed, drawFrameDuration: _drawFrameStopwatch.elapsed,
...@@ -226,6 +228,100 @@ abstract class WidgetRecorder extends Recorder { ...@@ -226,6 +228,100 @@ abstract class WidgetRecorder extends Recorder {
} }
} }
/// A recorder for measuring the performance of building a widget from scratch
/// starting from an empty frame.
///
/// The recorder will call [createWidget] and render it, then it will pump
/// another frame that clears the screen. It repeats this process, measuring the
/// performance of frames that render the widget and ignoring the frames that
/// clear the screen.
abstract class WidgetBuildRecorder extends Recorder implements _RecordingWidgetsBindingListener {
WidgetBuildRecorder({@required String name}) : super._(name);
/// Creates a widget to be benchmarked.
///
/// The widget is not expected to animate as we only care about construction
/// of the widget. If you are interested in benchmarking an animation,
/// consider using [WidgetRecorder].
Widget createWidget();
final Completer<Profile> _profileCompleter = Completer<Profile>();
Stopwatch _drawFrameStopwatch;
/// Whether in this frame we should call [createWidget] and render it.
///
/// If false, then this frame will clear the screen.
bool _showWidget = true;
/// The state that hosts the widget under test.
_WidgetBuildRecorderHostState _hostState;
Widget _getWidgetForFrame() {
if (_showWidget) {
return createWidget();
} else {
return null;
}
}
@override
void _frameWillDraw() {
_drawFrameStopwatch = Stopwatch()..start();
}
@override
void _frameDidDraw() {
// Only record frames that show the widget.
if (_showWidget) {
_frames.add(FrameMetrics._(
drawFrameDuration: _drawFrameStopwatch.elapsed,
sceneBuildDuration: null,
windowRenderDuration: null,
));
}
if (_shouldContinue()) {
_showWidget = !_showWidget;
_hostState._setStateTrampoline();
} else {
final Profile profile = _generateProfile();
_profileCompleter.complete(profile);
}
}
@override
Future<Profile> run() {
final _RecordingWidgetsBinding binding =
_RecordingWidgetsBinding.ensureInitialized();
binding._beginRecording(this, _WidgetBuildRecorderHost(this));
return _profileCompleter.future;
}
}
/// Hosts widgets created by [WidgetBuildRecorder].
class _WidgetBuildRecorderHost extends StatefulWidget {
const _WidgetBuildRecorderHost(this.recorder);
final WidgetBuildRecorder recorder;
@override
State<StatefulWidget> createState() => recorder._hostState = _WidgetBuildRecorderHostState();
}
class _WidgetBuildRecorderHostState extends State<_WidgetBuildRecorderHost> {
// This is just to bypass the @protected on setState.
void _setStateTrampoline() {
setState(() {});
}
@override
Widget build(BuildContext context) {
return SizedBox.expand(
child: widget.recorder._getWidgetForFrame(),
);
}
}
/// Pumps frames and records frame metrics. /// Pumps frames and records frame metrics.
abstract class Recorder { abstract class Recorder {
Recorder._(this.name); Recorder._(this.name);
...@@ -413,6 +509,14 @@ double _computeStandardDeviationForPopulation(Iterable<double> population) { ...@@ -413,6 +509,14 @@ double _computeStandardDeviationForPopulation(Iterable<double> population) {
return math.sqrt(sumOfSquaredDeltas / population.length); return math.sqrt(sumOfSquaredDeltas / population.length);
} }
/// Implemented by recorders that use [_RecordingWidgetsBinding] to receive
/// frame life-cycle calls.
abstract class _RecordingWidgetsBindingListener {
bool _shouldContinue();
void _frameWillDraw();
void _frameDidDraw();
}
/// A variant of [WidgetsBinding] that collaborates with a [Recorder] to decide /// A variant of [WidgetsBinding] that collaborates with a [Recorder] to decide
/// when to stop pumping frames. /// when to stop pumping frames.
/// ///
...@@ -438,10 +542,10 @@ class _RecordingWidgetsBinding extends BindingBase ...@@ -438,10 +542,10 @@ class _RecordingWidgetsBinding extends BindingBase
return WidgetsBinding.instance as _RecordingWidgetsBinding; return WidgetsBinding.instance as _RecordingWidgetsBinding;
} }
WidgetRecorder _recorder; _RecordingWidgetsBindingListener _listener;
void _beginRecording(WidgetRecorder recorder, Widget widget) { void _beginRecording(_RecordingWidgetsBindingListener recorder, Widget widget) {
_recorder = recorder; _listener = recorder;
runApp(widget); runApp(widget);
} }
...@@ -451,7 +555,7 @@ class _RecordingWidgetsBinding extends BindingBase ...@@ -451,7 +555,7 @@ class _RecordingWidgetsBinding extends BindingBase
@override @override
void handleBeginFrame(Duration rawTimeStamp) { void handleBeginFrame(Duration rawTimeStamp) {
_benchmarkStopped = !_recorder._shouldContinue(); _benchmarkStopped = !_listener._shouldContinue();
super.handleBeginFrame(rawTimeStamp); super.handleBeginFrame(rawTimeStamp);
} }
...@@ -464,8 +568,8 @@ class _RecordingWidgetsBinding extends BindingBase ...@@ -464,8 +568,8 @@ class _RecordingWidgetsBinding extends BindingBase
@override @override
void handleDrawFrame() { void handleDrawFrame() {
_recorder._frameWillDraw(); _listener._frameWillDraw();
super.handleDrawFrame(); super.handleDrawFrame();
_recorder._frameDidDraw(); _listener._frameDidDraw();
} }
} }
...@@ -8,6 +8,7 @@ import 'dart:html' as html; ...@@ -8,6 +8,7 @@ import 'dart:html' as html;
import 'package:macrobenchmarks/src/web/bench_text_out_of_picture_bounds.dart'; import 'package:macrobenchmarks/src/web/bench_text_out_of_picture_bounds.dart';
import 'src/web/bench_build_material_checkbox.dart';
import 'src/web/bench_card_infinite_scroll.dart'; import 'src/web/bench_card_infinite_scroll.dart';
import 'src/web/bench_draw_rect.dart'; import 'src/web/bench_draw_rect.dart';
import 'src/web/bench_simple_lazy_text_scroll.dart'; import 'src/web/bench_simple_lazy_text_scroll.dart';
...@@ -21,6 +22,7 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{ ...@@ -21,6 +22,7 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{
BenchDrawRect.benchmarkName: () => BenchDrawRect(), BenchDrawRect.benchmarkName: () => BenchDrawRect(),
BenchTextOutOfPictureBounds.benchmarkName: () => BenchTextOutOfPictureBounds(), BenchTextOutOfPictureBounds.benchmarkName: () => BenchTextOutOfPictureBounds(),
BenchSimpleLazyTextScroll.benchmarkName: () => BenchSimpleLazyTextScroll(), BenchSimpleLazyTextScroll.benchmarkName: () => BenchSimpleLazyTextScroll(),
BenchBuildMaterialCheckbox.benchmarkName: () => BenchBuildMaterialCheckbox(),
}; };
/// Whether we fell back to manual mode. /// Whether we fell back to manual mode.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment