Skip to content

Write unescaped runs in one call in escape_html - #1051

Merged
Keats merged 1 commit into
Keats:masterfrom
patrick-immometrica:perf/escape-html-chunks
Oct 26, 2026
Merged

Keats merged 1 commit into
Keats:masterfrom
patrick-immometrica:perf/escape-html-chunks

Conversation

@patrick-immometrica

Copy link
Copy Markdown
Contributor

Without the fast_escape feature, escape_html wrote its output one byte at a time. Since it always gets a &mut dyn Write (it's stored as Tera::escape_fn), that's a dynamic write_all call for every byte of every autoescaped string. This change writes the plain runs between special characters in one call, so a string without anything to escape is now a single write.

Output is the same. I added a small unit test for escape_html that covers the edge cases (empty input, specials at the start and end, adjacent specials, multibyte chars). It passes with and without fast_escape.

Numbers from my machine (release build, default features):

before after
render {{ text }}, 128 KB string, .html 765–862 µs 150–188 µs
escape_html via fn pointer, 1 KB plain text 7.2–9.0 µs 0.7–1.1 µs
escape_html via fn pointer, 1 KB HTML-heavy text 7.6–11.4 µs 1.8–2.0 µs
escape_html via fn pointer, 60 B plain text 0.42–1.3 µs 0.05–0.08 µs

The existing escape bench barely changes, because it calls escape_html directly and the compiler inlines it and devirtualizes the writer. During a render the function is called through the pointer, which is what the rows above measure.

escape_html is always called through a `&mut dyn Write`, so writing one byte
at a time meant a dynamic call per byte of output. Write everything between
two special characters in a single write_all instead.
@Keats
Keats merged commit 1d6d4cd into Keats:master Oct 26, 2026
5 checks passed
@Keats

Keats commented Oct 26, 2026

Copy link
Copy Markdown
Owner

Thanks

@patrick-immometrica
patrick-immometrica deleted the perf/escape-html-chunks branch October 26, 2026 21:44
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