PersonaDB

⚠️ NOTE / WARNING: This document is a hypothetical architectural and performance analysis only. It is NOT currently in the plan to implement these changes YET. Do not refactor or rewrite the codebase based on this document unless explicitly scheduled.


For this specific codebase (PersonaDB) and the constraint “without changing pretty much every line of code”, here is how those four options actually play out against your architecture:


TL;DR Verdict for PersonaDB

  1. Best near-zero-code-change runtime swap: GraalPy (or PyPy) — provided you make a ~40-line change in a single file (persona_db/engines/rng.py) to stop calling NumPy per scalar draw.
  2. Best practical speedup without even switching runtimes (staying on CPython): Surgical fix in rng.py + gen_02_genealogia.py (~60 lines total across 2 files) — gives a massive speedup (5x–20x+ at scale) because NumPy is currently making this codebase slower, not faster.
  3. Worst fit for “without changing every line of code”: NumPy (Vectorization) and Cython. Both would require rewriting almost every engine and generator from top to bottom.

Why Each Option Behaves This Way in PersonaDB

1. The Plot Twist: You Are Already Using NumPy, and It Is Currently Slowing You Down

Look at persona_db/engines/rng.py (SeededRNG):

Why full NumPy vectorization violates your constraint:


2. GraalPy — The Winner for “Zero/Minimal Code Changes”, With One Catch

Your workload in PersonaDB is almost entirely:

This is textbook JIT compiler territory (GraalPy / PyPy), except for one catch:


3. Cython — Why It Doesn’t Fit Your Constraint


4. CPython — The Hidden Gem (Fixing the 2 Real Bottlenecks in <60 Lines)

Even if you stay on standard CPython and don’t switch to GraalPy, you can get an order-of-magnitude speedup by fixing just two localized bottlenecks without touching the rest of the codebase:

  1. Replace NumPy scalar draws in persona_db/engines/rng.py (~40 lines):
    • Use Python’s random.Random(seed_int) (which is implemented in C in CPython and has zero array-allocation overhead for scalar draws) and random.choices(options, weights=weights, k=1)[0] (or itertools.accumulate + bisect).
    • For single-item weighted draws over small lists of strings/dicts, random.Random is 10x–30x faster than np.random.Generator.choice because it doesn’t construct a NumPy array out of options and weights on every call.
  2. Fix the $O(N^2)$ scans in persona_db/generators/gen_02_genealogia.py (~20 lines):
    • In _parents() / _pick_parent() (gen_02_genealogia.py:157-182), for every person, it scans the entire pool of $N$ personas and calls date.fromisoformat(p["data_nascimento"]) inside the inner loop ($O(N^2)$ date parses).
    • In _heranca() and _doacoes() (gen_02_genealogia.py:258, 294), for every deceased/donor persona, it scans all personas again to find matching familia_id ($O(N^2)$).
    • Pre-parsing birth dates once and grouping personas by familia_id in a dict turns those from $O(N^2)$ into $O(N)$ while keeping the exact same output.

Summary Comparison for PersonaDB

Path Code Changes in PersonaDB Expected Real-World Impact on PersonaDB Verdict
GraalPy (as-is, keeping NumPy in rng.py) 0 lines Modest or even slower due to C-extension boundary calls into numpy.random thousands of times per persona Held back by rng.py
GraalPy (after swapping rng.py to pure-Python random.Random) ~45 lines (only in rng.py & socio.py) Very High (5x–25x+) — JIT compiles all pure-Python dict/loop logic across engines & generators Best runtime choice
CPython (swapping rng.py to random.Random + indexing gen_02) ~60 lines (in rng.py & gen_02_genealogia.py) High (5x–20x+ at $N \ge 10\text{k}$) — eliminates per-call NumPy array creation and $O(N^2)$ family scans Best pragmatic fix on standard Python
NumPy (full vectorization) ~90% of codebase 10x–100x, but requires rewriting all engines/generators from per-person dicts to columnar arrays Violates constraint
Cython ~80% of codebase (to get real speed) 1.2x without cdef types; 50x+ only if you rewrite all dicts/strings into C structs Violates constraint