-
Notifications
You must be signed in to change notification settings - Fork 1
Optimization Research Notes
mkuch edited this page Jul 13, 2026
·
2 revisions
Research date: 2026-06-06
Scope: papers/specs/primary implementation docs that may inspire further PDF conversion speed work in this repo.
Sources:
- John Keiser, Daniel Lemire, Validating UTF-8 In Less Than One Instruction Per Byte, arXiv:2010.03090, Software: Practice and Experience 2021: https://arxiv.org/abs/2010.03090
- Daniel Lemire, Wojciech Mula, Transcoding Billions of Unicode Characters per Second with SIMD Instructions, arXiv:2109.10433: https://arxiv.org/abs/2109.10433
- Robert Clausecker, Daniel Lemire, Transcoding Unicode Characters with AVX-512 Instructions, arXiv:2212.05098: https://arxiv.org/abs/2212.05098
-
simdutfproject docs/repo: https://github.com/simdutf/simdutf and https://simdutf.github.io/simdutf/index.html
Key idea:
- Use SIMD kernels for UTF-8 validation/transcoding instead of scalar/library paths.
-
simdutfalready packages runtime CPU dispatch and has a single-header route.
How it maps here:
- Current Linux path uses
std::wstring_convert; Windows usesMultiByteToWideChar. - A hand-written scalar UTF-8 decoder was tested and reverted because results were mixed. A production SIMD implementation may be more worthwhile than custom scalar code.
Experiment to try:
- Vendor
simdutfsingle-header inthird_party/or add an optional CMake dependency. - Replace non-Windows
Utf8ToWide. - On Windows, compare
simdutfUTF-8->UTF-16LE againstMultiByteToWideChar. - Benchmark with
tools/benchmark.py --suite fulland targeted Unicode documents.
Risk:
- New dependency and CPU dispatch surface.
- Small docs may not benefit enough to justify the dependency.
Expected impact:
- Medium on Unicode-heavy docs.
- Low on ASCII docs.
Measured result:
- Tested on 2026-06-06 with vendored
simdutfv9.0.0. - Slower on the generated stress suite, so
RAYOMD_USE_SIMDUTFis available but defaults OFF. - The current workload appears to spend more time in layout/PDF/font work than in UTF conversion.
- Focused large-Unicode follow-up was mixed:
unicode_1mbimproved 2.92%,unicode_5mbimproved 8.99%, butunicode_2_5mbregressed 22.05% andunicode_10mbregressed 6.02%. This is not stable enough to enable by default.
Sources:
- Geoff Langdale, Daniel Lemire, Parsing Gigabytes of JSON per Second, arXiv:1902.08318, VLDB Journal 2019: https://arxiv.org/abs/1902.08318
- Scanning HTML at Tens of Gigabytes per Second on ARM Processors, arXiv:2503.01662: https://arxiv.org/abs/2503.01662
-
simdjsonperformance notes: https://simdjson.github.io/simdjson/md_doc_performance.html
Key idea:
- First pass builds a structural index using SIMD byte classification.
- Second pass uses indexes instead of repeatedly searching/parsing the raw string.
How it maps here:
- Markdown block parsing already uses SIMD newline/ascii scanning.
- Inline parsing still repeatedly searches for markers like
](, backticks,$, emphasis markers, pipes.
Experiment to try:
- Build a per-block marker bitmap/index for bytes relevant to inline Markdown.
- For tables, pre-scan pipe positions instead of rebuilding cells by character.
- For block parsing, extend
LineInfoto include cheap flags: has pipe, has inline marker, has non-ASCII, has emoji-normalizable byte.
Risk:
- Markdown edge cases can make structural indexes tricky.
- May only matter on large paragraphs/tables.
Expected impact:
- Medium for large/style-heavy docs.
- Low for short docs.
Sources:
- Emery Berger et al., Reconsidering Custom Memory Allocation, tech report: https://www.cs.utexas.edu/ftp/techreports/tr01-45.pdf
- Nicolas van Kempen, Emery D. Berger, Reconsidering "Reconsidering Custom Memory Allocation", arXiv:2605.17119: https://arxiv.org/abs/2605.17119
- Dominik Durner, Viktor Leis, Thomas Neumann, On the Impact of Memory Allocation on High-Performance Query Processing, arXiv:1905.01135: https://arxiv.org/abs/1905.01135
Key idea:
- Region/arena allocation helps when many objects share one lifetime.
- Query-processing work shows allocator choice can matter significantly in allocation-heavy engines.
How it maps here:
-
Block, table rows/cells, styled spans, wrapped lines, and page assembly all have document-lifetime or page-lifetime data. - C++17
std::pmr::monotonic_buffer_resourceis available without external dependencies.
Experiment to try:
- Add a
DocumentArenafor parsing and wrapping. - Convert parser internals to
pmr::vector/pmr::stringfirst, not the public API. - If the public
std::vector<Block>API blocks this, try streaming parse->render instead.
Risk:
- Requires careful ownership/lifetime design.
- Can increase peak memory if arena capacity is too large or retained across requests.
Expected impact:
- Medium on large documents.
- Potentially good on stress
batch1000if allocation churn is measurable.
Sources:
- Microsoft OpenType
locaspec: https://learn.microsoft.com/en-us/typography/opentype/spec/loca - Microsoft/OpenType
cmapspec: https://learn.microsoft.com/en-us/typography/opentype/spec/cmap -
fontTools subsetdocumentation: https://fonttools.readthedocs.io/en/latest/subset/ - PDF Tools AG, Font subsetting - how it works and when to use: https://blog.pdf-tools.com/2015/05/font-subsetting-how-it-works-and-where.html
- Boxes and Glue font subsetting docs: https://boxesandglue.dev/textshape/subsetting/
Key idea:
- Current code subsets
glyf/locabut preserves original glyph IDs. - Deeper subsetting remaps glyph IDs densely and rebuilds
cmap,hmtx,loca, and compound glyph references.
How it maps here:
- PDF size is still dominated by embedded font data for Unicode docs.
- Smaller font streams reduce output bytes and may reduce write/cache pressure.
Experiment to try:
- Implement dense
old_gid -> new_gidremap. - Rewrite compound glyph component references.
- Rebuild
hmtxfor new glyph order. - Rebuild minimal
cmapformat 4 or rely on/CIDToGIDMapif PDF readers permit droppingcmapsafely for this embedding mode. - Cross-check output with multiple readers.
Risk:
- High. Font subsetting is error-prone.
- Bad subset fonts can render fine in one viewer and fail in another.
Expected impact:
- High for PDF size.
- Medium for batch conversion time if file writing dominates.
- Lower for repeated in-memory
--benchtimings.
Sources:
- Constantin Pestka, Marcus Paradies, Matthias Pohl, Asynchronous I/O -- With Great Power Comes Great Responsibility, arXiv:2411.16254: https://arxiv.org/abs/2411.16254
- Matthias Jasny et al., High-Performance DBMSs with io_uring: When and How to use it, arXiv:2512.04859: https://arxiv.org/abs/2512.04859
Key idea:
- Modern async I/O APIs reduce syscall overhead, but papers warn that naive integration does not always improve end-to-end performance.
How it maps here:
- Stress
batch1000has much higher wall time than user CPU time on WSL/mnt/e, indicating filesystem/host overhead. - POSIX
writewas tested and reverted because it regressed this workload.
Experiment to try:
- First benchmark on WSL ext4 (
~/tmp/...) versus/mnt/e/...before touching code. - If Linux-native filesystem is much faster, document that as the recommended benchmark/deploy path.
- Only then consider parallel batch workers or async I/O.
Risk:
- High complexity for little benefit on small PDFs.
- Cross-platform support cost is significant.
Expected impact:
- Potentially high for huge batches on slow mounts.
- Low for single-file conversion.
Measured result:
- Confirmed on 2026-06-06: WSL
/mnt/ewas the main batch bottleneck. - First controlled comparison:
batch1000improved from10.15 son/mnt/eto2.20 son native WSL ext4. - Final native WSL ext4 run reached
1.844 sforbatch1000.
Sources:
- Donald Knuth, Michael Plass, Breaking Paragraphs Into Lines: https://docslib.org/doc/4186638/breaking-paragraphs-into-linesx
- James O. Achugbue, On the line breaking problem in text formatting: https://digitalcommons.mtu.edu/michigantech-p/12606/
Key idea:
- Dynamic-programming line breaking optimizes visual quality across the whole paragraph, rather than greedy line-by-line wrapping.
How it maps here:
- This is primarily layout quality, not speed.
- It may increase CPU unless carefully constrained.
Experiment to try:
- Only consider if PDF visual quality becomes a product priority.
- Keep greedy wrapping for speed/default; optional quality mode could use DP.
Risk:
- Slower conversion.
- Requires tuning and language/hyphenation decisions.
Expected impact:
- Negative or neutral for speed.
- Positive for typography.
- Use WSL ext4 for Linux batch benchmarks/conversion when possible;
/mnt/eis much slower for large batches. - Leave
RAYOMD_USE_SIMDUTFOFF by default; the measured stress suite was slower with it enabled. - Prototype parser/renderer arena allocation with
std::pmronly if profiling shows allocation churn after the filesystem issue is removed. - Treat full TTF glyph remapping as a separate correctness-heavy project with PDF-viewer regression tests.
- Do not pursue io_uring now; the main I/O win came from avoiding the WSL Windows mount, not changing write APIs.