Files
thor/PARALLELIZATION.md
T
2026-07-25 11:44:00 -04:00

116 lines
3.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Parallelization Plan
## Current bottleneck
`compile_query` (which contains `query_new`) is ~100% of the grammar loading cost. With ~7 languages, sequential loading costs ~200-280ms. The rest of the build is ~12ms.
## Thread safety concerns
Current code is single-threaded and assumes it:
- `grammar_cache` map — concurrent writes unsafe
- `context.temp_allocator` — shared, not thread-safe
- `Site.pages` dynamic array — concurrent appends unsafe
- Site arena — bump pointer, not atomic
Any parallelization must address these.
## Dependency graph
```
init_site → build_vfs → scan_content (discover files)
→ grammar loading (must finish before any highlight_code)
→ load_page × N (markdown pipeline + highlight_code)
→ render_site (template rendering × N + minify × N)
→ RSS + sitemap (needs all pages loaded)
→ asset copying (independent)
```
Key barriers:
- Grammar loading must finish before any highlighting
- Pages must be loaded before rendering
- All pages must be rendered before RSS/sitemap
- Minify + write are independent per page after render
## Three approaches
### Option 1: Per-subsystem parallelism
```
Phase 1: Parallel grammar loading (7 threads)
Phase 2: Parallel content loading (N pages on M threads)
Phase 3: Parallel template rendering (N pages on M threads)
Phase 4: Parallel minification (N pages on M threads)
Phase 5: Sequential: RSS, sitemap, assets
```
Pros:
- Simple barriers — each phase completes before the next starts
- Easy to reason about
Cons:
- Thread pool setup/teardown per phase (or reuse a pool with barriers)
- Memory pressure: all pages loaded before any rendered
- Load imbalance within phases (complex pages vs simple)
### Option 2: Per-page parallelism
```
Pre-load all grammars (sequential or parallel)
Then for each page (in parallel):
load_page → md.process → render → minify → write
Then sequential: RSS, sitemap, assets
```
Pros:
- Natural work unit — each page flows through the full pipeline independently
- No barriers between phases
Cons:
- Complex pages clog workers while simple pages finish fast
- Grammar loading must happen first (barrier)
- Needs thread-safe shared state (grammar cache, template cache, allocator)
### Option 3: Generic worker queue
```
Single thread pool with work stealing.
Jobs: grammar_load(lang), load_page(file), render_page(page), minify(html), write_output(path)
Dependencies tracked via futures or callbacks.
```
Pros:
- Most flexible — handles all work types
- Best load balancing (work stealing across types)
- No wasted thread setup between phases
Cons:
- Most complex to implement
- Need dependency tracking (can't render before load completes)
- Careful shared-state management needed
- Memory management with arena allocator (thread safety)
## Recommended starting point
**Pre-load grammars in parallel, keep everything else sequential.**
```
Phase 0: Pre-scan content for unique languages (fast, sequential)
Phase 1: Parallel grammar loading (one thread per language, ~30ms)
→ each thread writes to a pre-assigned slot (no map contention)
→ each thread uses its own temp allocator
→ main thread merges results into grammar_cache after join
Phase 2: Sequential build as today (~12ms)
```
Expected: ~42ms total instead of ~210ms. Minimal architecture change — no thread-safe allocators needed for the rest of the pipeline.
This can later evolve toward option 3 (generic queue) if page count grows or per-page processing becomes a bottleneck.
## Implementation notes
- Odin's `core:thread` or `core:sync` can manage the thread pool
- Each grammar-loading thread needs its own `context.temp_allocator` for path strings
- The `grammar_cache` map write happens on the main thread after all threads join
- `dlopen` is thread-safe (POSIX guarantee)
- `query_new` is likely thread-safe (independent computation per language, no shared state in tree-sitter)