Optimize `${builder_path_hpp}` and `${builder_path_cpp}` serialization time (Arrow → column files on disk). The current serialization time is ${current_ingest_time_ms} (${config_str}).
Hardware: ${hardware_context}.
`file_loader_utils.hpp`/`.cpp` may contain the shared SSD serialization helpers used by `${builder_path_cpp}` (`ParquetFileScanner`, `FdStream`, `StringWriter`, Arrow extraction helpers, and column registration helpers). Prefer improving those helpers when the bottleneck is generic rather than duplicating helper code in `${builder_path_cpp}`.

Before profiling, read `${storage_plan_filename}` and `${base_impl_todo_filename}` to understand the intended storage layout and optimization goals.

For each optimization attempt (up to 2 major attempts and 1 refinement), follow these steps:

**Profile** - Add lightweight per-phase timing instrumentation (wall-clock timestamps around each major phase) to `${builder_path_hpp}` and `${builder_path_cpp}`. Compile once, call the run tool with mode "${run_tool_mode}", and report the per-phase breakdown. Then remove all timing instrumentation from the source files in a single edit (rewrite the instrumented section, do not remove markers one at a time) before proceeding.

**Plan** - Based on the profiling data, identify the top 3 bottlenecks. Consult the hints below for common patterns. Write a short plan with the optimizations you will implement.

**Implement** - Implement the planned optimizations in a batch. Before compiling, re-read every changed section and verify: (a) all variables declared before use, (b) no use-after-move/use-after-clear, (c) no duplicate variable names across scopes. Then compile once and validate correctness and the performance improvement by calling the run tool.

Constraints:
- Limit inspection to `${builder_path_hpp}`, `${builder_path_cpp}`, directly included headers, `file_loader_utils.hpp`/`.cpp` when present, `${storage_plan_filename}`, and `${base_impl_todo_filename}`. Do not inspect validator/compile/cache/tool internals unless an error explicitly indicates an interface mismatch. No broad grep/find/ls.
- No compile or run after individual edits. Only validate after a complete batch implementing one coherent optimization strategy.
- If the bottleneck is Parquet I/O bound or disk write bandwidth bound, stop and report.
${storage_constraint}

Hints - common optimization patterns:
- **Parallelism**: Independent table serializations can run concurrently in `std::thread`; the largest table dominates, so overlapping all smaller ones is free. Calibrate thread counts against the hardware above to avoid oversubscription.
- **Write batching**: Avoid per-row or per-element write syscalls. Accumulate column data in memory buffers and flush in large chunks to amortize syscall overhead and maximize write bandwidth.
- **`as_string()` heap cost**: allocates one `std::string` per row during Arrow extraction. For single-character or low-cardinality string columns, extract directly via `GetView()` or read as `uint8_t` dict codes to avoid heap allocation in the read phase.
- **Sort strategy**: For a sort key with low cardinality (thousands of distinct values across millions of rows), use counting sort / bucket sort instead of `std::sort`. Sort the permutation array, then apply it in a single gather pass over all columns simultaneously.
${interface_compat_hint}
