Skip to content

Conversation

@gaaclarke
Copy link
Member

@gaaclarke gaaclarke commented May 20, 2022

This starts sharing memory between Semantics and RenderSemanticsAnnotations. I noticed a significant decrease in build times in customer: money when I ripped out all the Semantics while testing something else. I theorized that the performance increase was related to the large memory cost of Semantics where almost all of its 62 instance variables were getting copied to the RenderObject. In production apps Semantics widgets become ubiquitous and account for a large percentage of the allocations.

I did a bit of testing to see what effect his might have. I ran a before and after with customer: money where I just clicked on the first button to load the main screen and saw 108MB of allocations go to 85MB, where 210 RenderSemanticsAnnotations where allocated. That would mean a savings of ~112 KB per RenderSemanticsAnnotations. That doesn't sound plausible, I'm not sure what Dart is doing under the covers though. I would have expected something more like (48 removed ivars * ~16 bytes * 210 instances) = 161 KB total.

I did run CPU benchmark tests with build_bench.dart and didn't notice a difference, but it also isn't that representative. I did try my customer: money benchmark and didn't notice a difference either but that benchmark isn't quite good either.

I don't think it's worth spending more time trying to track down the exact extent of the changes. The cleanup of this optimization is value enough. Writing a microbenchmark just for this may not be worth it. Hopefully I can get the customer:money one showing what we want eventually.

before:
Screen Shot 2022-05-20 at 3 44 38 PM

after:
Screen Shot 2022-05-20 at 3 43 35 PM

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I signed the CLA.
  • I listed at least one issue that this PR fixes in the description above.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label May 20, 2022
@gaaclarke gaaclarke changed the title Started sharing SemanticsProperties between the Widget and the Render… Started sharing SemanticsProperties between the Widget and the RenderObject May 20, 2022
@gaaclarke gaaclarke force-pushed the share-sematics-properties branch 3 times, most recently from b4a59d1 to e873d86 Compare May 20, 2022 20:47
@flutter-dashboard flutter-dashboard bot added the a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) label May 20, 2022
@gaaclarke gaaclarke force-pushed the share-sematics-properties branch from e873d86 to f55eb80 Compare May 20, 2022 21:52
@gaaclarke gaaclarke marked this pull request as ready for review May 20, 2022 23:02
@gaaclarke gaaclarke requested a review from goderbauer May 20, 2022 23:07
@Hixie
Copy link
Contributor

Hixie commented May 20, 2022

test-exempt: code refactor with no semantic change

however if this breaks anything in flutter/tests or google, we will need a migration guide
it might be worth pro-actively searching for potentially broken code in Google that might not be covered by tests.

Copy link
Contributor

@Hixie Hixie left a comment

Choose a reason for hiding this comment

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

LGTM

cc @goderbauer for visibility

@gaaclarke
Copy link
Member Author

gaaclarke commented May 20, 2022

it might be worth pro-actively searching for potentially broken code in Google that might not be covered by tests.

I saw no references of subclassing RenderSemanticsAnnotations or allocating it. I think those are things that would potentially break.


SemanticsProperties _properties;
/// Sets the [SemanticsProperties] en masse.
void updateProperties(SemanticsProperties value) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this not just a regular setter for properties?

Copy link
Member Author

Choose a reason for hiding this comment

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

Because it's a linter error to have a setter without a getter. I guess I could add a getter safely since it's effectively immutable. I tried to give out the least amount of access necessary.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, having a getter for this is normal style for RenderObjects. Adding one should be fine.

bool? liveRegion,
int? maxValueLength,
int? currentValueLength,
AttributedString? attributedLabel,
Copy link
Member

Choose a reason for hiding this comment

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

These also exist on SmeanticsProperties, no? Why are those allowed to be passed in separately?

Copy link
Member Author

Choose a reason for hiding this comment

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

They had slightly different logic where they get wrapped in AttributeString. I was afraid that if I moved it, I might accidentally start allocating more than one AttributeString. It was hard for me to prove that I wouldn't without storing that value somewhere.

Copy link
Member

Choose a reason for hiding this comment

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

It would be fine to store that value on the RenderObject. It's just confusing that the API of the RO allowed this value to be passed in in two different ways and one just silently gets ignored.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done, moved all that logic from the widget into the renderobject.

_checked = value;
markNeedsSemanticsUpdate();
}
bool? get checked => _properties.checked;
Copy link
Member

Choose a reason for hiding this comment

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

I would be in favor of removing these getters as well, if that's not breaking anything. Are these actually used anywhere?

Copy link
Member Author

Choose a reason for hiding this comment

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

They are only used locally in the framework afaik. They are probably used in tests. There is tons of documentation written for them. I was worried that some of that information might only live there. It seems like SemanticsProperties is the proper place for that documentation though.

Copy link
Member

Choose a reason for hiding this comment

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

+1. If this information is not also available on SemanticsProperties let's move it there and get rid of these here.

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, I skimmed through the docstrings and they seem like copy and paste. I don't think there is anything lost ripping them out and all the tests passed locally so I think we are good. Done.

hidden: properties.hidden,
image: properties.image,
properties: properties,
attributedLabel: _effectiveAttributedLabel,
Copy link
Member

Choose a reason for hiding this comment

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

Ah! Maybe we should move the logic for figuring the effectiveAttributed* also down one level to avoid the confusion that attributedLabel is passed in directly, but also as part of the properties.

..onDidGainAccessibilityFocus = properties.onDidGainAccessibilityFocus
..onDidLoseAccessibilityFocus = properties.onDidLoseAccessibilityFocus
..customSemanticsActions = properties.customSemanticsActions;
..textDirection = _getTextDirection(context);
Copy link
Member

Choose a reason for hiding this comment

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

(unrelated) I wonder if we have a "bug" here: properties also sets a textdirection, but we seem to just ignore that one...

Copy link
Member Author

Choose a reason for hiding this comment

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

Yea, it seemed weird, but I just tried not to touch it. _getTextDirection does use the value of properties if it is available fwiw.

Copy link
Member

Choose a reason for hiding this comment

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

Ah, cool. That makes sense then.

@goderbauer
Copy link
Member

Nice clean-up overall!

@dnfield
Copy link
Contributor

dnfield commented May 21, 2022

test-exempt: code refactor with no semantic change

Uhhh isn't this entirely a semantic change?

I'll leave now...

@gaaclarke gaaclarke requested a review from goderbauer May 23, 2022 18:06
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


SemanticsProperties _properties;
/// Sets the [SemanticsProperties] en masse.
void updateProperties(SemanticsProperties value) {
Copy link
Member

Choose a reason for hiding this comment

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

Make this a regular getter/setter pair to follow RenderObject convention? Since SemanticsProperties is immutable anyways, this shouldn't cause any trouble.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

if (_properties == value)
return;
_properties = value;
_updateAttributedFields(_properties);
Copy link
Member

Choose a reason for hiding this comment

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

In theory, if we are worried about unnecessary object instantiations, we could just reset the attributed fields to null here and do this pre-computation work the first time describeSemanticsConfiguration is called. That way, the AttributedStrings would actually only get instantiated when they are needed (i.e. when Semantics are enabled). Not sure how much this matters. Leaving this as-is is also fine.

Copy link
Member Author

Choose a reason for hiding this comment

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

I did play around with making it late but I was worried about getting stale data on accident. I just kept it the same.

engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request May 24, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request May 24, 2022
camsim99 pushed a commit to camsim99/flutter that referenced this pull request Aug 10, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Aug 30, 2022
engine-flutter-autoroll added a commit to engine-flutter-autoroll/plugins that referenced this pull request Aug 30, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: accessibility Accessibility, e.g. VoiceOver or TalkBack. (aka a11y) framework flutter/packages/flutter repository. See also f: labels.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants