Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
30d3f30
feat(bench): implement markkit-bench harness with stored results
chaliy Oct 26, 2026
7ce3a30
chore(bench): add baseline results for vm-linux-x86_64
chaliy Oct 26, 2026
b04c1d7
fix(bench): avoid rustdoc HTML-tag lint in --moniker help
chaliy Oct 26, 2026
cde4ebc
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 26, 2026
4edb616
docs(bench): add performance summary to README; rerun baseline on lat…
chaliy Oct 26, 2026
c8288a5
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
e10729c
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
6a28b7f
chore(knowledge): restore page-metadata log entry lost in merge
chaliy Oct 27, 2026
4472f55
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
8287fa7
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
4172eeb
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
4a00f9c
fix(html): make deeply nested input linear and stop nested-table row …
chaliy Oct 27, 2026
a1af4c0
Merge previous branch history (already squash-merged in #22)
chaliy Oct 27, 2026
57acc88
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
787e8d5
perf(docx): stream body blocks and compact the XML tree
chaliy Oct 27, 2026
4b96351
perf(spreadsheet): format cells without intermediate copies
chaliy Oct 27, 2026
d155986
perf(html): pre-size the DOM arena and inline node lists
chaliy Oct 27, 2026
b97d208
docs(bench): commit post-fix benchmark run and refresh README tables
chaliy Oct 27, 2026
dc97351
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
5007607
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
dacaeff
docs(bench): refresh benchmark run on top of current main
chaliy Oct 27, 2026
b942882
Merge origin/main (#43 squash) into claude/project-thread-n3yks7
chaliy Oct 27, 2026
8f79558
Merge origin/main into claude/project-thread-n3yks7
chaliy Oct 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/).

### Changed

- DOCX uses far less memory: body blocks are converted while `document.xml` is parsed and freed
right after, and the XML tree is compact (interned names and values, exact-size lists). A 1.5 MB
`document.xml` peaks at 3.9 MiB instead of 18.5 MiB, with half the allocations. Markdown
rendering allocates less for every format.
- HTML peak memory down about 35% and allocations about halved on large pages (DOM arena
pre-sized, inline node lists sized to their content, no copy of text before whitespace
collapsing).
- Spreadsheets make a third fewer allocations per cell (XLSX values are formatted without an
intermediate copy and trimmed in place).
- **Breaking**: limit fields moved from `MarkkitBuilder` to `MarkkitBuilder::limits`
(`config().max_input_size` → `config().limits.max_input_size`); `max_processing_secs` field is
now `limits.timeout: Duration`. Builder setters are unchanged, plus new `timeout`,
Expand Down
49 changes: 24 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,37 +252,36 @@ Other options: `include_hidden_sheets`, `allow_private_networks`, `allow_host`.
## Performance

Measured with `just bench` (in-process, release build, 20 iterations after 3 warmup) on a
4-CPU Linux x86_64 VM, markkit 0.1.1. Allocations and peak heap are per single extraction.
Full report: [`bench-vm-linux-x86_64-1793058767.md`](https://github.com/everruns/markkit/blob/main/crates/markkit-bench/results/bench-vm-linux-x86_64-1793058767.md); history in
4-CPU Linux x86_64 VM. Allocations and peak heap are per single extraction; the last column
is the 0.1.1 baseline. Full report: [`bench-vm-linux-x86_64-1793070312.md`](https://github.com/everruns/markkit/blob/main/crates/markkit-bench/results/bench-vm-linux-x86_64-1793070312.md); history in
[`crates/markkit-bench/results/`](https://github.com/everruns/markkit/blob/main/crates/markkit-bench/results/).

| Case | Input | Mean | Ops/s | Throughput | Allocs/op | Peak heap |
|------|------:|-----:|------:|-----------:|----------:|----------:|
| HTML article (fixture) | 881 B | 49 µs | 20,365 | 17 MiB/s | 213 | 28 KiB |
| HTML blog post with nav/scripts | 15 KiB | 641 µs | 1,561 | 23 MiB/s | 2,427 | 304 KiB |
| HTML long docs page | 300 KiB | 13.0 ms | 77 | 22 MiB/s | 47,578 | 7.5 MiB |
| HTML 50 tables | 257 KiB | 15.0 ms | 67 | 17 MiB/s | 50,349 | 8.6 MiB |
| HTML mostly boilerplate | 45 KiB | 706 µs | 1,417 | 63 MiB/s | 81 | 305 KiB |
| HTML 1000 nested divs | 11 KiB | 3.6 ms | 282 | 3 MiB/s | 442 | 147 KiB |
| DOCX memo | 1.4 KiB | 87 µs | 11,508 | 16 MiB/s | 733 | 93 KiB |
| DOCX 400-section report | 9 KiB (262 KiB out) | 35.7 ms | 28 | 0.2 MiB/s | 265,791 | 18.5 MiB |
| XLSX 50×5 | 2.8 KiB | 291 µs | 3,435 | 9 MiB/s | 1,668 | 129 KiB |
| XLSX 3 sheets, 150k cells | 766 KiB | 172 ms | 6 | 4 MiB/s | 782,800 | 24.9 MiB |
| ODS 2000×8 | 70 KiB | 23.2 ms | 43 | 3 MiB/s | 92,515 | 3.0 MiB |
| Format sniffing (DOCX) | 9 KiB | 0.9 µs | 1,141,227 | n/a | 0 | 0 B |
| Case | Input | Mean | Ops/s | Throughput | Allocs/op | Peak heap | Allocs / peak in 0.1.1 |
|------|------:|-----:|------:|-----------:|----------:|----------:|------:|
| HTML article (fixture) | 881 B | 54 µs | 18,375 | 15 MiB/s | 158 | 22 KiB | 213 / 28 KiB |
| HTML blog post with nav/scripts | 15 KiB | 690 µs | 1,449 | 22 MiB/s | 2,505 | 241 KiB | 2,427 / 304 KiB |
| HTML long docs page | 300 KiB | 15.1 ms | 66 | 19 MiB/s | 49,342 | 4.7 MiB | 47,578 / 7.5 MiB |
| HTML 50 tables | 257 KiB | 18.3 ms | 55 | 14 MiB/s | 33,635 | 5.1 MiB | 50,349 / 8.6 MiB |
| HTML mostly boilerplate | 45 KiB | 704 µs | 1,420 | 63 MiB/s | 66 | 252 KiB | 81 / 305 KiB |
| HTML 1000 nested divs | 11 KiB | 512 µs | 1,953 | 20 MiB/s | 551 | 52 KiB | 442 / 147 KiB |
| DOCX memo | 1.4 KiB | 75 µs | 13,401 | 18 MiB/s | 499 | 59 KiB | 733 / 93 KiB |
| DOCX 400-section report | 9 KiB (262 KiB out) | 22.3 ms | 45 | 0.4 MiB/s | 136,866 | 3.9 MiB | 265,791 / 18.5 MiB |
| XLSX 50×5 | 2.8 KiB | 301 µs | 3,321 | 9 MiB/s | 1,165 | 129 KiB | 1,668 / 129 KiB |
| XLSX 3 sheets, 150k cells | 766 KiB | 172 ms | 6 | 4 MiB/s | 512,786 | 21.5 MiB | 782,800 / 24.9 MiB |
| ODS 2000×8 | 70 KiB | 24.5 ms | 41 | 3 MiB/s | 72,498 | 3.0 MiB | 92,515 / 3.0 MiB |
| Format sniffing (DOCX) | 9 KiB | 0.9 µs | 1,076,774 | n/a | 0 | 0 B | 0 / 0 B |

Takeaways:

- **HTML** extracts at 17–25 MiB/s; peak heap is ~25x the input size.
- **HTML** extracts at 14–22 MiB/s with peak heap 16–20x the input (was ~25x).
- **Deep nesting is linear**: 1000 nested `<div>`s take 0.5 ms (was 3.6 ms), and 64k nested
`<div>`s take 2.4 ms (was 12.6 s).
- **Boilerplate-heavy pages are cheap**: scripts, styles and nav are dropped at ~60 MiB/s.
- **Deep nesting is the slow outlier**: 1000 nested `<div>`s run at 3 MiB/s with few
allocations, so the cost is CPU in tree building, not memory.
- **Office formats allocate most**: a 150k-cell XLSX takes 172 ms and ~5 allocations per cell;
a 9 KiB compressed DOCX peaks at 18.5 MiB of heap (~2000x its compressed size).
- **Format detection** is effectively free (under 1 µs, no allocations).

Timings on a shared VM are noisy (stddev up to ~70% on the smallest cases); allocation counts
are deterministic and the better regression signal. Compare runs from the same machine only.
- **DOCX** streams the document body: a 400-section report peaks at 3.9 MiB (was 18.5 MiB) with
half the allocations.
- **Spreadsheets** cost about 3.4 allocations per cell (was 5.2); one is calamine's and two are
the cell's text and its table node, so peak heap now tracks the size of the output table.
- **Format detection** is effectively free (microseconds, no allocations).

## Security

Expand Down
35 changes: 18 additions & 17 deletions crates/markkit-bench/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,22 +27,23 @@ same moniker only. Commit a fresh run with performance-relevant PRs.

Design and metric definitions: `knowledge/operations/performance-results.md`.

## Latest baseline (vm, linux x86_64, 4 CPUs, 2026-10-26)
## Latest baseline (vm, linux x86_64, 4 CPUs, 2026-10-27)

20 iterations, 3 warmup, release. Full report:
[`bench-vm-linux-x86_64-1793058767.md`](results/bench-vm-linux-x86_64-1793058767.md).

| Case | Input | Mean | Ops/s | Throughput | Allocs/op | Peak heap |
|------|------:|-----:|------:|-----------:|----------:|----------:|
| HTML article (fixture) | 881 B | 49 µs | 20,365 | 17 MiB/s | 213 | 28 KiB |
| HTML blog post with nav/scripts | 15 KiB | 641 µs | 1,561 | 23 MiB/s | 2,427 | 304 KiB |
| HTML long docs page | 300 KiB | 13.0 ms | 77 | 22 MiB/s | 47,578 | 7.5 MiB |
| HTML 50 tables | 257 KiB | 15.0 ms | 67 | 17 MiB/s | 50,349 | 8.6 MiB |
| HTML mostly boilerplate | 45 KiB | 706 µs | 1,417 | 63 MiB/s | 81 | 305 KiB |
| HTML 1000 nested divs | 11 KiB | 3.6 ms | 282 | 3 MiB/s | 442 | 147 KiB |
| DOCX memo | 1.4 KiB | 87 µs | 11,508 | 16 MiB/s | 733 | 93 KiB |
| DOCX 400-section report | 9 KiB (262 KiB out) | 35.7 ms | 28 | 0.2 MiB/s | 265,791 | 18.5 MiB |
| XLSX 50×5 | 2.8 KiB | 291 µs | 3,435 | 9 MiB/s | 1,668 | 129 KiB |
| XLSX 3 sheets, 150k cells | 766 KiB | 172 ms | 6 | 4 MiB/s | 782,800 | 24.9 MiB |
| ODS 2000×8 | 70 KiB | 23.2 ms | 43 | 3 MiB/s | 92,515 | 3.0 MiB |
| Format sniffing (DOCX) | 9 KiB | 0.9 µs | 1,141,227 | n/a | 0 | 0 B |
[`bench-vm-linux-x86_64-1793070312.md`](results/bench-vm-linux-x86_64-1793070312.md).
Last column: the 0.1.1 baseline ([`bench-vm-linux-x86_64-1793058767.md`](results/bench-vm-linux-x86_64-1793058767.md)).

| Case | Input | Mean | Ops/s | Throughput | Allocs/op | Peak heap | Allocs / peak in 0.1.1 |
|------|------:|-----:|------:|-----------:|----------:|----------:|------:|
| HTML article (fixture) | 881 B | 54 µs | 18,375 | 15 MiB/s | 158 | 22 KiB | 213 / 28 KiB |
| HTML blog post with nav/scripts | 15 KiB | 690 µs | 1,449 | 22 MiB/s | 2,505 | 241 KiB | 2,427 / 304 KiB |
| HTML long docs page | 300 KiB | 15.1 ms | 66 | 19 MiB/s | 49,342 | 4.7 MiB | 47,578 / 7.5 MiB |
| HTML 50 tables | 257 KiB | 18.3 ms | 55 | 14 MiB/s | 33,635 | 5.1 MiB | 50,349 / 8.6 MiB |
| HTML mostly boilerplate | 45 KiB | 704 µs | 1,420 | 63 MiB/s | 66 | 252 KiB | 81 / 305 KiB |
| HTML 1000 nested divs | 11 KiB | 512 µs | 1,953 | 20 MiB/s | 551 | 52 KiB | 442 / 147 KiB |
| DOCX memo | 1.4 KiB | 75 µs | 13,401 | 18 MiB/s | 499 | 59 KiB | 733 / 93 KiB |
| DOCX 400-section report | 9 KiB (262 KiB out) | 22.3 ms | 45 | 0.4 MiB/s | 136,866 | 3.9 MiB | 265,791 / 18.5 MiB |
| XLSX 50×5 | 2.8 KiB | 301 µs | 3,321 | 9 MiB/s | 1,165 | 129 KiB | 1,668 / 129 KiB |
| XLSX 3 sheets, 150k cells | 766 KiB | 172 ms | 6 | 4 MiB/s | 512,786 | 21.5 MiB | 782,800 / 24.9 MiB |
| ODS 2000×8 | 70 KiB | 24.5 ms | 41 | 3 MiB/s | 72,498 | 3.0 MiB | 92,515 / 3.0 MiB |
| Format sniffing (DOCX) | 9 KiB | 0.9 µs | 1,076,774 | n/a | 0 | 0 B | 0 / 0 B |
Loading
Loading