-
Notifications
You must be signed in to change notification settings - Fork 1
Optimization Progress
Objective: process all todo*.md optimization notes, implement viable speedups, benchmark after each batch, and keep only changes that improve or preserve conversion speed.
-
todo.md -
todo_part2.md -
todo_v3.md
WSL Release benchmarks using build/linux-verify/rayomd --bench ... elegant normal.
| Input | Baseline avg | Final avg | Notes |
|---|---|---|---|
tester.md |
0.20 ms | 0.12 ms | 40% faster on final post-clean 1000-iteration run. |
| verifier ASCII | 0.017 ms | 0.015 ms | About 12% faster; displayed as 0.02 ms due report formatting. |
todo.md |
1.93 ms | 0.91 ms | 53% faster on final post-clean 1000-iteration run. |
todo_part2.md |
2.64 ms | 1.28 ms | 52% faster on final post-clean 1000-iteration run. |
todo_v3.md |
2.29 ms | 0.79 ms | 66% faster on final 1000-iteration run. |
| emoji-only status-symbol doc | not measured | standard-font-ascii path | Verifies supported emoji normalization can avoid Unicode path when no other Unicode remains. |
Rejected experiment:
- Direct-mapped glyph lookup cache: removed after
tester.mdworsened from 0.14 ms to 0.17 ms andtodo_part2.mdworsened from 1.31 ms to 1.35 ms.
Verification notes:
- WSL Release build and
tests/verify_cli.pycompleted. - Windows Release build completed from fresh
build/windows-fixtree. - Windows benchmark mode initially failed for nested output paths because Win32 used one-level
CreateDirectoryW; fixed with recursive directory creation. - Windows GUI-subsystem executable benchmarks were run hidden with
Start-Process -Wait.
Windows Release benchmarks using build/windows-fix/rayomd.exe --bench ... elegant normal.
| Input | Windows final avg | Notes |
|---|---|---|
tester.md |
0.35 ms | 1000 iterations. |
| Windows ASCII | 0.06 ms | 5000 iterations. |
todo.md |
2.11 ms | 1000 iterations. |
todo_part2.md |
3.94 ms | 1000 iterations. |
todo_v3.md |
2.39 ms | 1000 iterations. |
- Complete. All todo files were reviewed; implemented speedups are benchmarked and deferred items are documented.
- Reused
HexTextfor faux-bold text instead of encoding/inserting CIDs twice. - Replaced
std::set<uint16_t>CID collection with a bit-backed collector and sorted vector. - Computed the CID cache key once per Unicode PDF build and reused it across font caches.
- Replaced
std::to_string/ostringstreamPDF hot-path assembly with reserved strings andto_charsappend helpers. - Hoisted
TextWidthscale calculation and removedWrapCodeLinestring copies. - Converted UTF-8 conversion to
std::string_viewinputs to avoid substring allocations. - Merged styled spaces into adjacent spans where visually equivalent, reducing
PaintText/HexTextcalls and PDF content size. - Added fast paths for plain
StripInlineMarkdown, rule-line checks, and non-table line rejection. - Added Linux CLI
mmapreads withifstreamfallback. - Added GUI/native PDF output-buffer reuse.
- Treated supported status-symbol emoji as ASCII-path-safe after normalization.
- Changed CID width caching from
unordered_mapto a direct lazy array for faster large Unicode documents. - Added a legacy stress benchmark helper to generate large mixed-style Markdown corpora and run single-file plus 10/100/1000-file batch benchmarks with max-RSS reporting. This helper was later removed after consolidation into
tools/benchmark.py.
- Full TTF glyph-ID remapping for
cmap/hmtx/namesubsetting: not implemented yet; needs correctness work because current subset preserves original glyph IDs. - Parser arena/streaming refactor: not implemented yet; requires changing
Blockownership/lifetime or rendering directly from a scanner. -
simdutf: vendored as an optional CMake experiment, but defaults OFF because stress benchmarks were slower. - StringZilla dependency: not added; external dependency and licensing/build integration need separate decision.
- Knuth-Plass line breaking: skipped for this performance goal because it improves layout quality rather than conversion speed.
Triggered by research notes in research_notes.md.
Accepted/practical result:
- Added a legacy WSL ext4 wrapper to copy the project into native WSL storage, build there, and run the same generated stress corpus. This helper was later removed after consolidation into
tools/benchmark.py. - Native WSL ext4 is the biggest measured win for Linux batch conversion. On the same stress harness,
batch1000improved from10.15 son/mnt/eto2.20 son WSL ext4 in the first controlled comparison. - Final kept default run on WSL ext4 measured:
| Case | Time | RSS |
|---|---|---|
style_mix --bench
|
2.85 ms avg | n/a |
large_mix --bench
|
17.39 ms avg | n/a |
batch10 |
0.039 s wall | 8200 KB |
random100 |
0.371 s wall | 14240 KB |
batch1000 |
1.844 s wall | 47024 KB |
Rejected experiments from this round:
-
simdutfUTF conversion, UTF-16 widening path: slower on WSL ext4 (style_mix2.95 ms,large_mix17.66 ms,batch10002.300 s). Kept optional but defaulted OFF. -
simdutfLinux UTF-32 path: still slower on single-doc benches (style_mix3.13 ms,large_mix19.58 ms). Reverted to the narrower optional UTF-16 path and kept default OFF. - PDF object move/copy reduction: regressed the full stress suite (
style_mix3.74 ms,large_mix20.75 ms,batch10002.540 s). Reverted.
Focused large-Unicode simdutf follow-up:
- Used a legacy large-Unicode WSL ext4 helper to compare
RAYOMD_USE_SIMDUTF=OFFvsONon native WSL ext4. This helper was later removed after consolidation intotools/benchmark.py. - Raw report:
benchmark-output/simdutf-large-unicode/simdutf_20260606/simdutf_large_unicode.md. - Result remained mixed, so default stays OFF:
| Doc | OFF avg ms | ON avg ms | ON delta |
|---|---|---|---|
unicode_1mb |
68.19 | 66.20 | -2.92% |
unicode_2_5mb |
178.60 | 217.98 | +22.05% |
unicode_5mb |
425.31 | 387.09 | -8.99% |
unicode_10mb |
800.01 | 848.21 | +6.02% |
Windows verification:
- Fresh Windows Release build succeeded in
build/windows-final2. - Hidden Windows
--bench tester.mdsmoke test: 100 iterations, 0.34 ms average, 100395 average PDF bytes. -
RAYOMD_USE_SIMDUTF=ONWindows build was not verified because the escalation request hit the session usage limit; the option is OFF by default and LinuxONbuild was verified.
Run date: 2026-06-06.
- Added a legacy commercial benchmark helper for deterministic synthetic corpora, 50+ test campaigns, JSON output, Markdown reports, and Linux/Windows memory reporting. This helper was later removed after consolidation into
tools/benchmark.py. - Added a legacy commercial WSL ext4 wrapper to run the Linux campaign on native WSL ext4 and copy metrics back without generated PDFs. This helper was later removed after consolidation into
tools/benchmark.py. - Raised Markdown input file cap from 64 MiB to 128 MiB in Linux CLI and Windows CLI/GUI paths so 100 MiB inputs can be benchmarked.
- Ran 60 total commercial benchmark cases: 48 Linux WSL ext4, 12 Windows, all passed.
- Covered
--bench,--export,--batch,--stdin-batch,--serve, all three styles, standard margins, and generated files from about 100 KB to 100 MB. - Headline local results: Linux 100 MB export 7.03 s, Windows 100 MB export 13.48 s, Linux 100 MB warm conversion 6.48 s avg, Linux peak warm throughput 21.91 MiB/s, Linux 100-file stdin batch 0.144 s.
- Extra Windows Pandoc compatibility smoke test passed separately: 100 KB ASCII export in 8.11 s, output 151,524 bytes.
- Full tracked summary:
../benchmarks/commercial_benchmark_summary.md. - Raw ignored reports:
benchmark-output/commercial/reports/commercial_benchmark_combined_combined_commercial_20260606.mdand.json.
Generated by the legacy stress helper, later removed after consolidation into tools/benchmark.py, with:
-
single/style_mix.md(36 KB, heavy inline styles) -
single/large_mix.md(213 KB, headings, paragraphs, tables, lists, quotes, code, math, Unicode) batch10/random100/batch1000/
Baseline stress run on accepted optimized code:
| Case | Time | RSS |
|---|---|---|
style_mix --bench
|
2.78 ms avg | n/a |
large_mix --bench
|
16.62 ms avg | n/a |
batch10 |
0.116 s wall | 8412 KB |
random100 |
1.034 s wall | 14516 KB |
batch1000 |
8.216 s wall | 47316 KB |
Rejected larger optimization experiments:
- Precomputed width+hex paragraph spans: improved
batch1000to 7.947 s once, but regressedstyle_mixto 2.97 ms andlarge_mixto 16.73 ms. - Width-only paragraph spans: kept
style_mixflat at 2.78 ms, but regressedlarge_mixto 17.03 ms andbatch1000to 8.934 s. - Direct BMP glyph array: regressed
style_mixto 2.96 ms,large_mixto 17.31 ms, andbatch1000to 9.244 s. - POSIX output writes for Linux CLI: regressed
style_mixto 3.39 ms,large_mixto 18.54 ms, andbatch1000to 8.426 s. - Manual non-Windows UTF-8 decoder: mixed results; one run improved
large_mixto 15.74 ms andbatch1000to 7.979 s, but rerun regressedstyle_mixto 2.86 ms andbatch1000to 8.548 s. Reverted as not stable enough.
Final post-revert stress sanity run:
| Case | Time | RSS |
|---|---|---|
style_mix --bench
|
2.88 ms avg | n/a |
large_mix --bench
|
16.78 ms avg | n/a |
batch10 |
0.124 s wall | 8220 KB |
random100 |
1.067 s wall | 14348 KB |
batch1000 |
8.248 s wall | 46936 KB |
- 1.1 Parser
string_view: already mostly present; added plain inline/table/rule fast paths.Block::textownership refactor deferred. - 1.2
IsBlockStartredundant parsing: already solved byClassifyLine. - 1.3 SIMD line/ascii scanning: already present.
- 1.4 Arena allocator: deferred; current parser returns owning
Blockdata. - 2.1 Font object/map caches: already present; improved by computing CID key once.
- 2.2 Font subsetting: existing
glyf/locasubset retained; full glyph-ID remap forcmap/hmtxdeferred. - 2.3 Width lookup: changed from hash-map lazy cache to direct lazy CID cache for large-document speed.
- 2.4 BMP glyph map: already sparse
unordered_map. - 3.1
ostringstream: removed fromtiny_pdf.cppPDF generation. - 3.2
F()allocations: hot PDF paths useAppendF;F()remains public/reporting helper. - 3.3 Single small-file write: already present in Win32 writer.
- 3.4 No zlib: left unchanged intentionally.
- 3.5
PdfObjects::Buildreserve/to_string: reserve already present; integer appends now useto_chars. - 4.1
WrapText: partially improved with view/code-line changes; full output-buffer redesign deferred. - 4.2 UTF-8 conversion: improved to
string_viewinputs and fewer substring allocations; full per-block lazy UTF-8 rendering deferred. - 4.3 Hex table: already present.
- 4.4 Page reserve: already present.
- 5.1 Font cache in serve: already present; CID key reuse improved.
- 5.2 File reads: Win32 mmap already present; Linux CLI mmap added.
- 5.3 Output buffer reuse: CLI already reused buffers; GUI/native export now reuses a thread-local buffer.
- 6.1 GUI stats: already updated only on text changes.
- 6.2 WaitMessage/vsync: WaitMessage already present; vsync left unchanged.
- A.1 Duplicate
HexTexton faux-bold: implemented. - A.2 Oversized word fallback: improved by avoiding one-character
TextWidthcalls; binary-search splitter not needed for measured win. - A.3
MakeWidthsinteger allocations: implemented withAppendInt. - B.1
usedCidsstd::set: replaced with bit-backed collector and sorted vector. - B.2 Repeated
MakeCidKey: computed once per Unicode PDF build. - B.3 Full
cmap/hmtx/namepruning: deferred pending safe glyph-ID remap. - B.4 Lazy cmap parsing: deferred; startup-only in current benchmark because font is cached.
- C.1 UTF-8 fragment conversion: partially improved with view conversion and no substring allocation.
- C.2 Styled spaces: implemented by merging spaces into adjacent spans when visually equivalent.
- C.3 TextWidth division: implemented with precomputed scale.
- C.4 cp-to-cid cache: tested direct glyph cache and rejected because it was slower.
- C.5
WrapCodeLine(wstring_view): implemented. - D.1
SplitStyledWordsreserve: implemented. - D.2
SplitTableRowtrim allocation: reduced viaTrimView. - D.3
PdfObjects::Buildto_string: implemented withAppendSize/AppendInt. - D.4 Numbered list marker
to_string: implemented with reserved strings andAppendInt. - D.5
NormalizeSymbolscopy/replace work: added no-symbol fast exits and plain inline fast path. - E.1 SIMD mixed-mode renderer: deferred as future architecture work.
- E.3 Faster integer formatting: implemented via C++17
to_chars. - E.4 Parser arena: deferred with 1.4.
- Streaming parser / incremental read+parse: deferred as larger architecture work; current gains came from low-risk parser/rendering changes.
-
simdutf: vendored as optionalRAYOMD_USE_SIMDUTF; measured slower on stress benchmarks and left OFF by default. - Arena allocator: deferred with
todo.md1.4. - SIMD ascii/line scan: already present.
- Knuth-Plass: skipped for speed goal because it is a quality/layout feature.
- StringZilla: not added; external dependency integration deferred.
- Emoji fallback before path decision: implemented for supported status symbols.
-
usedCidsset: implemented with bit-backed collector. -
TextWidthCodepoints()allocation:TextWidthusesForEachCodepoint; unused allocating helper removed. -
WrapTextnormalization copy: noNormalizeSpaceshot-path copy exists in current code; wrapping already scans views. - Precomputed width scale: implemented.
- Release compiler tuning changed MinGW Windows builds from
-Osto-O2for RayoMD 1.1.7. - Five alternating warm
tester.mdruns (1,000 iterations,modern normal) had medians of1.52 msfor-Osand1.41 msfor-O2(about 7% faster). - Earlier three-run checks also favored
-O2:README.mdASCII median2.74 -> 2.63 ms; Unicode smoke median1.83 -> 1.71 ms; initialtester.mdmedian1.73 -> 1.58 ms. - Tradeoff: Windows executable size increased from
2,374,656to2,706,432bytes (about 14%). The faster setting was retained deliberately and should be rechecked when toolchain or packaging changes. - A renderer experiment passing table cells through
string_viewwas rejected: direct alternating table benchmarks regressed from2.30 msto2.61 msmedian.