Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/znippy-zoomies

.nornir/ljar-reasoning.md

ljar — reasoning

Class: reasoning · Function: ljar. Why the engine is shaped this way and where the real saturation risk lives. The implemented shape is in ljar-design.md.

Why JAR is a genuinely parallel problem

Unlike gzip's single continuous LZ77 window, a JAR is a set of independent members: each entry is its own DEFLATE (or STORE) stream with its own CRC-32, indexed by a Central Directory at the file tail. There is no cross-entry back-reference to decode through. So after one cheap serial step — parse the Central Directory — every entry can decode on its own core. That is embarrassingly parallel, and it is the common shape (a fat JAR is hundreds of .class files).

Why the balanced fan-out, not a plain one

The trap is skew. Real archives are lopsided: one 40 MB shaded uber-jar payload among a thousand tiny stubs, or a single fat resource among the class files. A plain fan-out hands each worker the next index; whichever worker grabs the monster runs alone long after its siblings have drained the tail — one busy core, the rest idle, exactly the core-saturation bug we refuse to ship. Weighting each unit by uncompressed_size makes the gatling schedule heaviest-first (LPT / longest-processing-time): the monster is claimed first, and the cheap tail backfills the workers that finish early. It is a scheduling change only — streaming_skewed_roundtrip_byte_identical (ljar/src/reader.rs) proves the decoded bytes and the output ordering are untouched.

Why streaming sorts by offset and batches

The streaming reader (ljar/src/reader.rs:38) sorts entries by local_header_offset so the compressed reads walk the file forward — sequential I/O, not a seek storm — and it decodes in batches of 64 (BATCH_SIZE). Batching bounds memory: only one batch of decompressed output is resident at a time, so peak RSS is independent of JAR size and the file is never fully loaded. The alternative (decode everything into one Vec) is an O(output) peak-RSS regression for no throughput gain.

Why not a bespoke thread pool

Earlier ljar carried its own rayon ThreadPool (LJAR_THREADS); it is gone. The constellation's no-rayon law (tests/rayon_free_law.rs) folds every codec onto the one shared gatling engine, so the ordering, self-dispatch, and index-ordered collection are proven once and reused (gatling/src/gatling_forkjoin.rs:209). See gatling-reasoning.md. (The parallel.rs module docstring still says "rayon thread pool" — that comment is stale; the code calls gatling.)

Honest numbers

No cores_busy is hard-coded here. The real figures land in the README.md A/B/C mashup on the quiet-box heavy-bench wave.