You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Although at some point a thorough look at all performance aspects of Seed will be useful (particularly vdom-diffing). I thought I would highlight some issues I have encountered whilst reviewing performance characteristics of my Seed Styling work.
I was particularly concerned about a potential performance penalty of rendering a large number of relatively expensive view functions. Therefore I began to first investigate this issue in current standard seed.
This creates a div! containing a h3!, p! , button! , EventHandler and significantly a md! element. The md! element renders markdown from a &'static str , and is therefore more involved than a simple wrapped html element.
Clicking the button in the view allows me to time an event loop, from Ev::Click to complete re-render. On my crappy old laptop this results in the following flame graph:
The total render time is ~2.5 seconds, made up of 530ms vdom diffing and 2.02 secs view code. These figures are based on the debug build. I am principally using this mode for performance analysis because it is easier to determine what contributes to the overall timing because functions are labelled clearly in the browser's performance tools.
The highlighted timing of approx 3.78ms is the function seed::virtual_dom::node::Node<Msg>::from_markdown::hb78... which generates a seed Node from a markdown string.
it is clear that this is a major contribution to the view time therefore overall rendering performance. Indeed, 500 x 3.78 ÷ 100 = 1.89 seconds which is almost all of the view code cost. Therefore optimisation of expensive update/view code is important in order to maintain application responsiveness (ideally < 100ms).
The total render time in release mode is 813ms with approximately 158ms of that for vdom diffing. Release mode does help these figures but clearly not sufficiently to be less than 100ms for a re-render.
An almost 1 second delay on interactions is simply not responsive enough.
Obviously if one was in this position one might consider:
a) Is it reasonable to display 500 instances of a markdown render plus other elements?
b) Can the from_markdown function be better optimised?
c) Would it not be better to paginate and display a limited number of views?
All these points are very valid and sensible to consider. That said there may be occasions where one may have to render a significant number of relatively expensive functions.
In these circumstances it is worth considering what could reasonably be done to alleviate the performance cost.
View Caching
When Seed renders a view it enters the main view() and any descendent view functions building up a Node<Msg> tree. In this instance 500 'leafs' of the above view code. This Node tree is then passed to the vdom diffing algorithm. When a user interacts with the page, perhaps clicking the button in the above view function, the entire app's view is traversed again generating a completely new Node<Msg> tree. This occurs even though a button may only affect a single 'leaf' or sub-tree of Nodes.
Caching non-affected sub-trees should therefore significantly improve performance.
I have implemented a naive cache granulated at the view function level. Basically the view function can identify that it (and any child functions) need re-rendering. If it does not
then a cached copy of the Node<Msg> subtree is used and not re-generated.
Using this approach I was able to achieve the following timings in debug mode.
Clearly view caching makes a substantial difference to the view timing, a reduction of around 75%. Vdom diffing is largely unaffected which is to be expected due to there being a similar number of nodes to diff.
In release build overall timing reduces from 1.02s to 193ms a reduction of 76%. vdom diffing is probably preventing the total render time from being less than 100ms. It appears vdom diffing is still around 156ms, and view code only 36ms!.
This 36 ms in release mode is down from 657ms in the release-non-caching version and a massive 2.02s in the non-caching in debug mode.
Clearly large savings can potentially be made using this approach.
Further work
I imagine that if specific Node<Msg> subtrees can be cached on the view side, then these could then either auto-key or somehow notify the vdom diffing algorithm to save on much of this work a well.
If similar gains can be made there then this example could indeed re-render in less than 100ms.
Note
I don't think this is massively concerning generally, it seems in this case a big cost is the markdown md! macro. Standard divs! etc render much much quicker. Most pages seem to render sub 20ms on release build which is of course perfectly fine!
Although at some point a thorough look at all performance aspects of Seed will be useful (particularly vdom-diffing). I thought I would highlight some issues I have encountered whilst reviewing performance characteristics of my Seed Styling work.
I was particularly concerned about a potential performance penalty of rendering a large number of relatively expensive view functions. Therefore I began to first investigate this issue in current standard seed.
Baseline
To get a baseline, on standard seed-quickstart, I rendered the following view function : https://gist.github.com/rebo/6cf6611c59ef5bf79ba11d08784fc99c .
This creates a
div!
containing ah3!
,p!
,button!
,EventHandler
and significantly amd!
element. Themd!
element renders markdown from a&'static str
, and is therefore more involved than a simple wrapped html element.I then render this 500 times by using:
Clicking the button in the view allows me to time an event loop, from
Ev::Click
to complete re-render. On my crappy old laptop this results in the following flame graph:The total render time is ~2.5 seconds, made up of 530ms vdom diffing and 2.02 secs view code. These figures are based on the debug build. I am principally using this mode for performance analysis because it is easier to determine what contributes to the overall timing because functions are labelled clearly in the browser's performance tools.
The highlighted timing of approx 3.78ms is the function
seed::virtual_dom::node::Node<Msg>::from_markdown::hb78...
which generates a seed Node from a markdown string.it is clear that this is a major contribution to the view time therefore overall rendering performance. Indeed,
500 x 3.78 ÷ 100 = 1.89 seconds
which is almost all of the view code cost. Therefore optimisation of expensive update/view code is important in order to maintain application responsiveness (ideally < 100ms).The total render time in release mode is 813ms with approximately 158ms of that for vdom diffing. Release mode does help these figures but clearly not sufficiently to be less than 100ms for a re-render.
An almost 1 second delay on interactions is simply not responsive enough.
Obviously if one was in this position one might consider:
a) Is it reasonable to display 500 instances of a markdown render plus other elements?
b) Can the
from_markdown
function be better optimised?c) Would it not be better to paginate and display a limited number of views?
All these points are very valid and sensible to consider. That said there may be occasions where one may have to render a significant number of relatively expensive functions.
In these circumstances it is worth considering what could reasonably be done to alleviate the performance cost.
View Caching
When Seed renders a view it enters the main
view()
and any descendent view functions building up aNode<Msg>
tree. In this instance 500 'leafs' of the above view code. This Node tree is then passed to the vdom diffing algorithm. When a user interacts with the page, perhaps clicking the button in the above view function, the entire app'sview
is traversed again generating a completely newNode<Msg>
tree. This occurs even though a button may only affect a single 'leaf' or sub-tree of Nodes.Caching non-affected sub-trees should therefore significantly improve performance.
I have implemented a naive cache granulated at the view function level. Basically the view function can identify that it (and any child functions) need re-rendering. If it does not
then a cached copy of the
Node<Msg>
subtree is used and not re-generated.Using this approach I was able to achieve the following timings in debug mode.
Clearly view caching makes a substantial difference to the view timing, a reduction of around 75%. Vdom diffing is largely unaffected which is to be expected due to there being a similar number of nodes to diff.
In release build overall timing reduces from 1.02s to 193ms a reduction of 76%. vdom diffing is probably preventing the total render time from being less than 100ms. It appears vdom diffing is still around 156ms, and view code only 36ms!.
This 36 ms in release mode is down from 657ms in the release-non-caching version and a massive 2.02s in the non-caching in debug mode.
Clearly large savings can potentially be made using this approach.
Further work
I imagine that if specific
Node<Msg>
subtrees can be cached on the view side, then these could then either auto-key or somehow notify the vdom diffing algorithm to save on much of this work a well.If similar gains can be made there then this example could indeed re-render in less than 100ms.
Note
I don't think this is massively concerning generally, it seems in this case a big cost is the markdown
md!
macro. Standard divs! etc render much much quicker. Most pages seem to render sub 20ms on release build which is of course perfectly fine!relevant
https://www.toptal.com/react/optimizing-react-performance
#385
The text was updated successfully, but these errors were encountered: