quick-lint-js icon indicating copy to clipboard operation
quick-lint-js copied to clipboard

27$: Web demo is slow (Firefox macOS)

Open strager opened this issue 3 years ago • 10 comments

Parsing jQuery on my MacBook Pro:

  • Web demo in Firefox: ~95 ms per parse
  • -O0 CLI build: ~70 ms per parse
  • -O2 CLI build: ~14 ms per parse

Surely the web demo shouldn't be 1/6th the speed. Right?

  • [ ] Precisely measure parse time in web demo
  • [ ] Audit web demo build flags
  • [ ] ~~Measure parse time in VS Code (which also uses WebAssembly)~~
  • [ ] Compare with Chromium and Safari

strager avatar Feb 25 '21 09:02 strager

On top of parsing being slow, layout is slow (~53 ms) even for small changes (single character additions within a line).

strager avatar Feb 25 '21 09:02 strager

On this program, linting 12 times took 1115.6 milliseconds in Edge on my 5950X on Linux. 382.2 milliseconds were in wasm. 468.8 milliseconds were in "Layout".

john_hillington_ reported slowness on his machine (~184 milliseconds per lint).

strager avatar May 23 '22 23:05 strager

Each change, the demo currently creates one giant text node, then splits it up. This is expensive.

Ideally, we'd reuse text nodes from the previous run. Short of that, we can generate the little text nodes from the start rather than splitting.

strager avatar Jul 11 '22 03:07 strager

Another approach is querying the location of the marks using Range#getClientRects. This prototype seems to roughly work:

  let range = window.document.createRange();
  range.setStart(editor.childNodes[0], 50);
  range.setEnd(editor.childNodes[0], 53);
  let rects = range.getClientRects();
  console.log(rects);
  for (let r of rects) {
    let s = document.createElement('span');
    s.style.position = 'absolute';
    s.style.top = `${r.top}px`;
    s.style.left = `${r.left}px`;
    s.style.width = `${r.width}px`;
    s.style.height = `${r.height}px`;
    s.style.backgroundColor = 'rgba(255, 0, 255, 0.5)';
    document.body.appendChild(s);
  }

strager avatar Jul 11 '22 03:07 strager

In Edge, getClientRects is relatively slow (~2 ms per call; 15 ms on a jQuery example with a few marks). Synchronizing the shadow code input is the real beast though (40+ ms per update). But even updating the textarea alone is slow (16 ms per update).

Splitting text nodes seems to be fast in Edge, but I didn't profile too carefully.

strager avatar Jul 15 '22 06:07 strager

Perhaps we could try contenteditable.

strager avatar Jul 15 '22 06:07 strager

In Edge, contenteditable is just as slow.

In Edge, with JS disabled, typing one character into a textarea containing jQuery takes about 77 ms according to Edge's profiler (17 ms layout, 14 ms update tree). Typing one character into a contenteditable div with a monospace font takes the same amount of time.

strager avatar Jul 15 '22 21:07 strager

I tried Monaco Editor. I am impressed. Typing a character in jQuery takes 26 ms! However, decorations are deferred and take 40+1+9+1+10=61 ms. Disabling syntax highlighting and other features might speed up the decoration process.

strager avatar Jul 15 '22 22:07 strager

I tried a stripped-down Monaco Editor instance. Typing a character in jQuery took 2+10+2+1 = 15 ms.

Config in the playground:

monaco.editor.create(document.getElementById('container'), {
    value: '',
    lineNumbers: false,
    unusualLineTerminators: 'off',
    lineDecorationsWidth: 0,
    roundedSelection: false,
    renderValidationDecorations: 'off', // ???
    minimap: {
        enabled: false
    },
    stopRenderingLineAfter: -1,
    hover: {
        enabled: false // @@@
    },
    links: false,
    colorDecorators: false,
    contextmenu: false,
    inlineSuggest: {
        enabled: false,
    },
    quickSuggestions: false,
    padding: {
        top: 0,
        bottom: 0,
    },
    parameterHints: {
        enabled: false,
    },
    autoIndent: 'brackets',
    suggestOnTriggerCharacters: false,
    acceptSuggestionOnEnter: 'off',
    acceptSuggestionOnCommitCharacter: false,
    snippetSuggestions: 'none',
    copyWithSyntaxHighlighting: false,
    tabCompletion: false,
    selectionHighlight: false,
    occurrencesHighlight: false,
    codeLens: false,
    lightbulb: {
        enabled: false,
    },
    folding: false,
    matchBrackets: 'never',
    renderWhitespace: 'none',
    renderLineHighlight: 'none',
    showUnused: false,
    showDeprecated: false,
    inlayHints: {
        enabled: false,
    },
    guides: {
        bracketPairs: false,
        bracketPairsHorizontal: false,
        highlightActiveBracketPair: false,
        indentation: false,
        highlightActiveIndentation: false,
    },
    bracketPairColorization: {
        enabled: false,
    },
});

strager avatar Jul 15 '22 22:07 strager

I pushed my position:absolute marks experiment to the refs/archive/web-demo-absolute-marking branch. I think moving forward we should use Monaco Editor instead of textarea.

strager avatar Jul 15 '22 22:07 strager