Key optimizations
Native parsers
Swift-cmark and tree-sitter run directly in native code, no JavaScript runtime overhead.
Lazy loading
Language parsers initialize only when needed, keeping startup fast.
Incremental parsing
Reuse unchanged blocks when appending new content.
View pooling
Reusable CodeView and TableView instances reduce allocations.
Benchmark results
These benchmarks were measured on a 2023 MacBook Pro with M3 chip:| Benchmark | Time |
|---|---|
| Plain-text stream append (steady-state) | <0.1 ms |
| Highlight 50 lines | ~2 ms |
| Highlight 500 lines | ~21 ms |
| Parse 500 blocks | ~5 ms |
| Parse + preprocess 300 blocks | ~3 ms |
These benchmarks reflect typical document sizes. The library is optimized for real-time streaming and interactive editing scenarios.
Plain-text streaming
The <0.1ms plain-text append benchmark measures the fast path for streaming text. When appending simple text to the last paragraph (no new markdown syntax), the view skips reparsing and updates only the trailing block. This optimization enables smooth real-time streaming from LLMs or chat interfaces.Syntax highlighting
Highlighting 50 lines takes ~2ms, scaling to ~21ms for 500 lines. This is tree-sitter’s parse time plus attribute application.Parsing
Parsing 500 blocks with swift-cmark takes ~5ms. This includes:- Math expression preprocessing with regex
- Cmark parsing with GFM extensions
- AST conversion to Swift types
Preprocessing
Parsing and preprocessing 300 blocks takes ~3ms. The preprocessing step includes:- Syntax highlighting map generation (thread-safe, runs on background queue)
- Math rendering (requires main thread for UIKit trait access)
- Image source collection for async loading
Sources/MarkdownView/MarkdownTextBuilder/PreprocessedContent.swift:12 for the preprocessing implementation.
Tree-sitter vs JavaScript
MarkdownView uses tree-sitter for syntax highlighting instead of JavaScript-based solutions like highlight.js. This provides significant performance benefits:Architecture comparison
- Tree-sitter (MarkdownView)
- Highlight.js (typical approach)
- Pure native code: No runtime overhead
- Semantic parsing: Produces a full syntax tree
- Incremental: Can re-parse only changed regions
- 19 languages: Pre-compiled grammars included
Performance comparison
| Operation | Tree-sitter | Highlight.js | Speedup |
|---|---|---|---|
| 50 lines | ~2 ms | ~15-25 ms | 7-12x |
| 500 lines | ~21 ms | ~100-150 ms | 5-7x |
| Parser init | Lazy (on first use) | JSContext creation | Instant |
| Memory | Native structs | JS heap + bridge | Lower |
Tree-sitter’s native parsers eliminate the bridge overhead between JavaScript and Swift, making it significantly faster for syntax highlighting.
Lazy parser loading
Language parsers are initialized only when first needed:Sources/MarkdownView/Components/CodeView/CodeHighlighter.swift
Language cache
Sources/MarkdownView/Components/CodeView/CodeHighlighter.swift
CodeHighlighter instances.
Incremental parsing
When appending text to an existing document, MarkdownView can reuse work from the previous parse:Sources/MarkdownParser/MarkdownParser/MarkdownParser.swift
When incremental parsing applies
Incremental parsing is beneficial when:- Appending text to the end of a document (streaming)
- Adding new blocks after existing content
- Making small edits that don’t affect earlier blocks
Example: Streaming scenario
Example: Streaming scenario
Incremental rendering
TheTextBuilder also supports incremental rendering:
Sources/MarkdownView/MarkdownTextBuilder/TextBuilder.swift
View pooling
CodeView and TableView instances are expensive to create, so MarkdownView uses a view pool:Memory characteristics
Parser memory
Parser memory
The cmark parser is created on-demand and freed after each parse, keeping memory usage low. The parsed AST (Swift enums) is compact and uses value semantics.
Syntax highlighting cache
Syntax highlighting cache
Highlight maps are cached using
NSCache with a limit of 256 entries. The cache automatically evicts old entries under memory pressure.Image cache
Image cache
The
ImageLoader uses an in-memory cache for downloaded images to avoid redundant network requests. Images are loaded asynchronously and cached by URL.Attributed string
Attributed string
The final
NSAttributedString can be large for long documents. MarkdownView uses per-block segments to enable partial updates without rebuilding the entire string.Performance tips
Use incremental parsing
When streaming or appending content, use
parseIncremental to avoid reparsing the entire document.Preprocess on background queue
Syntax highlighting is thread-safe. Generate highlight maps on a background queue before rendering on the main thread.
Throttle live updates
Use the built-in throttling mechanism to batch rapid updates:
Limit code block size
Very long code blocks (>1000 lines) can slow down syntax highlighting. Consider truncating or paginating extremely large blocks.
Profiling tools
To measure performance in your app:Use Instruments
Profile with Time Profiler to identify hot paths. Look for time spent in
cmark_parser_finish (parsing) and highlightWithTreeSitter (highlighting).