Switched Element.renderObject to iterative implementation.#112885
Switched Element.renderObject to iterative implementation.#112885auto-submit[bot] merged 4 commits intoflutter:masterfrom
Element.renderObject to iterative implementation.#112885Conversation
|
It looks like this pull request may not have tests. Please make sure to add tests before merging. If you need an exemption to this rule, contact Hixie on the #hackers channel in Chat (don't just cc him here, he won't see it! He's on Discord!). If you are not sure if you need tests, consider this rule of thumb: the purpose of a test is to make sure someone doesn't accidentally revert the fix. Ask yourself, is there anything in your PR that you feel it is important we not accidentally revert back to how it was before your fix? Reviewers: Read the Tree Hygiene page and make sure this patch meets those guidelines before LGTMing. |
Element.renderObject to iterative implementation.
|
@goderbauer I was just curious and started looking at these micro-optimizations for the framework. I'm not sure how heavy we want to be about building tests around these micro-optimizations. If you gave me the go ahead I could crank out tons of these for |
| return; | ||
| } else if (element is RenderObjectElement) { | ||
| result = element.renderObject; | ||
| final Queue<Element> elements = Queue<Element>(); |
There was a problem hiding this comment.
I've generally found that data structures besides list/map/set are not particularly fast in Dart. I'm curious if you tried with just a plain List
There was a problem hiding this comment.
I couldn't use a List because "removeFirst" would be O(n). In order to use a List directly I'd have to do something like a RingBuffer, but then there is the risk of overflow.
There was a problem hiding this comment.
Fair enough, I just checked and the default Queue in dart is a ListQueue with a resizing ring buffer. No need to re-implement.
| if (current._lifecycleState == _ElementLifecycle.defunct) { | ||
| break; | ||
| } else if (current is RenderObjectElement) { | ||
| result = current.renderObject; |
There was a problem hiding this comment.
FYI there is a slight change here that may not be a problem. The old version, if you have multiple children that are RenderObjectElement, the last one visited will be returned. This implementation stops execution at the first one found.
There was a problem hiding this comment.
I'm surprised by the old behavior. I wonder if that was intentional or not?
There was a problem hiding this comment.
It's hard for me to believe it was intentional, the documentation states that the order of visitChildren is undefined so it can't be meaningful that the last one get precedence.
There was a problem hiding this comment.
Is there really a functional change here? The old code contained this assert: assert(result == null); which was enforcing that there can only ever be one child. If there's only ever one child, then it doesn't matter whether you return the first one or last one. It's the same one?
There was a problem hiding this comment.
In fact, we should keep that assert around, adapted to the new code. It is invalid to call this if the Element has multiple children. An assert should warn you about this instead of just silently returning a "random" child.
There was a problem hiding this comment.
Also, since there's only going to be one child, you probably don't even need the Queue?
There was a problem hiding this comment.
@goderbauer good call, I removed the assert when I was writing the benchmark to make sure it didn't mess up the results and missed it's purpose. I made the change, it is surprisingly only slightly faster than the one that allocates the queue.
| if (current._lifecycleState == _ElementLifecycle.defunct) { | ||
| break; | ||
| } else if (current is RenderObjectElement) { | ||
| result = current.renderObject; |
There was a problem hiding this comment.
Is there really a functional change here? The old code contained this assert: assert(result == null); which was enforcing that there can only ever be one child. If there's only ever one child, then it doesn't matter whether you return the first one or last one. It's the same one?
| if (current._lifecycleState == _ElementLifecycle.defunct) { | ||
| break; | ||
| } else if (current is RenderObjectElement) { | ||
| result = current.renderObject; |
There was a problem hiding this comment.
In fact, we should keep that assert around, adapted to the new code. It is invalid to call this if the Element has multiple children. An assert should warn you about this instead of just silently returning a "random" child.
| if (current._lifecycleState == _ElementLifecycle.defunct) { | ||
| break; | ||
| } else if (current is RenderObjectElement) { | ||
| result = current.renderObject; |
There was a problem hiding this comment.
Also, since there's only going to be one child, you probably don't even need the Queue?
issue: #112888
The iterative implementation is 30% faster in local testing on x86_64 AOT where
Element is RenderObjectElement. (97% faster with JIT).Benchmark:
ouput
Pre-launch Checklist
///).If you need help, consider asking for advice on the #hackers-new channel on Discord.