Artifact Caching, Explained
Where pyvorin-native 1.0.9 stores compiled code, how the cache key is built, what eviction costs, and the measured gap between a cold and a warm compile.
Published Jul 28, 2026
Every compiled function in pyvorin-native 1.0.9 has two lives: the first run, which pays the full compile, and every subsequent run, which loads a prebuilt artefact from a local disk cache in effectively zero time. This page explains the machinery behind the second life — where the cache lives, what a cache entry contains, how the key is derived, when entries are evicted and how their integrity is checked — with the internals read from the installed build and the economics measured on it.
Two caches, two jobs
The product keeps two caches in two different places, and the first confusion to clear away is that the CLI command named cache manages only one of them.
The compile artefact cache is the one this page is about. It lives inside the install tree at <site-packages>/.pyvorin_cache/disk_compile — deliberately not in any user's home directory, so its write access rides on your deployment permissions for the environment itself. On our test install it currently holds 207 files: SHA256-named shared objects plus a single cache_index.json that on our install indexes 214 entries. Each shared object is the compiled form of one function, loadable directly into the process.
The user cache lives at ~/.pyvorin_cache and holds columnar kernel state — on our install, columnar_kernels contains generated .c sources and their compiled .so pairs, 158 files and 1.20 MB in total as reported by the CLI. This directory is created at import time, before any licence activity, which matters if your deployment audits write behaviour. It is a working store for the runtime, not a cache of your code. The product CLI manages this second one:
python -m pyvorin cache status
Cache directory: /root/.pyvorin_cache
Total files: 158
Total size: 1.20 MB
Know what the commands do before using them: cache clear empties the user cache and leaves the compile artefact cache untouched, as previously verified on this build. If your intent is "force everything to recompile", the CLI will not do it; you remove the disk_compile directory itself.
What a cache entry contains
The index is a JSON object keyed by SHA256 hash, one record per compiled function. Reading the fields directly from our install's cache_index.json, each record carries:
| Field | Meaning |
|---|---|
so_path | path to the SHA256-named shared object holding the machine code |
checksum | per-entry checksum, validated when the entry is loaded |
compile_time_ms | how long the original compile took |
hit_count / last_accessed | recency and frequency counters behind the LRU policy |
| optimisation pipeline record | the transform pipeline the compile ran, with per-pass timings |
unsupported_features, partial_compilation, vectorized | what the compiled code is and is not |
arg_type_hints, param_list_families, ret_type_name | the signature specialisation the artefact was built for |
Two properties follow from this shape. An entry is self-describing: the artefact, its validation data and its provenance travel together, which is what makes per-entry integrity checking possible. And an entry is keyed by hash rather than by name, so two functions with identical source and options share one artefact, while any divergence — however small — produces a fresh key and a fresh file.
How the key is derived
The key is a SHA256 hash over the function source, the function name, the compiler options, and the modification times of the runtime and compiler toolchain. Four consequences drive everything else:
- Source is the primary key. Editing a function changes its key automatically. You never clear the cache to pick up a code change; the old entry simply stops being consulted and is eventually evicted.
- Upgrades re-key everything. Because toolchain modification times feed the hash, upgrading the
pyvorin-nativepackage invalidates every artefact at once. Nothing from the previous build is silently reused, and nothing breaks — the first compile under the new version simply pays full cost again. - Options are part of identity. The same function compiled with different flags —
--fast-mathagainst--no-fast-math, for instance — is a different artefact under a different key, never the wrong one reused. - The cache is a plain directory. No database, no daemon. Backing it up or restoring it is a directory copy, which is exactly what makes CI caching practical.
What a hit is worth
Measured on our test host, a 100,000-iteration numeric loop, cold cache: compile_time: 257.517 ms in the JSON report, with a steady-state run time of about 0.012 ms per call. The identical file run again, warm: compile_time: 0.000 ms and a steady-state median of 0.010 ms. The compile cost did not shrink. It disappeared, replaced by a checksum-verified load from the index.
The amortisation arithmetic, derived from those captured figures, puts the break-even near 21,000 calls — one compile of 257.5 ms divided by a steady-state cost of roughly 0.012 ms. A batch job crosses that in its first second; a busy request handler crosses it inside an hour of uptime. The cases that never cross it are the micro-workloads the benchmark suite flags at the bottom of the table — 0.05x at worst — where the loop is so small that overheads dominate and compilation leaves you slower than the interpreter. The cache does not change that judgement; it removes the repeat cost for everything above it.
Eviction and integrity
The cache is an LRU capped at 500 entries, enforced with the recency data in the index: when the cache fills, the least recently used artefacts are evicted and their functions recompile on next call. On a healthy single project this ceiling is invisible. It becomes visible on a monorepo where thousands of functions across dozens of branches share one environment — cold entries evict hot ones, and the cache thrashes. The fix is operational, not product-level: partition the cache per project or per pipeline so eviction stays local. The 500-entry cap is a property of the shipped build, so size your partitions against it rather than waiting for the misses.
Integrity is per-entry and load-time. The checksum in each index record is validated when the entry is consulted; a corrupted or hand-edited artefact fails validation and is recompiled rather than executed. This is the property that makes restoring a cache from CI storage safe: a bad restore costs time, never correctness. Corruption is detected, not honoured.
Cache behaviour in CI
The CI implications follow directly from the key derivation, and the full pipeline recipes — GitHub Actions and GitLab CI, cache keys that hit, and how to prove the cache is working — are on the caching compiled artifacts in CI page. The short form: derive your pipeline cache key from the pyvorin-native version, the compile flags you pass, and a hash of the sources being compiled; restore disk_compile into the install tree before the compile step; save it afterwards. A compile that hit reports compile_time: 0.000 ms in the JSON output, which is an easy assertion for the pipeline to check on every build — a cache that silently stopped hitting is just a slow build with extra steps.
Two cautions belong in any CI design. Do not cache the user cache expecting compile wins: it is not the compile cache, and clearing or restoring it changes nothing about compile times. And do not share one restored cache across different flag sets — the keys differ by design, so a shared directory accumulates both variants and burns the 500-entry budget twice as fast.
A worked judgement call
The tempting policy is "compile everything, cache it all" — one blanket step, no curation. The index makes a quiet argument against it. Every artefact occupies the same LRU budget, so a cache full of functions that run once per deploy evicts the ones your latency actually depends on, and the eviction is silent. Our rule: partition the cache by project, precompile the request path and the batch inner loop at provision time, and leave cold-path code to compile lazily or not at all. Cache the winners, not the catalogue — the 500-entry ceiling means curation is not optional hygiene, it is the policy that keeps the cache hitting where it matters.
Where to go next
- Caching compiled artifacts in CI — the pipeline recipes, cache keys and proof assertions for this cache.
- How to run a speed proof — measuring compile and steady-state costs so cache wins are visible, not assumed.
- Measuring Pyvorin adoption — cache hit rate as one of the five adoption metrics.
- Benchmarks — the workload table behind the break-even judgement.
Last reviewed 28 July 2026 against pyvorin-native 1.0.9 installed at /root/pvfinal. Cache locations, index fields and file counts were read from the installed artefacts; cold and warm timings were captured from real runs on 13 September 2026; the 500-entry LRU cap and checksum validation were read from the installed cache implementation.