Skip to content

perf(save_tt): hold table_string as lines across build phases - #666

Closed
EinMaulwurf wants to merge 1 commit into
vincentarelbundock:mainfrom
EinMaulwurf:perf/batch-lines-insert
Closed

perf(save_tt): hold table_string as lines across build phases#666
EinMaulwurf wants to merge 1 commit into
vincentarelbundock:mainfrom
EinMaulwurf:perf/batch-lines-insert

Conversation

@EinMaulwurf

Copy link
Copy Markdown
Contributor

NOTE: This PR and the code changes are entirely "vibe coded" using GLM-5.2.


lines_insert() / lines_drop*() took and returned a single string, so every call strsplit()-ed the entire, growing @table_string on the way in and paste(collapse)-ed it on the way out. In the hot backends these are called dozens of times per build (per hline/vline chunk, per unique cell style, per note, per span, per CSS group) - O(N * L) string traffic that is pure overhead.

Add a line-oriented layer and use it in the hot loops:

  • R/utils.R: lines_insert_vec() / lines_drop_vec() / lines_drop_between_vec() / lines_drop_consecutive_empty_vec() operate on a character vector of lines. The original string-based helpers become thin split -> vec -> collapse wrappers, so every existing caller keeps working unchanged. lines_insert_vec() splits new into individual lines on insert so a chain of *_vec() calls is byte-for-byte identical to the equivalent chain of string round-trips (multi-line inserts must not stay lumped into one element, or a later marker lookup could match the whole block - e.g. the Typst footer template embeds its own notes marker).

  • R/table_string_lines.R: table_string_lines() getter + table_string_lines<- replacement that split / collapse @table_string exactly once, letting a backend hold the table as lines for a whole phase.

  • Typst (R/typst_style.R, R/typst_tt.R): typst_hlines(), typst_vlines(), typst_apply_styles() and typst_notes() now read the table as lines once, splice all their content via the cheap primitives, and write it back once. The same edits are applied in the same order.

  • HTML (R/html_style.R): the rowspan/colspan loop and the per-CSS-group loop do the same - one split / collapse per phase instead of one per item.

Output is byte-for-byte identical: verified across 8 fixtures (simple, alignment, spans, heat-map, hlines/vlines, grouped columns, notes, combined) x 4 formats (typst/html/latex/markdown) = 32/32 identical to main, and the full tinytest suite passes (222 assertions, 0 failures; remaining files are gated on missing optional deps / maintainer-only environments).

`lines_insert()` / `lines_drop*()` took and returned a single string, so
every call `strsplit()`-ed the *entire, growing* `@table_string` on the way
in and `paste(collapse)`-ed it on the way out. In the hot backends these are
called dozens of times per build (per hline/vline chunk, per unique cell
style, per note, per span, per CSS group) - O(N * L) string traffic that is
pure overhead. See tt_save_audit.md §2.4 / §4.2.

Add a line-oriented layer and use it in the hot loops:

* `R/utils.R`: `lines_insert_vec()` / `lines_drop_vec()` /
  `lines_drop_between_vec()` / `lines_drop_consecutive_empty_vec()` operate
  on a character vector of lines. The original string-based helpers become
  thin split -> vec -> collapse wrappers, so every existing caller keeps
  working unchanged. `lines_insert_vec()` splits `new` into individual lines
  on insert so a chain of `*_vec()` calls is byte-for-byte identical to the
  equivalent chain of string round-trips (multi-line inserts must not stay
  lumped into one element, or a later marker lookup could match the whole
  block - e.g. the Typst footer template embeds its own notes marker).

* `R/table_string_lines.R`: `table_string_lines()` getter +
  `table_string_lines<-` replacement that split / collapse `@table_string`
  exactly once, letting a backend hold the table as lines for a whole phase.

* Typst (`R/typst_style.R`, `R/typst_tt.R`): `typst_hlines()`,
  `typst_vlines()`, `typst_apply_styles()` and `typst_notes()` now read the
  table as lines once, splice all their content via the cheap primitives, and
  write it back once. The same edits are applied in the same order.

* HTML (`R/html_style.R`): the rowspan/colspan loop and the per-CSS-group
  loop do the same - one split / collapse per phase instead of one per item.

Output is byte-for-byte identical: verified across 8 fixtures (simple,
alignment, spans, heat-map, hlines/vlines, grouped columns, notes, combined)
x 4 formats (typst/html/latex/markdown) = 32/32 identical to `main`, and
the full tinytest suite passes (222 assertions, 0 failures; remaining files
are gated on missing optional deps / maintainer-only environments).
@vincentarelbundock

Copy link
Copy Markdown
Owner

thanks for this!

the basic idea is good but the PR introduces to many superfluous helpers and code changes. I merged a simpler version here: #668

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants