Documentmodus: visueel opslaan gebruikt kwadratisch geheugen en tijd voor veel regels #1651

Closed
opened 2026-08-21 12:00:24 +00:00 by brenno · 1 comment
Owner

Probleem

Een visuele save bouwt tweemaal een volledige LCS-matrix van (m+1) × (n+1) integers: eerst original↔baseline en daarna baseline↔current. Bij documenten met veel korte regels groeit dit kwadratisch en kan Opslaan de UI langdurig blokkeren of het proces uit geheugen laten lopen. De algemene documentlimiet is 32 MiB en begrenst het aantal regels niet voldoende.

Reproductie

  1. Maak een toegestaan Markdown-document met tienduizenden korte regels.
  2. Doe één visuele wijziging.
  3. Sla op en observeer tijd- en geheugengebruik.

Verwacht

Opslaan schaalt begrensd/lineair of gebruikt een diff-algoritme zonder volledige kwadratische matrix.

Technische aanwijzing

lib/utils/source_patcher.dart:_alignLines en _lcsDiff maken elk List<List> van alle regelparen.

Gevonden bij audit van commit d439638c6b.

## Probleem Een visuele save bouwt tweemaal een volledige LCS-matrix van (m+1) × (n+1) integers: eerst original↔baseline en daarna baseline↔current. Bij documenten met veel korte regels groeit dit kwadratisch en kan Opslaan de UI langdurig blokkeren of het proces uit geheugen laten lopen. De algemene documentlimiet is 32 MiB en begrenst het aantal regels niet voldoende. ## Reproductie 1. Maak een toegestaan Markdown-document met tienduizenden korte regels. 2. Doe één visuele wijziging. 3. Sla op en observeer tijd- en geheugengebruik. ## Verwacht Opslaan schaalt begrensd/lineair of gebruikt een diff-algoritme zonder volledige kwadratische matrix. ## Technische aanwijzing lib/utils/source_patcher.dart:_alignLines en _lcsDiff maken elk List<List<int>> van alle regelparen. Gevonden bij audit van commit d439638c6bd1b519d68d87680cb022b7d5eddc85.
Author
Owner

Triage: accepted

Bevestigd tegen main (e93ef205c). _alignLines (lib/utils/source_patcher.dart) en _lcsDiff bouwen elk een volledige List<List<int>> van (m+1) × (n+1). Bij 30.000 regels aan beide zijden is dat ~900 miljoen int-cellen per matrix. De documentgrens van 32 MiB begrenst het aantal regels niet: een bestand van 32 MiB met alleen a\n telt 16 miljoen regels.

Oplossingsrichting

Drie stappen, in deze volgorde — de eerste doet in de praktijk het meeste werk:

  1. Snoei de gemeenschappelijke kop en staart weg vóór de matrix. Een opslag wijzigt doorgaans een handvol regels; na het snoeien blijft er een venster van enkele regels over en is de matrix triviaal. Dit alleen dekt vermoedelijk elk echt document.
  2. Plafond op het resterende venster (bijvoorbeeld 5.000 × 5.000). Daarboven: geen volledige matrix, maar een regel-hash-index (Map<String, List<int>>) om ankers te vinden en daartussen te patchen. Degraderen in plaats van hangen.
  3. Nooit in het frame. De hele patch kan naar een compute()-isolate, net als de import (zie Import off-isolate). Opslaan mag even duren; de interface mag niet stilvallen.

Regressietest (verplicht)

test/source_patcher_test.dart: document van ~50.000 korte regels met één gewijzigde regel — assertie op de uitkomst (byte-getrouw op alle andere regels) plus een begrensde looptijd. Voor die tweede: injecteer en bevries een klok in plaats van de marge te verbreden (zie Load-flaky timingtest), of tel de matrixcellen achter een testhaak.

Kosten

Eén bestand. Stap 1 en 2 samen een halve dag inclusief tests; stap 3 is los te doen en raakt document_save_actions.dart (die dan await).

Prioriteit

Middelhoog. Het treft alleen grote documenten, maar het neemt de hele interface mee, en de grens ligt lager dan de documentgrens suggereert.

## Triage: accepted **Bevestigd tegen `main` (e93ef205c).** `_alignLines` (`lib/utils/source_patcher.dart`) en `_lcsDiff` bouwen elk een volledige `List<List<int>>` van `(m+1) × (n+1)`. Bij 30.000 regels aan beide zijden is dat ~900 miljoen `int`-cellen per matrix. De documentgrens van 32 MiB begrenst het aantal regels niet: een bestand van 32 MiB met alleen `a\n` telt 16 miljoen regels. ## Oplossingsrichting Drie stappen, in deze volgorde — de eerste doet in de praktijk het meeste werk: 1. **Snoei de gemeenschappelijke kop en staart weg** vóór de matrix. Een opslag wijzigt doorgaans een handvol regels; na het snoeien blijft er een venster van enkele regels over en is de matrix triviaal. Dit alleen dekt vermoedelijk elk echt document. 2. **Plafond op het resterende venster** (bijvoorbeeld 5.000 × 5.000). Daarboven: geen volledige matrix, maar een regel-hash-index (`Map<String, List<int>>`) om ankers te vinden en daartussen te patchen. Degraderen in plaats van hangen. 3. **Nooit in het frame.** De hele patch kan naar een `compute()`-isolate, net als de import (zie `Import off-isolate`). Opslaan mag even duren; de interface mag niet stilvallen. ## Regressietest (verplicht) `test/source_patcher_test.dart`: document van ~50.000 korte regels met één gewijzigde regel — assertie op de uitkomst (byte-getrouw op alle andere regels) plus een begrensde looptijd. Voor die tweede: injecteer en bevries een klok in plaats van de marge te verbreden (zie `Load-flaky timingtest`), of tel de matrixcellen achter een testhaak. ## Kosten Eén bestand. Stap 1 en 2 samen een halve dag inclusief tests; stap 3 is los te doen en raakt `document_save_actions.dart` (die dan `await`). ## Prioriteit Middelhoog. Het treft alleen grote documenten, maar het neemt de hele interface mee, en de grens ligt lager dan de documentgrens suggereert.
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#1651
No description provided.