forked from firka/flutter
Add a perf test for picture raster cache (#45050)
This will catch issues like https://github.com/flutter/flutter/issues/44252, and this test is inspired by the `list_demo` sample app in https://github.com/flutter/flutter/issues/43083#issue-509586473 This is tested locally on a Moto G4 before and after the fix https://github.com/flutter/engine/pull/13710 The `average_frame_rasterizer_time_millis` of this test drops from ~5.4ms to ~4.9ms after that fix.
This commit is contained in:
@@ -6,3 +6,4 @@ const String kCullOpacityRouteName = '/cull_opacity';
|
||||
const String kCubicBezierRouteName = '/cubic_bezier';
|
||||
const String kBackdropFilterRouteName = '/backdrop_filter';
|
||||
const String kSimpleAnimationRouteName = '/simple_animation';
|
||||
const String kPictureCacheRouteName = '/picture_cache';
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:macrobenchmarks/src/picture_cache.dart';
|
||||
|
||||
import 'common.dart';
|
||||
import 'src/backdrop_filter.dart';
|
||||
@@ -26,6 +27,7 @@ class MacrobenchmarksApp extends StatelessWidget {
|
||||
kCubicBezierRouteName: (BuildContext context) => CubicBezierPage(),
|
||||
kBackdropFilterRouteName: (BuildContext context) => BackdropFilterPage(),
|
||||
kSimpleAnimationRouteName: (BuildContext conttext) => SimpleAnimationPage(),
|
||||
kPictureCacheRouteName: (BuildContext conttext) => PictureCachePage(),
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -66,6 +68,13 @@ class HomePage extends StatelessWidget {
|
||||
Navigator.pushNamed(context, kSimpleAnimationRouteName);
|
||||
},
|
||||
),
|
||||
RaisedButton(
|
||||
key: const Key(kPictureCacheRouteName),
|
||||
child: const Text('Picture Cache'),
|
||||
onPressed: () {
|
||||
Navigator.pushNamed(context, kPictureCacheRouteName);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
294
dev/benchmarks/macrobenchmarks/lib/src/picture_cache.dart
Normal file
294
dev/benchmarks/macrobenchmarks/lib/src/picture_cache.dart
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright 2019 The Chromium 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 'package:flutter/rendering.dart';
|
||||
|
||||
class PictureCachePage extends StatelessWidget {
|
||||
static const List<String> kTabNames = <String>['1', '2', '3', '4', '5'];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: DefaultTabController(
|
||||
length: kTabNames.length, // This is the number of tabs.
|
||||
child: NestedScrollView(
|
||||
key: const Key('nested-scroll'), // this key is used by the driver test
|
||||
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
|
||||
// These are the slivers that show up in the "outer" scroll view.
|
||||
return <Widget>[
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
child: SliverAppBar(
|
||||
title: const Text('Picture Cache'),
|
||||
pinned: true,
|
||||
expandedHeight: 50.0,
|
||||
forceElevated: innerBoxIsScrolled,
|
||||
bottom: TabBar(
|
||||
tabs: kTabNames.map((String name) => Tab(text: name)).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: TabBarView(
|
||||
children: kTabNames.map((String name) {
|
||||
return SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Builder(
|
||||
builder: (BuildContext context) {
|
||||
return VerticalList();
|
||||
},
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class VerticalList extends StatelessWidget {
|
||||
static const int kItemCount = 100;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverOverlapInjector(
|
||||
// This is the flip side of the SliverOverlapAbsorber above.
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
),
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) => ListItem(index: index),
|
||||
childCount: kItemCount,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ListItem extends StatelessWidget {
|
||||
const ListItem({Key key, this.index})
|
||||
: super(key: key);
|
||||
|
||||
final int index;
|
||||
|
||||
static const String kMockChineseTitle = '复杂的中文标题?复杂的中文标题!';
|
||||
static const String kMockName = '李耳123456';
|
||||
static const int kMockCount = 999;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final List<Widget> contents = <Widget>[
|
||||
const SizedBox(
|
||||
height: 15,
|
||||
),
|
||||
_buildUserInfo(),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
)
|
||||
];
|
||||
if (index % 3 != 0) {
|
||||
contents.add(_buildImageContent());
|
||||
} else {
|
||||
contents.addAll(<Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 40, right: 15),
|
||||
child: _buildContentText(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 40, right: 15),
|
||||
child: _buildBottomRow(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
contents.addAll(<Widget>[
|
||||
const SizedBox(
|
||||
height: 13,
|
||||
),
|
||||
buildDivider(0.5, const EdgeInsets.only(left: 40, right: 15)),
|
||||
]);
|
||||
return MaterialButton(
|
||||
onPressed: () {},
|
||||
padding: EdgeInsets.zero,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: contents,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Text _buildRankText() {
|
||||
return Text(
|
||||
(index + 1).toString(),
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: index + 1 <= 3 ? const Color(0xFFE5645F) : Colors.black,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildImageContent() {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
width: 40,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 30),
|
||||
child: _buildContentText(),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 10,
|
||||
),
|
||||
_buildBottomRow(),
|
||||
],
|
||||
),
|
||||
),
|
||||
Image.asset(
|
||||
index % 2 == 0 ? 'food/butternut_squash_soup.png' : 'food/cherry_pie.png',
|
||||
package: 'flutter_gallery_assets',
|
||||
fit: BoxFit.cover,
|
||||
width: 110,
|
||||
height: 70,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildContentText() {
|
||||
return const Text(
|
||||
kMockChineseTitle,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomRow() {
|
||||
return Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 7,
|
||||
),
|
||||
height: 16,
|
||||
alignment: Alignment.center,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: const Color(0xFFFBEEEE),
|
||||
),
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
const SizedBox(
|
||||
width: 3,
|
||||
),
|
||||
Text(
|
||||
'hot:${_convertCountToStr(kMockCount)}',
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFE5645F),
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 9,
|
||||
),
|
||||
const Text(
|
||||
'ans:$kMockCount',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF999999),
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 9,
|
||||
),
|
||||
const Text(
|
||||
'like:$kMockCount',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF999999),
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
String _convertCountToStr(int count) {
|
||||
if (count < 10000) {
|
||||
return count.toString();
|
||||
} else if (count < 100000) {
|
||||
return (count / 10000).toStringAsPrecision(2) + 'w';
|
||||
} else {
|
||||
return (count / 10000).floor().toString() + 'w';
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildUserInfo() {
|
||||
return GestureDetector(
|
||||
onTap: () {},
|
||||
child: Row(
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 40, alignment: Alignment.center, child: _buildRankText()),
|
||||
const CircleAvatar(
|
||||
radius: 11.5,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 6,
|
||||
),
|
||||
ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 250),
|
||||
child: Text(
|
||||
kMockName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 15,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDivider(double height, EdgeInsets padding) {
|
||||
return Container(
|
||||
padding: padding,
|
||||
height: height,
|
||||
color: const Color(0xFFF5F5F5),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,5 +87,8 @@ dev_dependencies:
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
assets:
|
||||
- packages/flutter_gallery_assets/food/butternut_squash_soup.png
|
||||
- packages/flutter_gallery_assets/food/cherry_pie.png
|
||||
|
||||
# PUBSPEC CHECKSUM: eeee
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// Copyright 2019 The Chromium 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_driver/driver_extension.dart';
|
||||
import 'package:macrobenchmarks/main.dart' as app;
|
||||
|
||||
void main() {
|
||||
enableFlutterDriverExtension();
|
||||
app.main();
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2019 The Chromium 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_driver/flutter_driver.dart';
|
||||
import 'package:macrobenchmarks/common.dart';
|
||||
|
||||
import 'util.dart';
|
||||
|
||||
void main() {
|
||||
macroPerfTest(
|
||||
'picture_cache_perf',
|
||||
kPictureCacheRouteName,
|
||||
pageDelay: const Duration(seconds: 1),
|
||||
driverOps: (FlutterDriver driver) async {
|
||||
final SerializableFinder nestedScroll = find.byValueKey('nested-scroll');
|
||||
Future<void> _scrollOnce(double offset) async {
|
||||
await driver.scroll(nestedScroll, offset, 0.0, const Duration(milliseconds: 300));
|
||||
await Future<void>.delayed(const Duration(milliseconds: 500));
|
||||
}
|
||||
for (int i = 0; i < 3; i += 1) {
|
||||
await _scrollOnce(-300.0);
|
||||
await _scrollOnce(-300.0);
|
||||
await _scrollOnce(300.0);
|
||||
await _scrollOnce(300.0);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -10,7 +10,10 @@ import 'package:test/test.dart' hide TypeMatcher, isInstanceOf;
|
||||
void macroPerfTest(
|
||||
String testName,
|
||||
String routeName,
|
||||
{Duration pageDelay, Duration duration = const Duration(seconds: 3)}) {
|
||||
{ Duration pageDelay,
|
||||
Duration duration = const Duration(seconds: 3),
|
||||
Future<void> driverOps(FlutterDriver driver),
|
||||
}) {
|
||||
test(testName, () async {
|
||||
final FlutterDriver driver = await FlutterDriver.connect();
|
||||
|
||||
@@ -32,7 +35,11 @@ void macroPerfTest(
|
||||
}
|
||||
|
||||
final Timeline timeline = await driver.traceAction(() async {
|
||||
await Future<void>.delayed(duration);
|
||||
final Future<void> durationFuture = Future<void>.delayed(duration);
|
||||
if (driverOps != null) {
|
||||
await driverOps(driver);
|
||||
}
|
||||
await durationFuture;
|
||||
});
|
||||
|
||||
final TimelineSummary summary = TimelineSummary.summarize(timeline);
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// Copyright 2019 The Chromium 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/tasks/perf_tests.dart';
|
||||
import 'package:flutter_devicelab/framework/adb.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
|
||||
Future<void> main() async {
|
||||
deviceOperatingSystem = DeviceOperatingSystem.android;
|
||||
await task(createPictureCachePerfTest());
|
||||
}
|
||||
@@ -72,6 +72,14 @@ TaskFunction createSimpleAnimationPerfTest({bool needsMeasureCpuGpu = false}) {
|
||||
).run;
|
||||
}
|
||||
|
||||
TaskFunction createPictureCachePerfTest() {
|
||||
return PerfTest(
|
||||
'${flutterDirectory.path}/dev/benchmarks/macrobenchmarks',
|
||||
'test_driver/picture_cache_perf.dart',
|
||||
'picture_cache_perf',
|
||||
).run;
|
||||
}
|
||||
|
||||
TaskFunction createFlutterGalleryStartupTest() {
|
||||
return StartupTest(
|
||||
'${flutterDirectory.path}/examples/flutter_gallery',
|
||||
|
||||
@@ -167,6 +167,12 @@ tasks:
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["mac/android"]
|
||||
|
||||
picture_cache_perf__timeline_summary:
|
||||
description: >
|
||||
Measures the runtime performance of raster caching many pictures on Android.
|
||||
stage: devicelab
|
||||
required_agent_capabilities: ["mac/android"]
|
||||
|
||||
cubic_bezier_perf__timeline_summary:
|
||||
description: >
|
||||
Measures the runtime performance of cubic bezier animations on Android.
|
||||
|
||||
@@ -20,6 +20,7 @@ dependencies:
|
||||
rally_assets: 1.0.0
|
||||
|
||||
# Also update dev/benchmarks/complex_layout/pubspec.yaml
|
||||
# and dev/benchmarks/macrobenchmarks/pubspec.yaml
|
||||
flutter_gallery_assets: 0.1.9+2
|
||||
|
||||
charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
Reference in New Issue
Block a user