Opacity Peephole optimization benchmarks (#94447)

This commit is contained in:
Jim Graham 2021-12-01 01:39:02 -08:00 committed by GitHub
parent fff85f9c57
commit ee204880a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 607 additions and 12 deletions

View file

@ -1863,6 +1863,56 @@ targets:
timeout: 60
scheduler: luci
- name: Linux_android opacity_peephole_one_rect_perf__e2e_summary
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab","android","linux"]
task_name: opacity_peephole_one_rect_perf__e2e_summary
scheduler: luci
- name: Linux_android opacity_peephole_col_of_rows_perf__e2e_summary
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab","android","linux"]
task_name: opacity_peephole_col_of_rows_perf__e2e_summary
scheduler: luci
- name: Linux_android opacity_peephole_opacity_of_grid_perf__e2e_summary
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab","android","linux"]
task_name: opacity_peephole_opacity_of_grid_perf__e2e_summary
scheduler: luci
- name: Linux_android opacity_peephole_grid_of_opacity_perf__e2e_summary
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab","android","linux"]
task_name: opacity_peephole_grid_of_opacity_perf__e2e_summary
scheduler: luci
- name: Linux_android opacity_peephole_fade_transition_text_perf__e2e_summary
recipe: devicelab/devicelab_drone
presubmit: false
timeout: 60
properties:
tags: >
["devicelab","android","linux"]
task_name: opacity_peephole_fade_transition_text_perf__e2e_summary
scheduler: luci
- name: Mac build_aar_module_test
recipe: devicelab/devicelab_drone
timeout: 60

View file

@ -61,6 +61,11 @@
/dev/devicelab/bin/tasks/routing_test.dart @zanderso @flutter/tool
/dev/devicelab/bin/tasks/textfield_perf__e2e_summary.dart @zanderso @flutter/engine
/dev/devicelab/bin/tasks/web_size__compile_test.dart @yjbanov @flutter/web
/dev/devicelab/bin/tasks/opacity_peephole_col_of_rows_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/opacity_peephole_grid_of_opacity_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/opacity_peephole_one_rect_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/opacity_peephole_opacity_of_grid_perf__e2e_summary.dart @flar @flutter/engine
/dev/devicelab/bin/tasks/opacity_peephole_fade_transition_text_perf__e2e_summary.dart @flar @flutter/engine
## Windows Android DeviceLab tests
/dev/devicelab/bin/tasks/basic_material_app_win__compile.dart @zanderso @flutter/tool

View file

@ -22,6 +22,16 @@ const String kSimpleScrollRouteName = '/simple_scroll';
const String kStackSizeRouteName = '/stack_size';
const String kAnimationWithMicrotasksRouteName = '/animation_with_microtasks';
const String kAnimatedImageRouteName = '/animated_image';
const String kOpacityPeepholeRouteName = '/opacity_peephole';
const String kOpacityPeepholeOneRectRouteName = '$kOpacityPeepholeRouteName/one_big_rect';
const String kOpacityPeepholeColumnOfOpacityRouteName = '$kOpacityPeepholeRouteName/column_of_opacity';
const String kOpacityPeepholeOpacityOfCachedChildRouteName = '$kOpacityPeepholeRouteName/opacity_of_cached_child';
const String kOpacityPeepholeOpacityOfColumnRouteName = '$kOpacityPeepholeRouteName/opacity_of_column';
const String kOpacityPeepholeGridOfOpacityRouteName = '$kOpacityPeepholeRouteName/grid_of_opacity';
const String kOpacityPeepholeOpacityOfGridRouteName = '$kOpacityPeepholeRouteName/opacity_of_grid';
const String kOpacityPeepholeOpacityOfColOfRowsRouteName = '$kOpacityPeepholeRouteName/opacity_of_col_of_rows';
const String kOpacityPeepholeFadeTransitionTextRouteName = '$kOpacityPeepholeRouteName/fade_transition_text';
const String kScrollableName = '/macrobenchmark_listview';

View file

@ -19,6 +19,7 @@ import 'src/heavy_grid_view.dart';
import 'src/large_image_changer.dart';
import 'src/large_images.dart';
import 'src/multi_widget_construction.dart';
import 'src/opacity_peephole.dart';
import 'src/picture_cache.dart';
import 'src/post_backdrop_filter.dart';
import 'src/simple_animation.dart';
@ -60,6 +61,8 @@ class MacrobenchmarksApp extends StatelessWidget {
kStackSizeRouteName: (BuildContext context) => const StackSizePage(),
kAnimationWithMicrotasksRouteName: (BuildContext context) => const AnimationWithMicrotasks(),
kAnimatedImageRouteName: (BuildContext context) => const AnimatedImagePage(),
kOpacityPeepholeRouteName: (BuildContext context) => const OpacityPeepholePage(),
...opacityPeepholeRoutes,
},
);
}
@ -210,6 +213,13 @@ class HomePage extends StatelessWidget {
Navigator.pushNamed(context, kAnimatedImageRouteName);
},
),
ElevatedButton(
key: const Key(kOpacityPeepholeRouteName),
child: const Text('Opacity Peephole tests'),
onPressed: () {
Navigator.pushNamed(context, kOpacityPeepholeRouteName);
},
),
],
),
);

View file

@ -0,0 +1,332 @@
// 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 '../common.dart';
// Various tests to verify that the Opacity layer propagates the opacity to various
// combinations of children that can apply it themselves.
// See https://github.com/flutter/flutter/issues/75697
class OpacityPeepholePage extends StatelessWidget {
const OpacityPeepholePage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Opacity Peephole tests')),
body: ListView(
key: const Key('${kOpacityPeepholeRouteName}_listview'),
children: <Widget>[
for (OpacityPeepholeCase variant in allOpacityPeepholeCases)
ElevatedButton(
key: Key(variant.route),
child: Text(variant.name),
onPressed: () {
Navigator.pushNamed(context, variant.route);
},
)
],
),
);
}
}
typedef ValueBuilder = Widget Function(double v);
typedef AnimationBuilder = Widget Function(Animation<double> animation);
double _opacity(double v) => v * 0.5 + 0.25;
int _red(double v) => (v * 255).round();
int _green(double v) => _red(1 - v);
int _blue(double v) => 0;
class OpacityPeepholeCase {
OpacityPeepholeCase.forValue({required String route, required String name, required ValueBuilder builder})
: this.forAnimation(
route: route,
name: name,
builder: (Animation<double> animation) => AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) => builder(animation.value),
),
);
OpacityPeepholeCase.forAnimation({required this.route, required this.name, required AnimationBuilder builder})
: animationBuilder = builder;
final String route;
final String name;
final AnimationBuilder animationBuilder;
Widget buildPage(BuildContext context) {
return VariantPage(variant: this);
}
}
List<OpacityPeepholeCase> allOpacityPeepholeCases = <OpacityPeepholeCase>[
// Tests that Opacity can hand down value to a simple child
OpacityPeepholeCase.forValue(
route: kOpacityPeepholeOneRectRouteName,
name: 'One Big Rectangle',
builder: (double v) {
return Opacity(
opacity: _opacity(v),
child: Container(
width: 300,
height: 400,
color: Color.fromARGB(255, _red(v), _green(v), _blue(v)),
),
);
}
),
// Tests that a column of Opacity widgets can individually hand their values down to simple children
OpacityPeepholeCase.forValue(
route: kOpacityPeepholeColumnOfOpacityRouteName,
name: 'Column of Opacity',
builder: (double v) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int i = 0; i < 10; i++, v = 1 - v)
Opacity(
opacity: _opacity(v),
child: Padding(
padding: const EdgeInsets.all(5),
child: Container(
width: 300,
height: 30,
color: Color.fromARGB(255, _red(v), _green(v), _blue(v)),
),
),
),
],
);
},
),
// Tests that an Opacity can hand value down to a cached child
OpacityPeepholeCase.forValue(
route: kOpacityPeepholeOpacityOfCachedChildRouteName,
name: 'Opacity of Cached Child',
builder: (double v) {
// ChildV starts as a constant so the same color pattern always appears and the child will be cached
double childV = 0;
return Opacity(
opacity: _opacity(v),
child: RepaintBoundary(
child: SizedBox(
width: 300,
height: 400,
child: Stack(
children: <Widget>[
for (double i = 0; i < 100; i += 10, childV = 1 - childV)
Positioned.fromRelativeRect(
rect: RelativeRect.fromLTRB(i, i, i, i),
child: Container(
color: Color.fromARGB(255, _red(childV), _green(childV), _blue(childV)),
),
),
],
),
),
),
);
}
),
// Tests that an Opacity can hand a value down to a Column of simple non-overlapping children
OpacityPeepholeCase.forValue(
route: kOpacityPeepholeOpacityOfColumnRouteName,
name: 'Opacity of Column',
builder: (double v) {
return Opacity(
opacity: _opacity(v),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int i = 0; i < 10; i++, v = 1 - v)
Padding(
padding: const EdgeInsets.all(5),
// RepaintBoundary here to avoid combining children into 1 big Picture
child: RepaintBoundary(
child: Container(
width: 300,
height: 30,
color: Color.fromARGB(255, _red(v), _green(v), _blue(v)),
),
),
),
],
),
);
},
),
// Tests that an entire grid of Opacity objects can hand their values down to their simple children
OpacityPeepholeCase.forValue(
route: kOpacityPeepholeGridOfOpacityRouteName,
name: 'Grid of Opacity',
builder: (double v) {
double rowV = v;
double colV = rowV;
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int i = 0; i < 10; i++, rowV = 1 - rowV, colV = rowV)
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int j = 0; j < 7; j++, colV = 1 - colV)
Opacity(
opacity: _opacity(colV),
child: Padding(
padding: const EdgeInsets.all(5),
child: Container(
width: 30,
height: 30,
color: Color.fromARGB(255, _red(colV), _green(colV), _blue(colV)),
),
),
),
],
),
],
);
},
),
// tests if an Opacity can hand its value down to a 2D grid of simple non-overlapping children.
// The success of this case would depend on the sophistication of the non-overlapping tests.
OpacityPeepholeCase.forValue(
route: kOpacityPeepholeOpacityOfGridRouteName,
name: 'Opacity of Grid',
builder: (double v) {
double rowV = v;
double colV = rowV;
return Opacity(
opacity: _opacity(v),
child: SizedBox(
width: 300,
height: 400,
child: Stack(
children: <Widget>[
for (int i = 0; i < 10; i++, rowV = 1 - rowV, colV = rowV)
for (int j = 0; j < 7; j++, colV = 1 - colV)
Positioned.fromRect(
rect: Rect.fromLTWH(j * 40 + 5, i * 40 + 5, 30, 30),
// RepaintBoundary here to avoid combining the 70 children into a single Picture
child: RepaintBoundary(
child: Container(
color: Color.fromARGB(255, _red(colV), _green(colV), _blue(colV)),
),
),
),
],
),
),
);
},
),
// tests if an Opacity can hand its value down to a Column of non-overlapping rows of non-overlapping simple children.
// This test only requires linear non-overlapping tests to succeed.
OpacityPeepholeCase.forValue(
route: kOpacityPeepholeOpacityOfColOfRowsRouteName,
name: 'Opacity of Column of Rows',
builder: (double v) {
double rowV = v;
double colV = v;
return Opacity(
opacity: _opacity(v),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int i = 0; i < 10; i++, rowV = 1 - rowV, colV = rowV)
Padding(
padding: const EdgeInsets.only(top: 5, bottom: 5),
// RepaintBoundary here to separate each row into a separate layer child
child: RepaintBoundary(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
for (int j = 0; j < 7; j++, colV = 1 - colV)
Padding(
padding: const EdgeInsets.only(left: 5, right: 5),
// RepaintBoundary here to prevent the row children combining into a single Picture
child: RepaintBoundary(
child: Container(
width: 30,
height: 30,
color: Color.fromARGB(255, _red(colV), _green(colV), _blue(colV)),
),
),
),
],
),
),
),
],
),
);
},
),
OpacityPeepholeCase.forAnimation(
route: kOpacityPeepholeFadeTransitionTextRouteName,
name: 'FadeTransition text',
builder: (Animation<double> animation) {
return FadeTransition(
opacity: Tween<double>(begin: 0.25, end: 0.75).animate(animation),
child: const SizedBox(
width: 300,
height: 400,
child: Center(
child: Text('Hello, World',
style: TextStyle(fontSize: 48),
),
),
),
);
},
),
];
Map<String, WidgetBuilder> opacityPeepholeRoutes = <String, WidgetBuilder>{
for (OpacityPeepholeCase variant in allOpacityPeepholeCases)
variant.route: variant.buildPage,
};
class VariantPage extends StatefulWidget {
const VariantPage({Key? key, required this.variant}) : super(key: key);
final OpacityPeepholeCase variant;
@override
State<VariantPage> createState() => VariantPageState();
}
class VariantPageState extends State<VariantPage> with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(vsync: this, duration: const Duration(seconds: 4));
_controller.repeat(reverse: true);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.variant.name),
),
body: Center(
child: widget.variant.animationBuilder(_controller),
),
);
}
}

View file

@ -0,0 +1,16 @@
// 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:macrobenchmarks/common.dart';
import 'util.dart';
void main() {
macroPerfTestE2E(
'opacity_peephole_col_of_rows_perf',
kOpacityPeepholeOpacityOfColOfRowsRouteName,
pageDelay: const Duration(seconds: 1),
duration: const Duration(seconds: 10),
);
}

View file

@ -0,0 +1,16 @@
// 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:macrobenchmarks/common.dart';
import 'util.dart';
void main() {
macroPerfTestE2E(
'opacity_peephole_fade_transition_text_perf',
kOpacityPeepholeFadeTransitionTextRouteName,
pageDelay: const Duration(seconds: 1),
duration: const Duration(seconds: 10),
);
}

View file

@ -0,0 +1,16 @@
// 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:macrobenchmarks/common.dart';
import 'util.dart';
void main() {
macroPerfTestE2E(
'opacity_peephole_grid_of_opacity_perf',
kOpacityPeepholeGridOfOpacityRouteName,
pageDelay: const Duration(seconds: 1),
duration: const Duration(seconds: 10),
);
}

View file

@ -0,0 +1,16 @@
// 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:macrobenchmarks/common.dart';
import 'util.dart';
void main() {
macroPerfTestE2E(
'opacity_peephole_one_rect_perf',
kOpacityPeepholeOneRectRouteName,
pageDelay: const Duration(seconds: 1),
duration: const Duration(seconds: 10),
);
}

View file

@ -0,0 +1,16 @@
// 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:macrobenchmarks/common.dart';
import 'util.dart';
void main() {
macroPerfTestE2E(
'opacity_peephole_opacity_of_grid_perf',
kOpacityPeepholeOpacityOfGridRouteName,
pageDelay: const Duration(seconds: 1),
duration: const Duration(seconds: 10),
);
}

View file

@ -5,7 +5,6 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:macrobenchmarks/common.dart';
import 'package:macrobenchmarks/main.dart' as app;
typedef ControlCallback = Future<void> Function(WidgetController controller);
@ -34,17 +33,21 @@ void macroPerfTestE2E(
// See: https://github.com/flutter/flutter/issues/19434
await tester.binding.delayed(const Duration(microseconds: 250));
final Finder scrollable =
find.byKey(const ValueKey<String>(kScrollableName));
expect(scrollable, findsOneWidget);
final Finder button =
find.byKey(ValueKey<String>(routeName), skipOffstage: false);
await tester.scrollUntilVisible(button, 50);
expect(button, findsOneWidget);
await tester.pumpAndSettle();
await tester.tap(button);
// Cannot be pumpAndSettle because some tests have infinite animation.
await tester.pump(const Duration(milliseconds: 20));
expect(routeName, startsWith('/'));
int i = 0;
while (i < routeName.length) {
i = routeName.indexOf('/', i + 1);
if (i < 0) {
i = routeName.length;
}
final Finder button = find.byKey(ValueKey<String>(routeName.substring(0, i)), skipOffstage: false);
await tester.scrollUntilVisible(button, 50);
expect(button, findsOneWidget);
await tester.pumpAndSettle();
await tester.tap(button);
// Cannot be pumpAndSettle because some tests have infinite animation.
await tester.pump(const Duration(milliseconds: 20));
}
if (pageDelay != null) {
// Wait for the page to load

View file

@ -0,0 +1,14 @@
// 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createOpacityPeepholeColOfRowsPerfE2ETest());
}

View file

@ -0,0 +1,14 @@
// 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createOpacityPeepholeFadeTransitionTextPerfE2ETest());
}

View file

@ -0,0 +1,14 @@
// 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createOpacityPeepholeGridOfOpacityPerfE2ETest());
}

View file

@ -0,0 +1,14 @@
// 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createOpacityPeepholeOneRectPerfE2ETest());
}

View file

@ -0,0 +1,14 @@
// 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 'package:flutter_devicelab/framework/devices.dart';
import 'package:flutter_devicelab/framework/framework.dart';
import 'package:flutter_devicelab/tasks/perf_tests.dart';
Future<void> main() async {
deviceOperatingSystem = DeviceOperatingSystem.android;
await task(createOpacityPeepholeOpacityOfGridPerfE2ETest());
}

View file

@ -484,6 +484,41 @@ TaskFunction createFramePolicyIntegrationTest() {
};
}
TaskFunction createOpacityPeepholeOneRectPerfE2ETest() {
return PerfTest.e2e(
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
'test/opacity_peephole_one_rect_perf_e2e.dart',
).run;
}
TaskFunction createOpacityPeepholeColOfRowsPerfE2ETest() {
return PerfTest.e2e(
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
'test/opacity_peephole_col_of_rows_perf_e2e.dart',
).run;
}
TaskFunction createOpacityPeepholeOpacityOfGridPerfE2ETest() {
return PerfTest.e2e(
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
'test/opacity_peephole_opacity_of_grid_perf_e2e.dart',
).run;
}
TaskFunction createOpacityPeepholeGridOfOpacityPerfE2ETest() {
return PerfTest.e2e(
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
'test/opacity_peephole_grid_of_opacity_perf_e2e.dart',
).run;
}
TaskFunction createOpacityPeepholeFadeTransitionTextPerfE2ETest() {
return PerfTest.e2e(
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
'test/opacity_peephole_fade_transition_text_perf_e2e.dart',
).run;
}
Map<String, dynamic> _average(List<Map<String, dynamic>> results, int iterations) {
final Map<String, dynamic> tally = <String, dynamic>{};
for (final Map<String, dynamic> item in results) {