mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Add benchmark reproducing large static scrolling content (#53686)
This commit is contained in:
parent
bb5c340066
commit
0d07788069
@ -0,0 +1,115 @@
|
||||
// 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:ui';
|
||||
|
||||
import 'recorder.dart';
|
||||
import 'test_data.dart';
|
||||
|
||||
/// The height of each row.
|
||||
const double kRowHeight = 20.0;
|
||||
|
||||
/// Number of rows.
|
||||
const int kRows = 100;
|
||||
|
||||
/// Number of columns.
|
||||
const int kColumns = 10;
|
||||
|
||||
/// The amount the picture is scrolled on every iteration of the benchmark.
|
||||
const double kScrollDelta = 2.0;
|
||||
|
||||
/// Draws one complex picture, then moves a clip around it simulating scrolling
|
||||
/// large static content.
|
||||
///
|
||||
/// This benchmark measures how efficient we are at taking advantage of the
|
||||
/// static picture when all that changes is the clip.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * `bench_text_out_of_picture_bounds.dart`, which measures a volatile
|
||||
/// picture with a static clip.
|
||||
/// * https://github.com/flutter/flutter/issues/42987, which this benchmark is
|
||||
/// based on.
|
||||
class BenchDynamicClipOnStaticPicture extends SceneBuilderRecorder {
|
||||
BenchDynamicClipOnStaticPicture() : super(name: benchmarkName) {
|
||||
// If the scrollable extent is too small, the benchmark may end up
|
||||
// scrolling the picture out of the clip area entirely, resulting in
|
||||
// bogus metric vaules.
|
||||
const double maxScrollExtent = kMaxSampleCount * kScrollDelta;
|
||||
const double pictureHeight = kRows * kRowHeight;
|
||||
if (maxScrollExtent > pictureHeight) {
|
||||
throw Exception(
|
||||
'Bad combination of constant values kRowHeight, kRows, and '
|
||||
'kScrollData. With these numbers there is risk that the picture '
|
||||
'will scroll out of the clip entirely. To fix the issue reduce '
|
||||
'kScrollDelta, or increase either kRows or kRowHeight.'
|
||||
);
|
||||
}
|
||||
|
||||
// Create one static picture, then never change it again.
|
||||
const Color black = Color.fromARGB(255, 0, 0, 0);
|
||||
final PictureRecorder pictureRecorder = PictureRecorder();
|
||||
final Canvas canvas = Canvas(pictureRecorder);
|
||||
screenSize = window.physicalSize / window.devicePixelRatio;
|
||||
clipSize = Size(
|
||||
screenSize.width / 2,
|
||||
screenSize.height / 5,
|
||||
);
|
||||
final double cellWidth = screenSize.width / kColumns;
|
||||
|
||||
final List<Paragraph> paragraphs = generateLaidOutParagraphs(
|
||||
paragraphCount: 500,
|
||||
minWordCountPerParagraph: 3,
|
||||
maxWordCountPerParagraph: 3,
|
||||
widthConstraint: cellWidth,
|
||||
color: black,
|
||||
);
|
||||
|
||||
int paragraphCounter = 0;
|
||||
double yOffset = 0.0;
|
||||
for (int row = 0; row < kRows; row += 1) {
|
||||
for (int column = 0; column < kColumns; column += 1) {
|
||||
final double left = cellWidth * column;
|
||||
canvas.save();
|
||||
canvas.clipRect(Rect.fromLTWH(
|
||||
left,
|
||||
yOffset,
|
||||
cellWidth,
|
||||
20.0,
|
||||
));
|
||||
canvas.drawParagraph(
|
||||
paragraphs[paragraphCounter % paragraphs.length],
|
||||
Offset(left, yOffset),
|
||||
);
|
||||
canvas.restore();
|
||||
paragraphCounter += 1;
|
||||
}
|
||||
yOffset += kRowHeight;
|
||||
}
|
||||
|
||||
picture = pictureRecorder.endRecording();
|
||||
}
|
||||
|
||||
static const String benchmarkName = 'dynamic_clip_on_static_picture';
|
||||
|
||||
Size screenSize;
|
||||
Size clipSize;
|
||||
Picture picture;
|
||||
double pictureVerticalOffset = 0.0;
|
||||
|
||||
@override
|
||||
void onDrawFrame(SceneBuilder sceneBuilder) {
|
||||
// Render the exact same picture, but offset it as if it's being scrolled.
|
||||
// This will move the clip along the Y axis in picture's local coordinates
|
||||
// causing a repaint. If we're not efficient at managing clips and/or
|
||||
// repaints this will jank (see https://github.com/flutter/flutter/issues/42987).
|
||||
final Rect clip = Rect.fromLTWH(0.0, 0.0, clipSize.width, clipSize.height);
|
||||
sceneBuilder.pushClipRect(clip);
|
||||
sceneBuilder.pushOffset(0.0, pictureVerticalOffset);
|
||||
sceneBuilder.addPicture(Offset.zero, picture);
|
||||
sceneBuilder.pop();
|
||||
sceneBuilder.pop();
|
||||
pictureVerticalOffset -= kScrollDelta;
|
||||
}
|
||||
}
|
||||
@ -33,16 +33,18 @@ class BenchTextOutOfPictureBounds extends SceneBuilderRecorder {
|
||||
const Color green = Color.fromARGB(255, 0, 255, 0);
|
||||
|
||||
// We don't want paragraph generation and layout to pollute benchmark numbers.
|
||||
singleLineParagraphs = _generateParagraphs(
|
||||
singleLineParagraphs = generateLaidOutParagraphs(
|
||||
paragraphCount: 500,
|
||||
minWordCountPerParagraph: 2,
|
||||
maxWordCountPerParagraph: 5,
|
||||
maxWordCountPerParagraph: 4,
|
||||
widthConstraint: window.physicalSize.width / 2,
|
||||
color: red,
|
||||
);
|
||||
multiLineParagraphs = _generateParagraphs(
|
||||
multiLineParagraphs = generateLaidOutParagraphs(
|
||||
paragraphCount: 50,
|
||||
minWordCountPerParagraph: 30,
|
||||
maxWordCountPerParagraph: 50,
|
||||
maxWordCountPerParagraph: 49,
|
||||
widthConstraint: window.physicalSize.width / 2,
|
||||
color: green,
|
||||
);
|
||||
}
|
||||
@ -116,38 +118,4 @@ class BenchTextOutOfPictureBounds extends SceneBuilderRecorder {
|
||||
sceneBuilder.addPicture(Offset.zero, picture);
|
||||
sceneBuilder.pop();
|
||||
}
|
||||
|
||||
/// Generates strings and builds pre-laid out paragraphs to be used by the
|
||||
/// benchmark.
|
||||
List<Paragraph> _generateParagraphs({
|
||||
int paragraphCount,
|
||||
int minWordCountPerParagraph,
|
||||
int maxWordCountPerParagraph,
|
||||
Color color,
|
||||
}) {
|
||||
final List<Paragraph> strings = <Paragraph>[];
|
||||
int wordPointer = 0; // points to the next word in lipsum to extract
|
||||
for (int i = 0; i < paragraphCount; i++) {
|
||||
final int wordCount = minWordCountPerParagraph +
|
||||
_random.nextInt(maxWordCountPerParagraph - minWordCountPerParagraph);
|
||||
final List<String> string = <String>[];
|
||||
for (int j = 0; j < wordCount; j++) {
|
||||
string.add(lipsum[wordPointer]);
|
||||
wordPointer = (wordPointer + 1) % lipsum.length;
|
||||
}
|
||||
|
||||
final ParagraphBuilder builder =
|
||||
ParagraphBuilder(ParagraphStyle(fontFamily: 'sans-serif'))
|
||||
..pushStyle(TextStyle(color: color, fontSize: 18.0))
|
||||
..addText(string.join(' '))
|
||||
..pop();
|
||||
final Paragraph paragraph = builder.build();
|
||||
|
||||
// Fill half the screen.
|
||||
paragraph
|
||||
.layout(ParagraphConstraints(width: window.physicalSize.width / 2));
|
||||
strings.add(paragraph);
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
}
|
||||
|
||||
@ -17,14 +17,14 @@ import 'package:flutter/widgets.dart';
|
||||
|
||||
/// Minimum number of samples collected by a benchmark irrespective of noise
|
||||
/// levels.
|
||||
const int _kMinSampleCount = 50;
|
||||
const int kMinSampleCount = 50;
|
||||
|
||||
/// Maximum number of samples collected by a benchmark irrespective of noise
|
||||
/// levels.
|
||||
///
|
||||
/// If the noise doesn't settle down before we reach the max we'll report noisy
|
||||
/// results assuming the benchmarks is simply always noisy.
|
||||
const int _kMaxSampleCount = 10 * _kMinSampleCount;
|
||||
const int kMaxSampleCount = 10 * kMinSampleCount;
|
||||
|
||||
/// The number of samples used to extract metrics, such as noise, means,
|
||||
/// max/min values.
|
||||
@ -513,7 +513,7 @@ class Profile {
|
||||
final Timeseries timeseries = scoreData[key];
|
||||
|
||||
// Collect enough data points before considering to stop.
|
||||
if (timeseries.count < _kMinSampleCount) {
|
||||
if (timeseries.count < kMinSampleCount) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -522,11 +522,11 @@ class Profile {
|
||||
// If the timeseries has enough data, stop it, even if it's noisy under
|
||||
// the assumption that this benchmark is always noisy and there's nothing
|
||||
// we can do about it.
|
||||
if (timeseries.count > _kMaxSampleCount) {
|
||||
if (timeseries.count > kMaxSampleCount) {
|
||||
buffer.writeln(
|
||||
'WARNING: Noise of benchmark "$name.$key" did not converge below '
|
||||
'${_ratioToPercent(_kNoiseThreshold)}. Stopping because it reached the '
|
||||
'maximum number of samples $_kMaxSampleCount. Noise level is '
|
||||
'maximum number of samples $kMaxSampleCount. Noise level is '
|
||||
'${_ratioToPercent(timeseries.noise)}.',
|
||||
);
|
||||
return false;
|
||||
|
||||
@ -2,6 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
// Used to randomize data.
|
||||
//
|
||||
// Using constant seed for reproducibility.
|
||||
final math.Random _random = math.Random(0);
|
||||
|
||||
/// Random words used by benchmarks that contain text.
|
||||
final List<String> lipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing '
|
||||
'elit. Vivamus ut ligula a neque mattis posuere. Sed suscipit lobortis '
|
||||
@ -11,7 +21,7 @@ final List<String> lipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
'odio vestibulum ultricies. Nunc dolor libero, hendrerit eu urna sit '
|
||||
'amet, pretium iaculis nulla. Ut porttitor nisl et leo iaculis, vel '
|
||||
'fringilla odio pulvinar. Ut eget ligula id odio auctor egestas nec a '
|
||||
'nisl. Aliquam luctus dolor et magna posuere mattis.'
|
||||
'nisl. Aliquam luctus dolor et magna posuere mattis. '
|
||||
'Suspendisse fringilla nisl et massa congue, eget '
|
||||
'imperdiet lectus porta. Vestibulum sed dui sed dui porta imperdiet ut in risus. '
|
||||
'Fusce diam purus, faucibus id accumsan sit amet, semper a sem. Sed aliquam '
|
||||
@ -20,3 +30,37 @@ final List<String> lipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing
|
||||
'pulvinar rhoncus tellus. Nullam vel mauris semper, volutpat tellus at, sagittis '
|
||||
'lectus. Donec vitae nibh mauris. Morbi posuere sem id eros tristique tempus. '
|
||||
'Vivamus lacinia sapien neque, eu semper purus gravida ut.'.split(' ');
|
||||
|
||||
/// Generates strings and builds pre-laid out paragraphs to be used by
|
||||
/// benchmarks.
|
||||
List<Paragraph> generateLaidOutParagraphs({
|
||||
@required int paragraphCount,
|
||||
@required int minWordCountPerParagraph,
|
||||
@required int maxWordCountPerParagraph,
|
||||
@required double widthConstraint,
|
||||
@required Color color,
|
||||
}) {
|
||||
final List<Paragraph> strings = <Paragraph>[];
|
||||
int wordPointer = 0; // points to the next word in lipsum to extract
|
||||
for (int i = 0; i < paragraphCount; i++) {
|
||||
final int wordCount = minWordCountPerParagraph +
|
||||
_random.nextInt(maxWordCountPerParagraph - minWordCountPerParagraph + 1);
|
||||
final List<String> string = <String>[];
|
||||
for (int j = 0; j < wordCount; j++) {
|
||||
string.add(lipsum[wordPointer]);
|
||||
wordPointer = (wordPointer + 1) % lipsum.length;
|
||||
}
|
||||
|
||||
final ParagraphBuilder builder =
|
||||
ParagraphBuilder(ParagraphStyle(fontFamily: 'sans-serif'))
|
||||
..pushStyle(TextStyle(color: color, fontSize: 18.0))
|
||||
..addText(string.join(' '))
|
||||
..pop();
|
||||
final Paragraph paragraph = builder.build();
|
||||
|
||||
// Fill half the screen.
|
||||
paragraph.layout(ParagraphConstraints(width: widthConstraint));
|
||||
strings.add(paragraph);
|
||||
}
|
||||
return strings;
|
||||
}
|
||||
|
||||
@ -12,6 +12,7 @@ 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_draw_rect.dart';
|
||||
import 'src/web/bench_dynamic_clip_on_static_picture.dart';
|
||||
import 'src/web/bench_simple_lazy_text_scroll.dart';
|
||||
import 'src/web/bench_text_out_of_picture_bounds.dart';
|
||||
import 'src/web/recorder.dart';
|
||||
@ -30,6 +31,7 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{
|
||||
BenchTextOutOfPictureBounds.benchmarkName: () => BenchTextOutOfPictureBounds(),
|
||||
BenchSimpleLazyTextScroll.benchmarkName: () => BenchSimpleLazyTextScroll(),
|
||||
BenchBuildMaterialCheckbox.benchmarkName: () => BenchBuildMaterialCheckbox(),
|
||||
BenchDynamicClipOnStaticPicture.benchmarkName: () => BenchDynamicClipOnStaticPicture(),
|
||||
|
||||
// Benchmarks that we don't want to run using CanvasKit.
|
||||
if (!isCanvasKit) ...<String, RecorderFactory>{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user