Ckeditor5: Performance research

Created on 2 Dec 2019  Â·  8Comments  Â·  Source: ckeditor/ckeditor5

Let's start from creating a set of manual tests on which we'll be able to reliably and quickly test typical performance-oriented scenarios.

Some ideas for those scenarios:

  • Loading large HTML data with a lot of attributes.

    • Coming from an editor (so, only elements and structures that the editor could've produced).

    • Coming from external sources (e.g. other website), so with structures, elements and attributes that the editor doesn't understand and many inline styles (so we can test the new style normalization).

  • An editor with a long content (same cases as above) but focused on testing features inside the editor (so the content should be already preloaded).
  • Inserting content (like pasting) into an editor.

    • Also, 3-4 data sets.

    • The editor content should be diverse as well so you can test various scenarios (pasting into a table, image caption, middle of something, etc.)

  • Loading a couple of editors at once. We expect to see a linear time growth, so we could test just one, but let's have ~3-5 as at those points the load times start to be noticeable.

Other considerations:

  • The action to be tested should be triggered by a button (instead of happening automatically).
  • Use rich setups of editor plugins (so we cover diverse types of those).

Subtasks:

  • [x] Test with empty editor and setData button (https://github.com/ckeditor/ckeditor5/pull/6103)
  • [x] Test with content initialization (after pressing a button) (#6124)
  • [x] Generate content (small, medium, large)

    • [x] Base text (content that will serve as a reference for other small, medium, large variations) (https://github.com/ckeditor/ckeditor5/pull/6147)

    • [x] Semantic content

    • [x] Attributes/Inline (known and unknown to editor), combined CSS styles (https://github.com/ckeditor/ckeditor5/pull/6148)

  • [x] Editor with many features enabled (content preloaded) (https://github.com/ckeditor/ckeditor5/pull/6149)
  • [x] Loading multiple editors at once (https://github.com/ckeditor/ckeditor5/pull/6144)
  • [x] Review performance, check for any potential bottlenecks (https://github.com/ckeditor/ckeditor5/issues/5880#issuecomment-581922363)
performance task

Most helpful comment

It's great to see effort on this topic. As an example I'm attaching a HTML file which takes couple of seconds (5-10 on my machine) to load into CKEditor. The file is big-ish, but still something you might expect to work with in the CKEditor.

BMesh Operators.zip

All 8 comments

It's great to see effort on this topic. As an example I'm attaching a HTML file which takes couple of seconds (5-10 on my machine) to load into CKEditor. The file is big-ish, but still something you might expect to work with in the CKEditor.

BMesh Operators.zip

Added subtasks list for better tracking.

One note – I saw the newly added tests. They use fetch() in getFileContents(). AFAIK you can do a simple import 'path' as foo or something similar. IIRC we have a loader for txt files. You can check how it works in PFO tests.

tl;dr; for small editors most of the time goes to editor UI rendering (e.g. color dropdowns, table dropdown). In case of editors with a longer content we'd need to optimize data processing.

You can load profiles in your inspector from this archive: editor-init-profiles.zip.

editorinit - short, semantic content

Performance chart - small semantic content

Bottoms up - small semantic content

Total click handler time 1.14s.

Most of the time is consumed by initializing the toolbar (582ms), with font bg/color dropdown taking the longest time to render:

Which is similar to #6161.

editorinit - medium, semantic content

Click handler execution time 5.94s

As editor contains more content, it's no longer the UI - instead lodash's getRawTag and src/model/position#get parent function starts to be visible.

The first issue comes from our toMap calls, and was also mentioned in #5854.

Taking a step back and looking at the bigger picture, it's _runPendingChanges where the 85% (both calls combined) of total time goes, so this is where we should look for improvements.

editorinit - long, semantic content

Content stats: 6174 elements, 9.228 text nodes.

tl;dr; src/model/position#get parent (17.1 self %) and getRawTag (16.9 self %) hit the town.

Click handling took 19.4 s.

As the content gets larger you can see that, intuitively, model and editable output takes more time.

One thing to note is that src/model/position#get parent function is a relatively small and fast one, but it's called a staggering 858k times.

edit: added bottom-up list _(note, the bottom-up is taken from a different run, where the timing turned out a bit different then above 24.7s in click handler)_

editorinit - short, styled content

Click handling took 1.48s.

In this case time distribution is almost 50%/50% - half going for editor UI view initialization and another half on editable handling (toView, toModel, etc.).

editorinit - full website styled content

Click handling took 6.28s.

Call graph goes much deeper than previous ones, probably because there's more nested content.

Again, lodash's getRawTag and src/model/position#get parent function came first when looking at the self execution time.

View/model generation takes the most of the time.

Conditions

Tests were performed on latest master branch today, in Chrome 79.0.3945.130 running on Windows 10.

There are some quick wins possible. I recall seeing getRawTag (which is a result of calling isPlainObject) as a performance issue longer time ago.

Time given in seconds.

case | master | i/4504-rebased branch | improvement
---------|---------|----------|---------
medium semantic content | 5.94 | 4.62 | 22.22%
full styled website | 6.28 | 5.11 | 18.63%
short styled content | 1.48 | 1.22 | 15.27%

Pushed the changes to i/4504-rebased engine and utils repos.

https://github.com/ckeditor/ckeditor5-utils/tree/i/4504-rebased
https://github.com/ckeditor/ckeditor5-engine/tree/i/4504-rebased

There are couple of issues created from this one:

  • #6195: Improve listenTo() and fire() function execution time
  • #6194: Toolbar grouping should recalculate the styles less frequently
  • #6193: Table's InsertTableView takes a long time to render
  • #6192: Font color/background UI rendering should be improved

Also couple other things could be noted based on research but these probably couldn't be fixed with reasonable effort:

  • SVG parsing (DomParser().parseFromString()) takes a little of time in case of initializing editor with small amount of data.
  • view.(element/node) / model.(element/node) is regexps usage could be optimized (reuse regexps or try to use less performance hungry functions, e.g. String.startsWith / String.endsWith). It starts to take 2-3% of total time in case of medium and longer, semantic content. But is irrelevant for smaller content cases.

It saved ~10ms startup time when switched to innerHTML in an editor with an average toolbar (8 icons). AFAIR this caused some problems in Edge but since Edge will be officially Chromium soon, we may consider this small tweak (works in Chrome, FF and Safari).


~29ms gain in http://localhost:8125/ckeditor5/tests/manual/performance/richeditor.html.


Diff for future reference

diff --git a/src/icon/iconview.js b/src/icon/iconview.js
index c33f95a..43dd9cb 100644
--- a/src/icon/iconview.js
+++ b/src/icon/iconview.js
@@ -95,19 +95,13 @@ export default class IconView extends View {
           */
          _updateXMLContent() {
                   if ( this.content ) {
-                           const parsed = new DOMParser().parseFromString( this.content.trim(), 'image/svg+xml' );
-                           const svg = parsed.querySelector( 'svg' );
-                           const viewBox = svg.getAttribute( 'viewBox' );
+                           this.element.innerHTML = this.content.trim();
+
+                           const viewBox = this.element.firstChild.getAttribute( 'viewBox' );

                            if ( viewBox ) {
                                     this.viewBox = viewBox;
                            }
-
-                           this.element.innerHTML = '';
-
-                           while ( svg.childNodes.length > 0 ) {
-                                    this.element.appendChild( svg.childNodes[ 0 ] );
-                           }
                   }
          }
Was this page helpful?
0 / 5 - 0 ratings

Related issues

pjasiun picture pjasiun  Â·  3Comments

wwalc picture wwalc  Â·  3Comments

pomek picture pomek  Â·  3Comments

Reinmar picture Reinmar  Â·  3Comments

hybridpicker picture hybridpicker  Â·  3Comments