[Prestaties][P0] Documentmodus: elke toetsaanslag zet het hele document om naar Markdown, superlineair (256 ms bij 300 p.) #2621

Closed
opened 2026-10-28 16:31:33 +00:00 by brenno · 1 comment
Owner

Symptoom

In de visuele documentmodus wordt typen bij grotere documenten (rapporten van tientallen pagina's, veel tabellen en genummerde lijsten) steeds stroever; de vertraging groeit sneller dan het document.

Oorzaak

Bij élke wijziging in de Quill-editor zet _flushPendingQuillChange (lib/widgets/markdown_editor/markdown_editor.dart:604-622, ingepland via scheduleMicrotask op regel 601) het hele document om naar Markdown: MarkdownQuillCodec.markdownFromDocument (regel 614). Een microtask draait vóór het volgende frame, dus deze kosten zitten integraal in de toetsaanslag. Daarna volgen nog withSource → MarkdownSourceDocument.reparse en een paar volledige stringvergelijkingen (die zijn goedkoop, ~2 ms bij 150 pagina's).

De omzetting zelf is superlineair:

  • DeltaToMarkdown.convert bouwt eerst een compleet nieuw Document.fromDelta(...) (third_party/markdown_quill/lib/src/delta_to_markdown.dart:83), terwijl de aanroeper al een Document heeft.
  • Voor elk genummerd lijstitem maakt _prefixNumber een platte lijst van het hele document (_toDocumentList(root), regel 463) en zoekt de node met indexOf — O(lijstitems × documentlengte).

Meting

Tijdelijke meetproef (debug-JIT, Apple Silicon; document met koppen, alinea's met opmaak/links, opsommingen en elke twee pagina's een tabel van 8×4):

Pagina's Tekens markdownFromDocument per toetsaanslag met genummerde lijsten
50 78 k 13 ms 21 ms
75 117 k 30 ms –
150 235 k 75 ms 86 ms
300 470 k 256 ms 301 ms

Twee keer zoveel pagina's kost 2,5–3,4× zoveel tijd. Openen in de visuele stand (documentFromMarkdown) kost 160 ms bij 150 en 437 ms bij 300 pagina's.

Voorgestelde oplossing

  1. De terugschrijf naar Markdown ontkoppelen van de toetsaanslag: _visualEdited/dirty meteen zetten (zoals nu), maar de omzetting pas na een korte rustpauze (250–400 ms) doen, plus een verplichte flush op elk punt dat de bron nodig heeft: opslaan, autosave-tik/herstelmomentopname, wisselen naar Bron, export, presenteren, zoeken/vervangen openen, tabblad wisselen/sluiten, afsluiten. Er is al een flush: true-pad bij de moduswissel (_closeVisualEditor); maak er één flushPendingVisualEdit() van die de documentlaag aanroept vóór hij source leest.
  2. _prefixNumber lineair maken: bereken de nummering één keer per convert (één gang door het document, Map<Node, int>), in plaats van per item het hele document af te lopen. Het pakket staat in third_party/, dus dat kan hier.
  3. Geen tweede Document bouwen: een convertDocument(Document)-ingang die direct op de bestaande boom loopt.
  4. Vervolg (apart te wegen): flutter_quill legt het hele document in één RenderEditor op, zonder virtualisatie. Voor documenten boven een drempel (bv. 150 pagina's) is per-sectie visueel bewerken of standaard Bron-modus het overwegen waard.

Wat overeind moet blijven

Byte-getrouwe opslag (#1613/#1648), ongedaan maken per bewerking (coalescing op 'doc'), en dat een cursorbeweging geen bewerking is (#2141-lijn).

Toets

  • Schaaltoets naast test/large_deck_performance_test.dart: markdownFromDocument op 300 pagina's kost < 2,5× die op 150 pagina's (zelfde opzet als de bestaande parse-verhoudingstest).
  • Widgettest: tien toetsaanslagen snel achter elkaar → één omzetting; opslaan direct daarna bevat alle tien.

Onderdeel van de prestatie- en stabiliteitsinspectie van 28-10-2026 (EPIC #2637).

## Symptoom In de visuele documentmodus wordt typen bij grotere documenten (rapporten van tientallen pagina's, veel tabellen en genummerde lijsten) steeds stroever; de vertraging groeit sneller dan het document. ## Oorzaak Bij élke wijziging in de Quill-editor zet `_flushPendingQuillChange` (`lib/widgets/markdown_editor/markdown_editor.dart:604-622`, ingepland via `scheduleMicrotask` op regel 601) het **hele** document om naar Markdown: `MarkdownQuillCodec.markdownFromDocument` (regel 614). Een microtask draait vóór het volgende frame, dus deze kosten zitten integraal in de toetsaanslag. Daarna volgen nog `withSource` → `MarkdownSourceDocument.reparse` en een paar volledige stringvergelijkingen (die zijn goedkoop, ~2 ms bij 150 pagina's). De omzetting zelf is superlineair: - `DeltaToMarkdown.convert` bouwt eerst een compleet nieuw `Document.fromDelta(...)` (`third_party/markdown_quill/lib/src/delta_to_markdown.dart:83`), terwijl de aanroeper al een `Document` heeft. - Voor **elk genummerd lijstitem** maakt `_prefixNumber` een platte lijst van het hele document (`_toDocumentList(root)`, regel 463) en zoekt de node met `indexOf` — O(lijstitems × documentlengte). ## Meting Tijdelijke meetproef (debug-JIT, Apple Silicon; document met koppen, alinea's met opmaak/links, opsommingen en elke twee pagina's een tabel van 8×4): | Pagina's | Tekens | `markdownFromDocument` per toetsaanslag | met genummerde lijsten | |---|---|---|---| | 50 | 78 k | 13 ms | 21 ms | | 75 | 117 k | 30 ms | – | | 150 | 235 k | 75 ms | 86 ms | | 300 | 470 k | **256 ms** | **301 ms** | Twee keer zoveel pagina's kost 2,5–3,4× zoveel tijd. Openen in de visuele stand (`documentFromMarkdown`) kost 160 ms bij 150 en 437 ms bij 300 pagina's. ## Voorgestelde oplossing 1. **De terugschrijf naar Markdown ontkoppelen van de toetsaanslag**: `_visualEdited`/dirty meteen zetten (zoals nu), maar de omzetting pas na een korte rustpauze (250–400 ms) doen, plus een verplichte `flush` op elk punt dat de bron nodig heeft: opslaan, autosave-tik/herstelmomentopname, wisselen naar Bron, export, presenteren, zoeken/vervangen openen, tabblad wisselen/sluiten, afsluiten. Er is al een `flush: true`-pad bij de moduswissel (`_closeVisualEditor`); maak er één `flushPendingVisualEdit()` van die de documentlaag aanroept vóór hij `source` leest. 2. **`_prefixNumber` lineair maken**: bereken de nummering één keer per `convert` (één gang door het document, `Map<Node, int>`), in plaats van per item het hele document af te lopen. Het pakket staat in `third_party/`, dus dat kan hier. 3. **Geen tweede `Document` bouwen**: een `convertDocument(Document)`-ingang die direct op de bestaande boom loopt. 4. **Vervolg (apart te wegen)**: flutter_quill legt het hele document in één `RenderEditor` op, zonder virtualisatie. Voor documenten boven een drempel (bv. 150 pagina's) is per-sectie visueel bewerken of standaard Bron-modus het overwegen waard. ## Wat overeind moet blijven Byte-getrouwe opslag (#1613/#1648), ongedaan maken per bewerking (coalescing op `'doc'`), en dat een cursorbeweging geen bewerking is (#2141-lijn). ## Toets - Schaaltoets naast `test/large_deck_performance_test.dart`: `markdownFromDocument` op 300 pagina's kost < 2,5× die op 150 pagina's (zelfde opzet als de bestaande parse-verhoudingstest). - Widgettest: tien toetsaanslagen snel achter elkaar → één omzetting; opslaan direct daarna bevat alle tien. Onderdeel van de prestatie- en stabiliteitsinspectie van 28-10-2026 (EPIC #2637).
Author
Owner

Opgepakt. Tak: fix/prestatie-2635-2621-2622-2625. Verwachte reikwijdte: lib/widgets/markdown_editor/markdown_editor.dart, third_party/markdown_quill/lib/src/delta_to_markdown.dart en de flush-aanroepen vóór bronlezing. Virtualisatie van de Quill-editor blijft apart.

Opgepakt. Tak: fix/prestatie-2635-2621-2622-2625. Verwachte reikwijdte: lib/widgets/markdown_editor/markdown_editor.dart, third_party/markdown_quill/lib/src/delta_to_markdown.dart en de flush-aanroepen vóór bronlezing. Virtualisatie van de Quill-editor blijft apart.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
LibreKAT/Ocideck#2621
No description provided.