## Summary
- Move the `runtime->initialized = 1` store from before `site.py` import to the end of `init_interp_main()`, so `Py_IsInitialized()` only returns true after initialization has fully completed
- Access `initialized` and `core_initialized` through new inline accessors using acquire/release atomics, to also protect from data race undefined behavior
- `PySys_AddAuditHook()` now uses the accessor, so with the flag move it correctly skips audit hook invocation during all init phases (matching the documented "after runtime initialization" behavior) ... We could argue that running these earlier would be good even if the intent was never explicitly expressed, but that'd be its own issue.
## Motivation
`Py_IsInitialized()` returned 1 while `Py_InitializeEx()` was still running — specifically, before `site.py` had been imported. See https://github.com/PyO3/pyo3/issues/5900 where a second thread could acquire the GIL and start executing Python with an incomplete `sys.path` because `site.py` hadn't finished.
The flag was also a plain `int` with no atomic operations, making concurrent reads a C-standard data race, though unlikely to manifest.
## Regression test:
The added test properly fails on `main` with `ERROR: Py_IsInitialized() was true during site import`.
---
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Store references to pickle.dumps and pickle.loads in _PyXI_state_t
so they are looked up only once per interpreter lifetime, avoiding
repeated PyImport_ImportModuleAttrString calls on every cross-interpreter
data transfer via pickle fallback.
Benchmarks show 1.7x-3.3x speedup for InterpreterPoolExecutor
when transferring mutable types (list, dict) through XIData.
The gc_stats struct contains ring buffers of gc_generation_stats
entries (11 young + 3×2 old on default builds). Embedding it inline
in _gc_runtime_state, which is itself inline in PyInterpreterState,
pushed fields like _gil.locked and threads.head to offsets beyond
what out-of-process profilers and debuggers can reasonably read in
a single buffer (e.g. offset 9384 for _gil.locked vs an 8 KiB read
buffer).
Heap-allocate generation_stats via PyMem_RawCalloc in _PyGC_Init and
free it in _PyGC_Fini. This shrinks PyInterpreterState by ~1.6 KiB
and keeps the GIL, thread-list, and other frequently-inspected fields
at stable, low offsets.