Skip to content

Conversation

@jonahwilliams
Copy link
Contributor

Running #94585, HashSet constructor shows up in the top 10 or so hot functions. Since this is unused in profile/release mode and private - just set to null.

@dnfield

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Dec 9, 2021
@jonahwilliams
Copy link
Contributor Author

Also fyi @srawlins , the lint this is hitting seems a bit bogus. I wrote:

final Set<Element>? _debugForgottenChildrenWithGlobalKey = kDebugMode ? HashSet<Element>() : null;

And I can satisfy the lint by switching it to

late final Set<Element>? _debugForgottenChildrenWithGlobalKey = kDebugMode ? HashSet<Element>() : null;

Which seems pointless (and worse IMO).

// [update] to remove the global key reservations of forgotten children.
final Set<Element> _debugForgottenChildrenWithGlobalKey = HashSet<Element>();
// Initialze to null in profile/release mode to avoid allocating unused sets
final Set<Element>? _debugForgottenChildrenWithGlobalKey = kDebugMode ? HashSet<Element>() : null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this tree shaken away as a write only field?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤷 is it write only?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes - it's assigned on creation and then never used again outside of asserts. @alexmarkov do you know why this would still show up in profile builds using --aot --tfa?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do those flags apply in profile mode? (I think so)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure it's this one here and not the one in _updateInheritence?

Looking at a release snapshot this is definitely shaken away. It looks like it is in the profile one as well if I'm reading the tooling output correctly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#94955 seems to substantially improve build times on Android Go. Running more benchmarks.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems like a good deal!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Digging a little more on this, late final does not end up letting the field get tree shaken, but your version here does.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I think we'd probably want to eliminate both the field and initializer. WDUT about reopening this (and applying it to all the complex fields ?)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sgtm

@jonahwilliams
Copy link
Contributor Author

I'll close this in favor of Dan's change, thanks all!

@jonahwilliams jonahwilliams deleted the unused_set branch December 10, 2021 02:17
@jonahwilliams jonahwilliams restored the unused_set branch December 11, 2021 18:14
@jonahwilliams jonahwilliams reopened this Dec 11, 2021
lockState(() {
_inactiveElements._unmountAll(); // this unregisters the GlobalKeys
});
lockState(_inactiveElements._unmountAll); // this unregisters the GlobalKeys
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unrelated, can't help myself

@jonahwilliams
Copy link
Contributor Author

Open to ideas for testing, some thoughts:

I could create a private test/release mode test combo that allowed me to assert these fields are null. but, the issue we don't actually care if these fields are null - just that they are eliminated along with their initializers in profile/release mode (and I don't particularly care how that happens).

A more appropriate test might be to analyze a compiled application, but I don't think we have any tools to do that in the SDK currently?

@dnfield
Copy link
Contributor

dnfield commented Dec 12, 2021

Maybe a test that tracks the size of the new Element constructor from the heap snapshot? That and RenderObject seem like good things to try to drive down, although it wouldn't strictly catch this. I also expect this will show some improvement in our transition perf benchmarks.

@jonahwilliams
Copy link
Contributor Author

benchmarks are good but ideally it would just be a pass/fail signal

@dnfield
Copy link
Contributor

dnfield commented Dec 12, 2021

Yeah, the only thing I can think of is measuring the size of the Element constructor with and without the change after gen_snapshot does its thing.

@jonahwilliams
Copy link
Contributor Author

I could assert that the size was smaller than it was before this change. that would allow a bit of wiggle room though it would require some explanation in the test as to why it might have failed and how to debug.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a cool find!


// In Profile/Release mode this field is initialized to `null`. The Dart compiler can
// eliminate unused fields, but not their initializers.
final Set<Element>? _debugIllFatedElements = kDebugMode ? HashSet<Element>() : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Can we come up with some kind of a check to avoid re-introducing debug only fields that are not initialized to null in release mode? A lint is probably a little to specific?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to maintain it, I could write a regex test... :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think a regex test is probably the best we can do right now. We should just add comments explaining what it does and why.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started writing a regex for this last week and thoguht we'd probably have to do some kind of post filtering on it to avoid it flagging things that are trivial to initialize like bools or ints.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe analyzer package is a better way to do it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, ideally we would apply this to all fields with debug in the name. The analysis should be able to detect that something is initialized to a const value and not complain? (At least for the likely simple cases).

At some point we could/should extent the analysis to ensure that debug only fields are only used from asserts and other debugOnly methods to ensure they will actually be shaken out.

Having this opt-in for now is fine, too, though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think super ideally the compiler would just eliminate both the field and the initializer and we could delete the test :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lol, yes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The compiler would have to understand the difference between side effects you want and side effects you don't. But maybe this could be special cased.. It's just a weird perf cliff :\

You could imagine a type that actually does work in its initializer though, and users depend (perhaps unintentionally) on that work getting done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bugs @mraleph has filed related to this would be helpful

@jonahwilliams jonahwilliams marked this pull request as ready for review December 14, 2021 16:38
@flutter-dashboard flutter-dashboard bot added the c: contributor-productivity Team-specific productivity, code health, technical debt. label Dec 14, 2021
@jonahwilliams
Copy link
Contributor Author

analysis test added, PTAL

final List<File> files = await _gitFiles(workingDirectory);
for (final File file in files) {
if (path.basename(file.path).endsWith('_test.dart'))
if (path.basename(file.path).endsWith('_test.dart') || path.basename(file.path) == 'analyze.dart')
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this the check trips on analyze.dart itself if you've changed it

}

const String _kDebugOnlyAnnotation = '@_debugOnly';
final RegExp _nullInitializedField = RegExp(r'kDebugMode \? [\w\<\> ,{}()]+ : null;');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't too bad using a Regex

Copy link
Contributor

@dnfield dnfield left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM


// In Profile/Release mode this field is initialized to `null`. The Dart compiler can
// eliminate unused fields, but not their initializers.
final Set<Element>? _debugIllFatedElements = kDebugMode ? HashSet<Element>() : null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, ideally we would apply this to all fields with debug in the name. The analysis should be able to detect that something is initialized to a const value and not complain? (At least for the likely simple cases).

At some point we could/should extent the analysis to ensure that debug only fields are only used from asserts and other debugOnly methods to ensure they will actually be shaken out.

Having this opt-in for now is fine, too, though.

@dnfield dnfield merged commit fda4094 into flutter:master Dec 14, 2021
@jonahwilliams jonahwilliams deleted the unused_set branch December 14, 2021 19:10
dnfield added a commit that referenced this pull request Dec 15, 2021
@zanderso
Copy link
Member

Due to an infrastructure issue, we're missing transition_perf benchmark data for the range where this commit landed. Also in the same range, the device that the benchmark was run on was changed. To make a long story short, I'm interested in the impact if any that this change had on performance of our benchmarks. Does anyone have any numbers on that handy?

@jonahwilliams
Copy link
Contributor Author

could always revert and reland 🤣

I think @dnfield is the one with the wonky android device that goes really slow and shows the exaggerated impact

@jonahwilliams
Copy link
Contributor Author

@zanderso running the benchmark from #95596 on a pixel 4.

Before this change, average time of 0.9118845 seconds. After this change, average time of 0.7123856 seconds (10 runs).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

c: contributor-productivity Team-specific productivity, code health, technical debt. framework flutter/packages/flutter repository. See also f: labels.

5 participants