Unverified Commit 0d077880 authored by Yegor's avatar Yegor Committed by GitHub

Add benchmark reproducing large static scrolling content (#53686)

parent bb5c3400
// 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 { ...@@ -33,16 +33,18 @@ class BenchTextOutOfPictureBounds extends SceneBuilderRecorder {
const Color green = Color.fromARGB(255, 0, 255, 0); const Color green = Color.fromARGB(255, 0, 255, 0);
// We don't want paragraph generation and layout to pollute benchmark numbers. // We don't want paragraph generation and layout to pollute benchmark numbers.
singleLineParagraphs = _generateParagraphs( singleLineParagraphs = generateLaidOutParagraphs(
paragraphCount: 500, paragraphCount: 500,
minWordCountPerParagraph: 2, minWordCountPerParagraph: 2,
maxWordCountPerParagraph: 5, maxWordCountPerParagraph: 4,
widthConstraint: window.physicalSize.width / 2,
color: red, color: red,
); );
multiLineParagraphs = _generateParagraphs( multiLineParagraphs = generateLaidOutParagraphs(
paragraphCount: 50, paragraphCount: 50,
minWordCountPerParagraph: 30, minWordCountPerParagraph: 30,
maxWordCountPerParagraph: 50, maxWordCountPerParagraph: 49,
widthConstraint: window.physicalSize.width / 2,
color: green, color: green,
); );
} }
...@@ -116,38 +118,4 @@ class BenchTextOutOfPictureBounds extends SceneBuilderRecorder { ...@@ -116,38 +118,4 @@ class BenchTextOutOfPictureBounds extends SceneBuilderRecorder {
sceneBuilder.addPicture(Offset.zero, picture); sceneBuilder.addPicture(Offset.zero, picture);
sceneBuilder.pop(); 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'; ...@@ -17,14 +17,14 @@ import 'package:flutter/widgets.dart';
/// Minimum number of samples collected by a benchmark irrespective of noise /// Minimum number of samples collected by a benchmark irrespective of noise
/// levels. /// levels.
const int _kMinSampleCount = 50; const int kMinSampleCount = 50;
/// Maximum number of samples collected by a benchmark irrespective of noise /// Maximum number of samples collected by a benchmark irrespective of noise
/// levels. /// levels.
/// ///
/// If the noise doesn't settle down before we reach the max we'll report noisy /// If the noise doesn't settle down before we reach the max we'll report noisy
/// results assuming the benchmarks is simply always 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, /// The number of samples used to extract metrics, such as noise, means,
/// max/min values. /// max/min values.
...@@ -513,7 +513,7 @@ class Profile { ...@@ -513,7 +513,7 @@ class Profile {
final Timeseries timeseries = scoreData[key]; final Timeseries timeseries = scoreData[key];
// Collect enough data points before considering to stop. // Collect enough data points before considering to stop.
if (timeseries.count < _kMinSampleCount) { if (timeseries.count < kMinSampleCount) {
return true; return true;
} }
...@@ -522,11 +522,11 @@ class Profile { ...@@ -522,11 +522,11 @@ class Profile {
// If the timeseries has enough data, stop it, even if it's noisy under // 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 // the assumption that this benchmark is always noisy and there's nothing
// we can do about it. // we can do about it.
if (timeseries.count > _kMaxSampleCount) { if (timeseries.count > kMaxSampleCount) {
buffer.writeln( buffer.writeln(
'WARNING: Noise of benchmark "$name.$key" did not converge below ' 'WARNING: Noise of benchmark "$name.$key" did not converge below '
'${_ratioToPercent(_kNoiseThreshold)}. Stopping because it reached the ' '${_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)}.', '${_ratioToPercent(timeseries.noise)}.',
); );
return false; return false;
......
...@@ -2,6 +2,16 @@ ...@@ -2,6 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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. /// Random words used by benchmarks that contain text.
final List<String> lipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing ' final List<String> lipsum = 'Lorem ipsum dolor sit amet, consectetur adipiscing '
'elit. Vivamus ut ligula a neque mattis posuere. Sed suscipit lobortis ' '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 ...@@ -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 ' 'odio vestibulum ultricies. Nunc dolor libero, hendrerit eu urna sit '
'amet, pretium iaculis nulla. Ut porttitor nisl et leo iaculis, vel ' 'amet, pretium iaculis nulla. Ut porttitor nisl et leo iaculis, vel '
'fringilla odio pulvinar. Ut eget ligula id odio auctor egestas nec a ' '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 ' 'Suspendisse fringilla nisl et massa congue, eget '
'imperdiet lectus porta. Vestibulum sed dui sed dui porta imperdiet ut in risus. ' '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 ' '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 ...@@ -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 ' 'pulvinar rhoncus tellus. Nullam vel mauris semper, volutpat tellus at, sagittis '
'lectus. Donec vitae nibh mauris. Morbi posuere sem id eros tristique tempus. ' 'lectus. Donec vitae nibh mauris. Morbi posuere sem id eros tristique tempus. '
'Vivamus lacinia sapien neque, eu semper purus gravida ut.'.split(' '); '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'; ...@@ -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_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_dynamic_clip_on_static_picture.dart';
import 'src/web/bench_simple_lazy_text_scroll.dart'; import 'src/web/bench_simple_lazy_text_scroll.dart';
import 'src/web/bench_text_out_of_picture_bounds.dart'; import 'src/web/bench_text_out_of_picture_bounds.dart';
import 'src/web/recorder.dart'; import 'src/web/recorder.dart';
...@@ -30,6 +31,7 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{ ...@@ -30,6 +31,7 @@ final Map<String, RecorderFactory> benchmarks = <String, RecorderFactory>{
BenchTextOutOfPictureBounds.benchmarkName: () => BenchTextOutOfPictureBounds(), BenchTextOutOfPictureBounds.benchmarkName: () => BenchTextOutOfPictureBounds(),
BenchSimpleLazyTextScroll.benchmarkName: () => BenchSimpleLazyTextScroll(), BenchSimpleLazyTextScroll.benchmarkName: () => BenchSimpleLazyTextScroll(),
BenchBuildMaterialCheckbox.benchmarkName: () => BenchBuildMaterialCheckbox(), BenchBuildMaterialCheckbox.benchmarkName: () => BenchBuildMaterialCheckbox(),
BenchDynamicClipOnStaticPicture.benchmarkName: () => BenchDynamicClipOnStaticPicture(),
// Benchmarks that we don't want to run using CanvasKit. // Benchmarks that we don't want to run using CanvasKit.
if (!isCanvasKit) ...<String, RecorderFactory>{ if (!isCanvasKit) ...<String, RecorderFactory>{
......
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