Slate: Decorations are slow

Created on 20 Apr 2018  Â·  11Comments  Â·  Source: ianstormtaylor/slate

We are investigating huge performance issues since we updated our app to Slate 0.33, and have noticed that using a syntax highlighting plugin, like slate-prism, slows everything a lot.

I have measured our the time to React.renderToString our app on a reasonable page containing code blocks like this one. Here are the times:

  • with highlighting: 16437ms to render the app and document
  • without: 899ms to render the app and document

I also measured the time spent in the calls of the decorateNode of the plugin, which amounts to 188ms. So the issue most likely comes from Slate and the way decorations are applied and rendered.

♥ help ⚑ perf

Most helpful comment

So this is probably not the final / optimal solution to the problem, but may indicate a direction:

I can see that the main slowdown is simply that the react component for each child node (of a decorated code block, etc) currently receives _all_ the decorations in its props (which may be hundreds in this case) and then iterates over all of them. What is needed is for decorations to not simply be passed to all children down the tree without discretion, and instead to be sent only to the children that overlap the decoration ranges.

Here I attempted that just on the slate-react side, by having each Node component order its decoration ranges around its children in advance and only pass down the ones that overlap each child:

https://github.com/ianstormtaylor/slate/compare/master...jasonphillips:speed-up-decorations-rendering

That leads to a significant speed-up, which you should be able to see here (I merely updated the slate-prism demo page with this build):

https://jasonphillips.github.io/slate-prism/

But it's still not as fast as it could be. I'm relying on node.getKeysAsArray() for my orderings and then loop through the children, but there are surely faster ways. That being said, this is already much faster than the current practice of naively passing all decorations down the tree whether or not they fall outside the scope of each child node.

All 11 comments

I'm also making heavy use of decorations in a project, and seeing similar performance problems that I suspect are rooted here (mainly since disabling all my decorations shows a speed-up). In my case, the decorations are injected externally upon certain events (meaning, somewhat like the search-highlighting demo here) rather than updated on each change via a decorateNode prop, which reinforces your suspicion that the slowdown is not local to calling that function.

My very preliminary performance profiling suggested that the slowdown may be related to areDescendantsSorted (defined here) which is invoked during decoration rendering in each loop -- and which you previously suspected might cause performance problems when it was first added:
https://github.com/ianstormtaylor/slate/pull/1221#discussion_r144492043

Even though getKeysAsArray() is memoized, the repeated scans across that large array via indexOf() could be the issue.

So this is probably not the final / optimal solution to the problem, but may indicate a direction:

I can see that the main slowdown is simply that the react component for each child node (of a decorated code block, etc) currently receives _all_ the decorations in its props (which may be hundreds in this case) and then iterates over all of them. What is needed is for decorations to not simply be passed to all children down the tree without discretion, and instead to be sent only to the children that overlap the decoration ranges.

Here I attempted that just on the slate-react side, by having each Node component order its decoration ranges around its children in advance and only pass down the ones that overlap each child:

https://github.com/ianstormtaylor/slate/compare/master...jasonphillips:speed-up-decorations-rendering

That leads to a significant speed-up, which you should be able to see here (I merely updated the slate-prism demo page with this build):

https://jasonphillips.github.io/slate-prism/

But it's still not as fast as it could be. I'm relying on node.getKeysAsArray() for my orderings and then loop through the children, but there are surely faster ways. That being said, this is already much faster than the current practice of naively passing all decorations down the tree whether or not they fall outside the scope of each child node.

If the problem are caused by areDescendantsSorted, can you check whether https://github.com/ianstormtaylor/slate/pull/1771 perhaps helps a bit? The problem of areDescendantsSorts is that sorting in getKeys is very slow.

BTW, would you like to check whether https://github.com/ianstormtaylor/slate/pull/1783 helps on this problem?

I'll see if I can improve upon your design @jasonphillips. As is, the performance feels good already.

I was thinking about converting the list of decorations to a sorted list of trivial decorations (a decoration with start and end on the same text key) and only pass down the decorations needed.

I'll have a look at @zhujinxuan PRs to see if there are more opportunities

Hi, @jacobbloom I made two small fixes in slate-react->text.js for that problem. Can you try that fix in your document and see whether that works? (If perfect, we will not worry about areSorted any one in decs)

@zhujinxuan did you mean to pull me into this or was that an autocomplete mistake?

He meant to mention @jasonphillips

FYI I have an opened PR on our fork, continuing on the work of @jasonphillips https://github.com/GitbookIO/slate/pull/3

@zhujinxuan where can we see your changes?

Sorry, I made the wrong mention.

The change can be seen in #1771 at the slate-react-> text.js where startKey === endKey

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ezakto picture ezakto  Â·  3Comments

bengotow picture bengotow  Â·  3Comments

ianstormtaylor picture ianstormtaylor  Â·  3Comments

gorillatron picture gorillatron  Â·  3Comments

JSH3R0 picture JSH3R0  Â·  3Comments