Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimise vtag #1447

Merged
merged 65 commits into from
Aug 22, 2020
Merged

Optimise vtag #1447

merged 65 commits into from
Aug 22, 2020

Conversation

bakape
Copy link
Contributor

@bakape bakape commented Jul 25, 2020

Description

Optimized overall performance of VTag operations, mostly focusing on speeding up construction, reducing allocations and memory footprint.

Optimizations
  • Enabled yew-macro to detect literals in most cases. These are set in VTag as static lifetime strings and thus notably reduce the need to allocate Strings (depends on HTML parametrization level). These include:
    • Attribute keys and values
    • HTML tag names
    • VText contents
  • Reduced memory footprint of Vtag, by excluding impossible used field combinations using an enum.
  • Removed key from VList. Arguably a very niche feature that still increased the memory footprint of VList and anything using it by 3 words.
  • In-place non-dynamic VTag construction via yew-macro
  • Inlined static class literal and href assignment into the attribute array construction to reduce possible reallocations
  • Changed Attributes to be stored inside a Vec of key-value pairs. This has the benefit of not needing to construct HashMaps on VTag construction and that most scenarios will not encounter a change in attribute list length and key order.
  • Added static if guards to dynamic tag name VTags to enable the compiler to more eagerly optimise out encased calls.
Breaking changes
  • In-place VTag construction can create previously not present moved value usage conflicts.
  • Removed key from VList.
  • Removed kind attribute from VTag. It's use cases are now fully covered with the type attribute.

Checklist:

  • I have run ./ci/run_stable_checks.sh
  • I have reviewed my own code
  • I have added tests

@siku2
Copy link
Member

siku2 commented Jul 25, 2020

Before I take a closer look at this, there are a few things I've noticed:

  • There are a number of cases where the error span has regressed to Span::call_site (points at the entire macro invocation instead of pointing at a specific location)
  • Using #v.to_string() emits less useful error messages than ::std::string::ToString::to_string(#v). This is true for all cases where you need to call an associated method.
  • I don't think removing key from VList is acceptable. Admittedly, keys aren't really being used yet but they should bring major performance gains when used for big lists that change frequently.

As a whole this looks very promising but this PR does a little too much in my opinion.
Could you limit the scope of this PR to just the String optimizations for now?

@bakape
Copy link
Contributor Author

bakape commented Jul 25, 2020

There are a number of cases where the error span has regressed to Span::call_site (points at the entire macro invocation instead of pointing at a specific location)

I'll look into that.

Using #v.to_string() emits less useful error messages than ::std::string::ToString::to_string(#v). This is true for all cases where you need to call an associated method.

Noted.

I don't think removing key from VList is acceptable. Admittedly, keys aren't really being used yet but they should bring major performance gains when used for big lists that change frequently.

I might be misunderstanding the point of keys on VList then. Obvious what they are for on individual nodes but not quite for lists.

Could you limit the scope of this PR to just the String optimizations for now?

This PR is about reducing memory usage for VTag in general. I could separate static string usage, VTagInner and in-place construction into separate PRs, if needed. But I hardly see that as less work than reviewing one PR.

@siku2
Copy link
Member

siku2 commented Jul 25, 2020

I might be misunderstanding the point of keys on VList then. Obvious what they are for on individual nodes but not quite for lists.

The thing is that fragments (lists) can also be members of a list like so:

html! {
  <key="my-list">
    <div />
    <key="item in my list">
      <span />
    </>
  <>
}

This PR is about reducing memory usage for VTag in general. I could separate static string usage, VTagInner and in-place construction into separate PRs, if needed. But I hardly see that as less work than reviewing one PR.

If you feel like splitting these up would be an unnecessary endeavour that's totally fine by me. Again, I haven't taken a closer look yet so I will take your word for it.

Copy link
Contributor

@teymour-aldridge teymour-aldridge left a comment

Choose a reason for hiding this comment

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

Just a few preliminary thoughts I have. The most important one is about using a Cow.

yew/src/utils.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
yew/src/virtual_dom/vtag.rs Outdated Show resolved Hide resolved
@jstarry
Copy link
Member

jstarry commented Jul 26, 2020

@bakape these optimizations look very promising!

This PR is about reducing memory usage for VTag in general. I could separate static string usage, VTagInner and in-place construction into separate PRs, if needed. But I hardly see that as less work than reviewing one PR.

I also prefer separate PRs. It's not about "less work" as it is about reducing cognitive overhead. Also, some of your changes are simple and can get merged quickly, while others are more complex or controversial and require more thought. I personally can always find time for a 100 line PR, but rarely get time to fully review a 1000 line PR.

It's definitely more work for you to split things up, but the alternative is this huge PR moves much more slowly.

@bakape
Copy link
Contributor Author

bakape commented Jul 26, 2020

@jstarry Okay, I will then do as suggested and split this PR, starting with the static string optimisations.

bakape and others added 3 commits July 26, 2020 10:40
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
@jstarry
Copy link
Member

jstarry commented Aug 18, 2020

Just submitted the last round of feedback, once these comments are addressed we can merge. Thank you!

@bakape
Copy link
Contributor Author

bakape commented Aug 19, 2020

@jstarry Done except for the attribute stderr regression.

jstarry
jstarry previously approved these changes Aug 22, 2020
Copy link
Member

@jstarry jstarry left a comment

Choose a reason for hiding this comment

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

So awesome, thank you so much for this!

@mergify mergify bot dismissed jstarry’s stale review August 22, 2020 10:44

Pull request has been modified.

@jstarry jstarry merged commit 2b584ca into yewstack:master Aug 22, 2020
@jstarry
Copy link
Member

jstarry commented Aug 22, 2020

I wasn't sure if these changes would impact the JS benchmarks but looks like they improved performance by about 5%!

https://www.dropbox.com/s/k6uzlh20gyd9gsv/Screen%20Shot%202020-08-22%20at%207.24.14%20PM.png?dl=0

@siku2 siku2 mentioned this pull request Aug 22, 2020
@jstarry
Copy link
Member

jstarry commented Aug 23, 2020

@bakape I just had another idea. What if we took static strings a step further? Right now we are using static strings for tag names but we have one static string per usage. It would be nice to reuse static strings.

Example

  <div>
    <span />
    <span />
    <span />
    <div>
      <span />
    </div>
  </div>

This example currently uses 2 static "div" and 4 static "span". Yew could export static strings for all common elements and then the macro could substitute those so that we only need one static "div" and one static "span". Thoughts?

@bakape
Copy link
Contributor Author

bakape commented Aug 23, 2020

@jstarry I have something similar to that planned as a VTagInner variant - so it depends on PRing and merging VTagInner first. The gist is you can have the procedural macro detect vtag trees with only literals at compile time and serialize those as HTML strings.

So

  <div>
    <span />
    <span />
    <span />
    <div>
      <span />
    </div>
  </div>

becomes

VTagInner::Static("<div><span></span><span></span><span></span><div><span></span></div></div>")

Diffing those is much much faster, because it's just string comparison, and they are applied to the DOM via a single set_outer_html() call.

I also have a global event listener multiplexer in the works.

@jstarry
Copy link
Member

jstarry commented Aug 23, 2020

@bakape cool! I think we could still have the proc macro help with tags with dynamic attributes as well by reusing string literals.

I also have a global event listener multiplexer in the works.

Woot, this is going to be the killer feature for performance I think.

@bakape
Copy link
Contributor Author

bakape commented Aug 23, 2020

@jstarry Oh, I misunderstood what you meant in

This example currently uses 2 static "div" and 4 static "span". Yew could export static strings for all common elements and then the macro could substitute those so that we only need one static "div" and one static "span". Thoughts?

Would be very suprised, if the compiler actually creates 2 identical static strings. Let me check.

@bakape
Copy link
Contributor Author

bakape commented Aug 23, 2020

@jstarry It does not https://godbolt.org/z/1GbxPh

@jstarry
Copy link
Member

jstarry commented Aug 23, 2020

Ah great, never mind then. I'll be sure to use godbolt more often, such a cool tool :)

@siku2 siku2 added this to the v0.18 milestone Aug 29, 2020
teymour-aldridge added a commit to teymour-aldridge/yew that referenced this pull request Sep 1, 2020
* yew/vtag: remove needless indirection

* yew/vtag: reduce VTag memory footprint

* Revert "yew/vtag: remove needless indirection"

This reverts commit 7c59c61.

* yew/vtag,yew-macro: optimise attribute setting and memory usage

* yew/vtag,yew-macro: reduce string memory footprint and use static strings more

* yew,yew-macro: opportunistically use static memory for VText

* yew/vtag: use String instead of Box<str>

* yew-macro: remove one extra iteration

* yew/vtag: remove API extension for textarea

* yew/vtag: remove extra calls

* yew-macro: preconstruct StringRef for class literals

* yew-macro: construct non-dynamic VTags in-place

* yew-macro: Insert class and href into attrs in-place

* *: run stable checks

* yew/vtag: use key-pair vector for attributes

* yew/macro,yew: use trait associated methods with paths, where possible

* Update yew/src/virtual_dom/vtag.rs

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>

* Update yew/src/virtual_dom/vtag.rs

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>

* Update yew/src/virtual_dom/vtag.rs

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>

* Update yew/src/virtual_dom/vtag.rs

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>

* Update yew/src/virtual_dom/vtag.rs

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>

* Update yew/src/virtual_dom/vtag.rs

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>

* yew/vtag: comment clarification

as suggested by @teymour-aldridge

* yew/vtag: fix added test failing

* yew/vlist: revert removing key

* yew/vtag,yew-macro: stash VTagInner changes for later

* yew/vtag: restore diff_attributes() generating a patch list + add bechmarks

* yew: fix becnhmarks running with std_web

* yew: remove Href

* yew/vtag: fix comment changes

* examples: fix trait impl

* yew: swap Stringref with Cow

* examples: remove redundant clone

* ci: fix stable check

* yew/VText: remove needless constructor

* *: remove needless trait full paths

* yew/benchmarks: add IndexMap attribute diff becnhmark

* yew-macro: fix stderr regressions

* yew: convert Attributes to enum

with variants optimised for both the html! macro and the VTag API

* yew/benchmarks: move feature guard

* Update examples/common/src/markdown.rs

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* Update examples/common/src/markdown.rs

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* Update examples/common/src/markdown.rs

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* Update examples/common/src/markdown.rs

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* Update examples/common/src/markdown.rs

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* examples: remove unused import

* Apply suggestions from code review

Co-authored-by: Justin Starry <justin.m.starry@gmail.com>

* yew-macro: rebuild stderr

* yew-macro: accept Into<Cow> for dynamic tags

* yew-macro: remove unneeded {} wrapping

* yew: revert doc comment

* yew/vtag: clean up attribute type conversion

* yew-macro: remove now supported literal failures

Co-authored-by: Teymour Aldridge <42674621+teymour-aldridge@users.noreply.github.com>
Co-authored-by: Justin Starry <justin.starry@icloud.com>
Co-authored-by: Justin Starry <justin.m.starry@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants