Skip to content

Commit 64a0c19

Browse files
authored
switched to a double variant of clamp to avoid boxing (#103559)
1 parent 32157e3 commit 64a0c19

File tree

69 files changed

+409
-193
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

69 files changed

+409
-193
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
import 'package:flutter/foundation.dart' show clampDouble;
6+
7+
import '../common.dart';
8+
9+
const int _kBatchSize = 100000;
10+
const int _kNumIterations = 1000;
11+
12+
void main() {
13+
assert(false,
14+
"Don't run benchmarks in debug mode! Use 'flutter run --release'.");
15+
final BenchmarkResultPrinter printer = BenchmarkResultPrinter();
16+
17+
final Stopwatch watch = Stopwatch();
18+
{
19+
final List<double> clampDoubleValues = <double>[];
20+
for (int j = 0; j < _kNumIterations; ++j) {
21+
double tally = 0;
22+
watch.reset();
23+
watch.start();
24+
for (int i = 0; i < _kBatchSize; i += 1) {
25+
tally += clampDouble(-1.0, 0.0, 1.0);
26+
tally += clampDouble(2.0, 0.0, 1.0);
27+
tally += clampDouble(0.0, 0.0, 1.0);
28+
tally += clampDouble(double.nan, 0.0, 1.0);
29+
}
30+
watch.stop();
31+
clampDoubleValues.add(watch.elapsedMicroseconds.toDouble() / _kBatchSize);
32+
if (tally < 0.0) {
33+
print("This shouldn't happen.");
34+
}
35+
}
36+
37+
printer.addResultStatistics(
38+
description: 'clamp - clampDouble',
39+
values: clampDoubleValues,
40+
unit: 'us per iteration',
41+
name: 'clamp_clampDouble',
42+
);
43+
}
44+
45+
{
46+
final List<double> doubleClampValues = <double>[];
47+
48+
for (int j = 0; j < _kNumIterations; ++j) {
49+
double tally = 0;
50+
watch.reset();
51+
watch.start();
52+
for (int i = 0; i < _kBatchSize; i += 1) {
53+
tally += -1.0.clamp(0.0, 1.0);
54+
tally += 2.0.clamp(0.0, 1.0);
55+
tally += 0.0.clamp(0.0, 1.0);
56+
tally += double.nan.clamp(0.0, 1.0);
57+
}
58+
watch.stop();
59+
doubleClampValues.add(watch.elapsedMicroseconds.toDouble() / _kBatchSize);
60+
if (tally < 0.0) {
61+
print("This shouldn't happen.");
62+
}
63+
}
64+
65+
printer.addResultStatistics(
66+
description: 'clamp - Double.clamp',
67+
values: doubleClampValues,
68+
unit: 'us per iteration',
69+
name: 'clamp_Double_clamp',
70+
);
71+
}
72+
printer.printToStdout();
73+
}

dev/bots/analyze.dart

Lines changed: 85 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ Future<void> run(List<String> arguments) async {
8888
exitWithError(<String>['The analyze.dart script must be run with --enable-asserts.']);
8989
}
9090

91+
print('$clock No Double.clamp');
92+
await verifyNoDoubleClamp(flutterRoot);
93+
9194
print('$clock All tool test files end in _test.dart...');
9295
await verifyToolTestsEndInTestDart(flutterRoot);
9396

@@ -203,6 +206,80 @@ Future<void> run(List<String> arguments) async {
203206

204207
// TESTS
205208

209+
FeatureSet _parsingFeatureSet() => FeatureSet.fromEnableFlags2(
210+
sdkLanguageVersion: Version.parse('2.17.0-0'),
211+
flags: <String>['super-parameters']);
212+
213+
_Line _getLine(ParseStringResult parseResult, int offset) {
214+
final int lineNumber =
215+
parseResult.lineInfo.getLocation(offset).lineNumber;
216+
final String content = parseResult.content.substring(
217+
parseResult.lineInfo.getOffsetOfLine(lineNumber - 1),
218+
parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1);
219+
return _Line(lineNumber, content);
220+
}
221+
222+
class _DoubleClampVisitor extends RecursiveAstVisitor<CompilationUnit> {
223+
_DoubleClampVisitor(this.parseResult);
224+
225+
final List<_Line> clamps = <_Line>[];
226+
final ParseStringResult parseResult;
227+
228+
@override
229+
CompilationUnit? visitMethodInvocation(MethodInvocation node) {
230+
if (node.methodName.name == 'clamp') {
231+
final _Line line = _getLine(parseResult, node.function.offset);
232+
if (!line.content.contains('// ignore_clamp_double_lint')) {
233+
clamps.add(line);
234+
}
235+
}
236+
237+
node.visitChildren(this);
238+
return null;
239+
}
240+
}
241+
242+
/// Verify that we use clampDouble instead of Double.clamp for performance reasons.
243+
///
244+
/// We currently can't distinguish valid uses of clamp from problematic ones so
245+
/// if the clamp is operating on a type other than a `double` the
246+
/// `// ignore_clamp_double_lint` comment must be added to the line where clamp is
247+
/// invoked.
248+
///
249+
/// See also:
250+
/// * https://github.com/flutter/flutter/pull/103559
251+
/// * https://github.com/flutter/flutter/issues/103917
252+
Future<void> verifyNoDoubleClamp(String workingDirectory) async {
253+
final String flutterLibPath = '$workingDirectory/packages/flutter/lib';
254+
final Stream<File> testFiles =
255+
_allFiles(flutterLibPath, 'dart', minimumMatches: 100);
256+
final List<String> errors = <String>[];
257+
await for (final File file in testFiles) {
258+
try {
259+
final ParseStringResult parseResult = parseFile(
260+
featureSet: _parsingFeatureSet(),
261+
path: file.absolute.path,
262+
);
263+
final _DoubleClampVisitor visitor = _DoubleClampVisitor(parseResult);
264+
visitor.visitCompilationUnit(parseResult.unit);
265+
for (final _Line clamp in visitor.clamps) {
266+
errors.add('${file.path}:${clamp.line}: `clamp` method used without ignore_clamp_double_lint comment.');
267+
}
268+
} catch (ex) {
269+
// TODO(gaaclarke): There is a bug with super parameter parsing on mac so
270+
// we skip certain files until that is fixed.
271+
// https://github.com/dart-lang/sdk/issues/49032
272+
print('skipping ${file.path}: $ex');
273+
}
274+
}
275+
if (errors.isNotEmpty) {
276+
exitWithError(<String>[
277+
...errors,
278+
'\n${bold}See: https://github.com/flutter/flutter/pull/103559',
279+
]);
280+
}
281+
}
282+
206283
/// Verify tool test files end in `_test.dart`.
207284
///
208285
/// The test runner will only recognize files ending in `_test.dart` as tests to
@@ -518,16 +595,16 @@ Future<int> _verifyNoMissingLicenseForExtension(
518595
return 0;
519596
}
520597

521-
class _TestSkip {
522-
_TestSkip(this.line, this.content);
598+
class _Line {
599+
_Line(this.line, this.content);
523600

524601
final int line;
525602
final String content;
526603
}
527604

528-
Iterable<_TestSkip> _getTestSkips(File file) {
605+
Iterable<_Line> _getTestSkips(File file) {
529606
final ParseStringResult parseResult = parseFile(
530-
featureSet: FeatureSet.fromEnableFlags2(sdkLanguageVersion: Version.parse('2.17.0-0'), flags: <String>['super-parameters']),
607+
featureSet: _parsingFeatureSet(),
531608
path: file.absolute.path,
532609
);
533610
final _TestSkipLinesVisitor<CompilationUnit> visitor = _TestSkipLinesVisitor<CompilationUnit>(parseResult);
@@ -536,10 +613,10 @@ Iterable<_TestSkip> _getTestSkips(File file) {
536613
}
537614

538615
class _TestSkipLinesVisitor<T> extends RecursiveAstVisitor<T> {
539-
_TestSkipLinesVisitor(this.parseResult) : skips = <_TestSkip>{};
616+
_TestSkipLinesVisitor(this.parseResult) : skips = <_Line>{};
540617

541618
final ParseStringResult parseResult;
542-
final Set<_TestSkip> skips;
619+
final Set<_Line> skips;
543620

544621
static bool isTestMethod(String name) {
545622
return name.startsWith('test') || name == 'group' || name == 'expect';
@@ -550,10 +627,7 @@ class _TestSkipLinesVisitor<T> extends RecursiveAstVisitor<T> {
550627
if (isTestMethod(node.methodName.toString())) {
551628
for (final Expression argument in node.argumentList.arguments) {
552629
if (argument is NamedExpression && argument.name.label.name == 'skip') {
553-
final int lineNumber = parseResult.lineInfo.getLocation(argument.beginToken.charOffset).lineNumber;
554-
final String content = parseResult.content.substring(parseResult.lineInfo.getOffsetOfLine(lineNumber - 1),
555-
parseResult.lineInfo.getOffsetOfLine(lineNumber) - 1);
556-
skips.add(_TestSkip(lineNumber, content));
630+
skips.add(_getLine(parseResult, argument.beginToken.charOffset));
557631
}
558632
}
559633
}
@@ -571,7 +645,7 @@ Future<void> verifySkipTestComments(String workingDirectory) async {
571645
.where((File f) => f.path.endsWith('_test.dart'));
572646

573647
await for (final File file in testFiles) {
574-
for (final _TestSkip skip in _getTestSkips(file)) {
648+
for (final _Line skip in _getTestSkips(file)) {
575649
final Match? match = _skipTestCommentPattern.firstMatch(skip.content);
576650
final String? skipComment = match?.group(1);
577651
if (skipComment == null ||

dev/devicelab/lib/tasks/microbenchmarks.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ TaskFunction createMicrobenchmarkTask() {
6060
...await runMicrobench('lib/language/sync_star_bench.dart'),
6161
...await runMicrobench('lib/language/sync_star_semantics_bench.dart'),
6262
...await runMicrobench('lib/foundation/all_elements_bench.dart'),
63+
...await runMicrobench('lib/foundation/clamp.dart'),
6364
...await runMicrobench('lib/foundation/change_notifier_bench.dart'),
6465
...await runMicrobench('lib/foundation/standard_method_codec_bench.dart'),
6566
...await runMicrobench('lib/foundation/standard_message_codec_bench.dart'),

packages/flutter/lib/foundation.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export 'src/foundation/diagnostics.dart';
3333
export 'src/foundation/isolates.dart';
3434
export 'src/foundation/key.dart';
3535
export 'src/foundation/licenses.dart';
36+
export 'src/foundation/math.dart';
3637
export 'src/foundation/node.dart';
3738
export 'src/foundation/object.dart';
3839
export 'src/foundation/observer_list.dart';

packages/flutter/lib/src/animation/animation_controller.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -395,7 +395,7 @@ class AnimationController extends Animation<double>
395395
}
396396

397397
void _internalSetValue(double newValue) {
398-
_value = newValue.clamp(lowerBound, upperBound);
398+
_value = clampDouble(newValue, lowerBound, upperBound);
399399
if (_value == lowerBound) {
400400
_status = AnimationStatus.dismissed;
401401
} else if (_value == upperBound) {
@@ -598,7 +598,7 @@ class AnimationController extends Animation<double>
598598
stop();
599599
if (simulationDuration == Duration.zero) {
600600
if (value != target) {
601-
_value = target.clamp(lowerBound, upperBound);
601+
_value = clampDouble(target, lowerBound, upperBound);
602602
notifyListeners();
603603
}
604604
_status = (_direction == _AnimationDirection.forward) ?
@@ -741,7 +741,7 @@ class AnimationController extends Animation<double>
741741
assert(!isAnimating);
742742
_simulation = simulation;
743743
_lastElapsedDuration = Duration.zero;
744-
_value = simulation.x(0.0).clamp(lowerBound, upperBound);
744+
_value = clampDouble(simulation.x(0.0), lowerBound, upperBound);
745745
final TickerFuture result = _ticker!.start();
746746
_status = (_direction == _AnimationDirection.forward) ?
747747
AnimationStatus.forward :
@@ -820,7 +820,7 @@ class AnimationController extends Animation<double>
820820
_lastElapsedDuration = elapsed;
821821
final double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.microsecondsPerSecond;
822822
assert(elapsedInSeconds >= 0.0);
823-
_value = _simulation!.x(elapsedInSeconds).clamp(lowerBound, upperBound);
823+
_value = clampDouble(_simulation!.x(elapsedInSeconds), lowerBound, upperBound);
824824
if (_simulation!.isDone(elapsedInSeconds)) {
825825
_status = (_direction == _AnimationDirection.forward) ?
826826
AnimationStatus.completed :
@@ -855,7 +855,7 @@ class _InterpolationSimulation extends Simulation {
855855

856856
@override
857857
double x(double timeInSeconds) {
858-
final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
858+
final double t = clampDouble(timeInSeconds / _durationInSeconds, 0.0, 1.0);
859859
if (t == 0.0)
860860
return _begin;
861861
else if (t == 1.0)

packages/flutter/lib/src/animation/curves.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ class Interval extends Curve {
182182
assert(end >= 0.0);
183183
assert(end <= 1.0);
184184
assert(end >= begin);
185-
t = ((t - begin) / (end - begin)).clamp(0.0, 1.0);
185+
t = clampDouble((t - begin) / (end - begin), 0.0, 1.0);
186186
if (t == 0.0 || t == 1.0)
187187
return t;
188188
return curve.transform(t);

packages/flutter/lib/src/cupertino/context_menu.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:math' as math;
66
import 'dart:ui' as ui;
7+
78
import 'package:flutter/foundation.dart';
89
import 'package:flutter/gestures.dart' show kMinFlingVelocity, kLongPressTimeout;
910
import 'package:flutter/scheduler.dart';
@@ -958,7 +959,7 @@ class _ContextMenuRouteStaticState extends State<_ContextMenuRouteStatic> with T
958959
_moveAnimation = Tween<Offset>(
959960
begin: Offset.zero,
960961
end: Offset(
961-
endX.clamp(-_kPadding, _kPadding),
962+
clampDouble(endX, -_kPadding, _kPadding),
962963
endY,
963964
),
964965
).animate(

packages/flutter/lib/src/cupertino/desktop_text_selection.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:flutter/foundation.dart' show clampDouble;
56
import 'package:flutter/gestures.dart';
67
import 'package:flutter/rendering.dart';
78
import 'package:flutter/widgets.dart';
@@ -157,7 +158,7 @@ class _CupertinoDesktopTextSelectionControlsToolbarState extends State<_Cupertin
157158
final MediaQueryData mediaQuery = MediaQuery.of(context);
158159

159160
final Offset midpointAnchor = Offset(
160-
(widget.selectionMidpoint.dx - widget.globalEditableRegion.left).clamp(
161+
clampDouble(widget.selectionMidpoint.dx - widget.globalEditableRegion.left,
161162
mediaQuery.padding.left,
162163
mediaQuery.size.width - mediaQuery.padding.right,
163164
),

packages/flutter/lib/src/cupertino/refresh.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:math';
66

7+
import 'package:flutter/foundation.dart' show clampDouble;
78
import 'package:flutter/rendering.dart';
89
import 'package:flutter/scheduler.dart';
910
import 'package:flutter/services.dart';
@@ -375,7 +376,7 @@ class CupertinoSliverRefreshControl extends StatefulWidget {
375376
double refreshTriggerPullDistance,
376377
double refreshIndicatorExtent,
377378
) {
378-
final double percentageComplete = (pulledExtent / refreshTriggerPullDistance).clamp(0.0, 1.0);
379+
final double percentageComplete = clampDouble(pulledExtent / refreshTriggerPullDistance, 0.0, 1.0);
379380

380381
// Place the indicator at the top of the sliver that opens up. Note that we're using
381382
// a Stack/Positioned widget because the CupertinoActivityIndicator does some internal

packages/flutter/lib/src/cupertino/slider.dart

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import 'dart:math' as math;
66
import 'dart:ui' show lerpDouble;
77

8+
import 'package:flutter/foundation.dart' show clampDouble;
89
import 'package:flutter/gestures.dart';
910
import 'package:flutter/rendering.dart';
1011
import 'package:flutter/widgets.dart';
@@ -434,7 +435,7 @@ class _RenderCupertinoSlider extends RenderConstrainedBox {
434435
double _currentDragValue = 0.0;
435436

436437
double get _discretizedCurrentDragValue {
437-
double dragValue = _currentDragValue.clamp(0.0, 1.0);
438+
double dragValue = clampDouble(_currentDragValue, 0.0, 1.0);
438439
if (divisions != null)
439440
dragValue = (dragValue * divisions!).round() / divisions!;
440441
return dragValue;
@@ -554,20 +555,20 @@ class _RenderCupertinoSlider extends RenderConstrainedBox {
554555
config.onIncrease = _increaseAction;
555556
config.onDecrease = _decreaseAction;
556557
config.value = '${(value * 100).round()}%';
557-
config.increasedValue = '${((value + _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
558-
config.decreasedValue = '${((value - _semanticActionUnit).clamp(0.0, 1.0) * 100).round()}%';
558+
config.increasedValue = '${(clampDouble(value + _semanticActionUnit, 0.0, 1.0) * 100).round()}%';
559+
config.decreasedValue = '${(clampDouble(value - _semanticActionUnit, 0.0, 1.0) * 100).round()}%';
559560
}
560561
}
561562

562563
double get _semanticActionUnit => divisions != null ? 1.0 / divisions! : _kAdjustmentUnit;
563564

564565
void _increaseAction() {
565566
if (isInteractive)
566-
onChanged!((value + _semanticActionUnit).clamp(0.0, 1.0));
567+
onChanged!(clampDouble(value + _semanticActionUnit, 0.0, 1.0));
567568
}
568569

569570
void _decreaseAction() {
570571
if (isInteractive)
571-
onChanged!((value - _semanticActionUnit).clamp(0.0, 1.0));
572+
onChanged!(clampDouble(value - _semanticActionUnit, 0.0, 1.0));
572573
}
573574
}

0 commit comments

Comments
 (0)