# 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)