Skip to content

perf(save_tt): vectorize style resolution and typst CSS builder - #663

Merged
vincentarelbundock merged 5 commits into
vincentarelbundock:mainfrom
EinMaulwurf:perf/save-tt-style-resolution
Jul 7, 2026
Merged

perf(save_tt): vectorize style resolution and typst CSS builder#663
vincentarelbundock merged 5 commits into
vincentarelbundock:mainfrom
EinMaulwurf:perf/save-tt-style-resolution

Conversation

@EinMaulwurf

Copy link
Copy Markdown
Contributor

NOTE: This PR and the code changes are entirely "vibe coded" using GLM-5.2. So if that is a dealbreaker, please reject, I do understand. But this is a real-world issue I'm facing when generating lots of large typst-tables with lots of formatting. So I hope I can help. If wanted, I can provide further explanation and benchmark/test scripts.


Two phases of build_tt() dominate save_tt() time on tables with many style_tt() calls (heat-map backgrounds, per-column alignment, etc.):

  1. Resolving @Style into the rectangular @style_other/@style_lines grids. apply_style_to_rect() re-scanned the full cell grid for every style entry (O(N * cells * props)) and append_lines_to_rect() used rbind() in a loop (O(N^2)).
  2. The typst style_eval() backend, where process_typst_other_styles() built CSS one cell at a time via regex typst_insert_field() calls, and typst_apply_styles() grew style_dict_entries with c().

Replace with batched/vectorized equivalents:

  • expand_style.R: new resolve_styles_batch() builds a single (i,j)->linear-index map and walks each property once. Old per-row functions kept for backwards compatibility.
  • build_tt.R: calls resolve_styles_batch() instead of the per-row loop.
  • typst_style.R: new .typst_build_css_vectorized() emits all key:value fragments in a single pass with matching output format; typst_apply_styles() uses a pre-allocated list + unlist().

Output is byte-identical across 6 fixtures x 4 formats (typst, html, latex, markdown) and the full tinytest suite passes.

Benchmarks (median of 5 reps, 60x12 table with 170 style_tt() calls):
typst: 1.091s -> 0.703s (1.55x)
html: 1.442s -> 1.151s (1.25x)
Style resolution phase: 0.451s -> 0.137s (3.3x)

vincentarelbundock and others added 5 commits June 26, 2026 11:51
Two phases of build_tt() dominate save_tt() time on tables with many
style_tt() calls (heat-map backgrounds, per-column alignment, etc.):

  1. Resolving @Style into the rectangular @style_other/@style_lines
     grids. apply_style_to_rect() re-scanned the full cell grid for
     every style entry (O(N * cells * props)) and append_lines_to_rect()
     used rbind() in a loop (O(N^2)).
  2. The typst style_eval() backend, where process_typst_other_styles()
     built CSS one cell at a time via regex typst_insert_field() calls,
     and typst_apply_styles() grew style_dict_entries with c().

Replace with batched/vectorized equivalents:

  - expand_style.R: new resolve_styles_batch() builds a single
    (i,j)->linear-index map and walks each property once. Old per-row
    functions kept for backwards compatibility.
  - build_tt.R: calls resolve_styles_batch() instead of the per-row loop.
  - typst_style.R: new .typst_build_css_vectorized() emits all
    key:value fragments in a single pass with matching output format;
    typst_apply_styles() uses a pre-allocated list + unlist().

Output is byte-identical across 6 fixtures x 4 formats (typst, html,
latex, markdown) and the full tinytest suite passes.

Benchmarks (median of 5 reps, 60x12 table with 170 style_tt() calls):
  typst: 1.091s -> 0.703s (1.55x)
  html:  1.442s -> 1.151s (1.25x)
Style resolution phase: 0.451s -> 0.137s (3.3x)
@vincentarelbundock
vincentarelbundock merged commit 6509bd1 into vincentarelbundock:main Jul 7, 2026
@vincentarelbundock

Copy link
Copy Markdown
Owner

Thanks a lot for taking the time to put this together! This seems like a clear improvement. I've added a few other things and a thank you in the NEWS.

Merged.

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