loaderx 2.2.1__tar.gz → 2.3.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {loaderx-2.2.1/loaderx.egg-info → loaderx-2.3.3}/PKG-INFO +102 -79
- {loaderx-2.2.1 → loaderx-2.3.3}/README.md +101 -78
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx/__init__.py +1 -1
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx/_store.py +8 -4
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx/zrecord.py +23 -7
- {loaderx-2.2.1 → loaderx-2.3.3/loaderx.egg-info}/PKG-INFO +102 -79
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/_bench_common.py +5 -3
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/bench_dense.py +4 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/bench_ragged.py +9 -3
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/test_loaderx.py +90 -54
- {loaderx-2.2.1 → loaderx-2.3.3}/src/record/engine.zig +202 -241
- {loaderx-2.2.1 → loaderx-2.3.3}/src/record/executor.zig +0 -10
- {loaderx-2.2.1 → loaderx-2.3.3}/src/record/storage.zig +125 -55
- {loaderx-2.2.1 → loaderx-2.3.3}/src/store.zig +5 -10
- {loaderx-2.2.1 → loaderx-2.3.3}/LICENSE +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/MANIFEST.in +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/build.zig +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/build.zig.zon +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx/_lib.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx/dataloader.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx/utils.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx/zsampler.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx.egg-info/SOURCES.txt +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx.egg-info/dependency_links.txt +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx.egg-info/requires.txt +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/loaderx.egg-info/top_level.txt +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/pyproject.toml +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/bench.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/build_wheels.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/prepare_tokens.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/scripts/requirements-bench.txt +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/setup.cfg +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/setup.py +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/src/zsampler.zig +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/src/zstd/c.zig +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/COPYING +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/LICENSE +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/allocations.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/bits.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/bitstream.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/compiler.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/cpu.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/debug.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/debug.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/entropy_common.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/error_private.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/error_private.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/fse.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/fse_decompress.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/huf.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/mem.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/pool.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/pool.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/portability_macros.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/threading.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/threading.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/xxhash.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/xxhash.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/zstd_common.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/zstd_deps.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/zstd_internal.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/common/zstd_trace.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/clevels.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/fse_compress.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/hist.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/hist.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/huf_compress.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress_internal.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress_literals.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress_literals.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress_sequences.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress_sequences.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress_superblock.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_compress_superblock.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_cwksp.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_double_fast.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_double_fast.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_fast.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_fast.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_lazy.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_lazy.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_ldm.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_ldm.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_ldm_geartab.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_opt.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_opt.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_preSplit.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstd_preSplit.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstdmt_compress.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/compress/zstdmt_compress.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/huf_decompress.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/huf_decompress_amd64.S +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/zstd_ddict.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/zstd_ddict.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/zstd_decompress.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/zstd_decompress_block.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/zstd_decompress_block.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/decompress/zstd_decompress_internal.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/dictBuilder/cover.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/dictBuilder/cover.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/dictBuilder/divsufsort.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/dictBuilder/divsufsort.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/dictBuilder/fastcover.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/dictBuilder/zdict.c +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/zdict.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/zstd.h +0 -0
- {loaderx-2.2.1 → loaderx-2.3.3}/vendor/zstd/lib/zstd_errors.h +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loaderx
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.3
|
|
4
4
|
Summary: Rebuildable high-performance ordered record containers
|
|
5
5
|
Author-email: Ben0i0d <ben0i0d@foxmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -269,7 +269,7 @@ buffer, then completes the native append before returning. Nothing is inferred.
|
|
|
269
269
|
```python
|
|
270
270
|
from loaderx.zrecord import Dense, Ragged
|
|
271
271
|
|
|
272
|
-
ds = Dense.create('mnist/x', dtype=np.uint8, item_shape=(28, 28))
|
|
272
|
+
ds = Dense.create('mnist/x', dtype=np.uint8, item_shape=(28, 28), data_shards=4)
|
|
273
273
|
ds.append(images[i:i + 1024]) # synchronous native batch; returns None
|
|
274
274
|
ds.append(single_image[None]) # one sample is batch_size 1 — add the axis yourself
|
|
275
275
|
ds.close() # publish before opening
|
|
@@ -282,6 +282,11 @@ with Dense.open('mnist/x') as ds:
|
|
|
282
282
|
first_four = ds[:4] # opened containers are read-only
|
|
283
283
|
```
|
|
284
284
|
|
|
285
|
+
`data_shards` is the keyword-only write-parallelism setting. It is persisted in
|
|
286
|
+
the native Header, must be in `1..255`, and defaults to four. Values near the
|
|
287
|
+
writer lane count spread payload writes across independent files; opening a
|
|
288
|
+
completed container discovers the value automatically.
|
|
289
|
+
|
|
285
290
|
Dense and Ragged append both report native errors in the current call and return
|
|
286
291
|
only after accepting the batch. A writer cannot be read, and a reader cannot be
|
|
287
292
|
appended to. ``close()`` on a writer publishes the container Header;
|
|
@@ -295,7 +300,7 @@ schemas contain ``dtype`` and ``ndim``; Dense additionally contains
|
|
|
295
300
|
appended record to have that rank, while every dimension length may vary.
|
|
296
301
|
Structured, subarray, object, metadata-bearing, and zero-itemsize dtypes are not
|
|
297
302
|
supported: their semantics do not round-trip through one canonical NumPy dtype
|
|
298
|
-
string. The encoded schema has
|
|
303
|
+
string. The encoded schema has 2040 bytes available in the fixed 4096-byte
|
|
299
304
|
metadata page. Ragged schema size is fixed; Dense schema size grows
|
|
300
305
|
only with the integer ``item_shape``, so the physical limit is far above any
|
|
301
306
|
practical NumPy array rank.
|
|
@@ -502,8 +507,8 @@ so CFFI, NumPy allocation, and Ragged list/shape reconstruction are timed.
|
|
|
502
507
|
### Methodology
|
|
503
508
|
|
|
504
509
|
The comparison matrix began as one complete run on a warm page cache. All
|
|
505
|
-
Zrecord Store rows and the two loaderx identity rows were refreshed on 2.
|
|
506
|
-
|
|
510
|
+
Zrecord Store rows and the two loaderx identity rows were refreshed on 2.3.0
|
|
511
|
+
with the default `data_shards=4`. Unchanged
|
|
507
512
|
optional Store backends, sampler rows and external loader rows remain from the
|
|
508
513
|
immediately preceding 2.0.9 run. These are not three-run medians: an unexpected result is traced
|
|
509
514
|
separately instead of being hidden by repeated aggregation. Within each store workload every
|
|
@@ -583,9 +588,9 @@ Fixed-resolution vision records — 147 KiB per record, 36.8 MiB per batch:
|
|
|
583
588
|
|
|
584
589
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
585
590
|
|---|---:|---:|---:|---:|---:|---:|
|
|
586
|
-
| zrecord-zstd |
|
|
587
|
-
| zrecord-zstdict |
|
|
588
|
-
| zrecord-raw |
|
|
591
|
+
| zrecord-zstd | 7172 MiB/s | 11081 MiB/s | 77.2 | 4.38 ms | 24.4 MiB | 14.69x |
|
|
592
|
+
| zrecord-zstdict | 104 MiB/s | 12042 MiB/s | 83.9 | 4.09 ms | 16.2 MiB | 22.10x |
|
|
593
|
+
| zrecord-raw | 5721 MiB/s | 14457 MiB/s | 100.7 | 3.16 ms | 358.9 MiB | 1.00x |
|
|
589
594
|
| npy-mmap-raw | 2004 MiB/s | 4749 MiB/s | 33.1 | 11.08 ms | 358.9 MiB | 1.00x |
|
|
590
595
|
| hdf5-raw | 2350 MiB/s | 1836 MiB/s | 12.8 | 29.68 ms | 359.0 MiB | 1.00x |
|
|
591
596
|
| hdf5-gzip | 277 MiB/s | 657 MiB/s | 4.6 | 62.53 ms | 26.1 MiB | 13.73x |
|
|
@@ -599,8 +604,8 @@ Fixed-resolution vision records — 147 KiB per record, 36.8 MiB per batch:
|
|
|
599
604
|
| tiledb-raw | 743 MiB/s | 630 MiB/s | 4.4 | 66.01 ms | 359.0 MiB | 1.00x |
|
|
600
605
|
| tiledb-zstd | 1498 MiB/s | 1503 MiB/s | 10.5 | 27.82 ms | 26.9 MiB | 13.36x |
|
|
601
606
|
|
|
602
|
-
At 147 KiB per record, Zrecord-raw reaches 14.
|
|
603
|
-
plain zstd gathers at 10.
|
|
607
|
+
At 147 KiB per record, Zrecord-raw reaches 14.1 GiB/s and is 3.0x npy-mmap-raw;
|
|
608
|
+
plain zstd gathers at 10.8 GiB/s while reducing the corpus 14.69x. LMDB and Arrow
|
|
604
609
|
IPC are competitive raw record
|
|
605
610
|
stores, while codecs tied to whole IPC batches or Parquet row groups pay read
|
|
606
611
|
amplification on random gathers. Dense demonstrates that
|
|
@@ -618,9 +623,9 @@ list or a one-dimensional variable-length abstraction is not enough.
|
|
|
618
623
|
|
|
619
624
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
620
625
|
|---|---:|---:|---:|---:|---:|---:|
|
|
621
|
-
| zrecord-zstd |
|
|
622
|
-
| zrecord-zstdict | 102 MiB/s |
|
|
623
|
-
| zrecord-raw |
|
|
626
|
+
| zrecord-zstd | 2220 MiB/s | 9444 MiB/s | 56.7 | 5.67 ms | 26.9 MiB | 15.48x |
|
|
627
|
+
| zrecord-zstdict | 102 MiB/s | 10345 MiB/s | 62.1 | 5.15 ms | 18.2 MiB | 22.97x |
|
|
628
|
+
| zrecord-raw | 2171 MiB/s | 12356 MiB/s | 74.2 | 4.22 ms | 417.2 MiB | 1.00x |
|
|
624
629
|
| hdf5-raw | 1451 MiB/s | 1059 MiB/s | 6.4 | 44.91 ms | 418.0 MiB | 1.00x |
|
|
625
630
|
| hdf5-gzip | 250 MiB/s | 126 MiB/s | 0.7 | 359.23 ms | 29.9 MiB | 13.94x |
|
|
626
631
|
| lmdb-raw | 1717 MiB/s | 7269 MiB/s | 43.6 | 7.93 ms | 422.2 MiB | 0.99x |
|
|
@@ -633,16 +638,16 @@ list or a one-dimensional variable-length abstraction is not enough.
|
|
|
633
638
|
| tiledb-raw | 392 MiB/s | 48 MiB/s | 0.3 | 941.09 ms | 417.2 MiB | 1.00x |
|
|
634
639
|
| tiledb-zstd | 675 MiB/s | 138 MiB/s | 0.8 | 328.05 ms | 27.0 MiB | 15.46x |
|
|
635
640
|
|
|
636
|
-
Zrecord-raw is 1.
|
|
637
|
-
delivers 9.
|
|
641
|
+
Zrecord-raw is 1.7x LMDB and 3.1x Arrow IPC in logical gather. Zrecord-zstd
|
|
642
|
+
delivers 9.2 GiB/s of
|
|
638
643
|
logical payload while reducing the corpus to 26.9 MiB. HDF5, Arrow IPC, Parquet,
|
|
639
644
|
ArrayRecord and TileDB
|
|
640
645
|
show the same framework/codec tradeoffs in both tables; compressed batch, chunk
|
|
641
646
|
and row-group formats pay read amplification on random records.
|
|
642
647
|
|
|
643
648
|
The shared generator makes compression ratios directly comparable across
|
|
644
|
-
contracts: Zrecord zstd is 14.69x Dense versus 15.
|
|
645
|
-
22.10x versus 22.
|
|
649
|
+
contracts: Zrecord zstd is 14.69x Dense versus 15.48x Ragged, and zstdict is
|
|
650
|
+
22.10x versus 22.97x. The remaining difference comes from the H/W distribution
|
|
646
651
|
and Ragged shape metadata, not a different image entropy model.
|
|
647
652
|
|
|
648
653
|
### Small Token Records
|
|
@@ -662,9 +667,9 @@ accumulate at least two seconds.
|
|
|
662
667
|
|
|
663
668
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
664
669
|
|---|---:|---:|---:|---:|---:|---:|
|
|
665
|
-
| zrecord-zstd |
|
|
666
|
-
| zrecord-zstdict |
|
|
667
|
-
| zrecord-raw |
|
|
670
|
+
| zrecord-zstd | 989 MiB/s | 2012 MiB/s | 1030.3 | 0.34 ms | 193.2 MiB | 2.02x |
|
|
671
|
+
| zrecord-zstdict | 62 MiB/s | 2079 MiB/s | 1064.6 | 0.32 ms | 154.4 MiB | 2.53x |
|
|
672
|
+
| zrecord-raw | 4542 MiB/s | 6391 MiB/s | 3272.3 | 0.11 ms | 393.7 MiB | 0.99x |
|
|
668
673
|
| npy-mmap-raw | 1587 MiB/s | 9070 MiB/s | 4644.0 | 0.08 ms | 390.6 MiB | 1.00x |
|
|
669
674
|
| lmdb-raw | 639 MiB/s | 1104 MiB/s | 565.0 | 0.71 ms | 786.3 MiB | 0.50x |
|
|
670
675
|
| arrow-ipc-raw | 2068 MiB/s | 225 MiB/s | 115.0 | 2.81 ms | 390.8 MiB | 1.00x |
|
|
@@ -672,9 +677,9 @@ accumulate at least two seconds.
|
|
|
672
677
|
| arrayrecord-zstd | 127 MiB/s | 135 MiB/s | 69.4 | 4.45 ms | 201.3 MiB | 1.94x |
|
|
673
678
|
|
|
674
679
|
The contiguous NumPy baseline is strongest when the whole corpus is one fixed
|
|
675
|
-
typed matrix. Zrecord-raw reaches 3.
|
|
680
|
+
typed matrix. Zrecord-raw reaches 3.27 Mrecords/s while retaining independent
|
|
676
681
|
record semantics; the per-record zstd codecs halve disk and still return
|
|
677
|
-
1.
|
|
682
|
+
1.03–1.06 Mrecords/s. LMDB's B-tree/page overhead is visible in both throughput
|
|
678
683
|
and disk.
|
|
679
684
|
|
|
680
685
|
#### Variable Token Sequences
|
|
@@ -686,16 +691,16 @@ with exact `int32` values and original one-dimensional shapes.
|
|
|
686
691
|
|
|
687
692
|
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
688
693
|
|---|---:|---:|---:|---:|---:|---:|
|
|
689
|
-
| zrecord-zstd |
|
|
690
|
-
| zrecord-zstdict |
|
|
691
|
-
| zrecord-raw |
|
|
694
|
+
| zrecord-zstd | 287 MiB/s | 315 MiB/s | 594.6 | 0.60 ms | 67.4 MiB | 1.57x |
|
|
695
|
+
| zrecord-zstdict | 54 MiB/s | 332 MiB/s | 627.6 | 0.58 ms | 49.1 MiB | 2.16x |
|
|
696
|
+
| zrecord-raw | 536 MiB/s | 389 MiB/s | 734.9 | 0.50 ms | 110.5 MiB | 0.96x |
|
|
692
697
|
| lmdb-raw | 318 MiB/s | 113 MiB/s | 214.3 | 1.48 ms | 153.0 MiB | 0.69x |
|
|
693
698
|
| arrow-ipc-raw | 562 MiB/s | 44 MiB/s | 83.1 | 4.00 ms | 109.9 MiB | 0.96x |
|
|
694
699
|
| arrayrecord-raw | 288 MiB/s | 26 MiB/s | 49.3 | 7.39 ms | 118.8 MiB | 0.89x |
|
|
695
700
|
| arrayrecord-zstd | 62 MiB/s | 33 MiB/s | 61.5 | 5.58 ms | 76.2 MiB | 1.39x |
|
|
696
701
|
|
|
697
702
|
Here the record contract, not bulk byte bandwidth, is the useful scale.
|
|
698
|
-
Zrecord's three codecs return
|
|
703
|
+
Zrecord's three codecs return 595–735 krecords/s with 0.50–0.60 ms p95;
|
|
699
704
|
the dictionary gives the best disk ratio and is slightly ahead of plain zstd in
|
|
700
705
|
this pass.
|
|
701
706
|
|
|
@@ -742,8 +747,8 @@ and Grain reads ArrayRecord.
|
|
|
742
747
|
|
|
743
748
|
| loader | model | storage | batches/s | p95 | steady PSS | peak PSS | peak RSS |
|
|
744
749
|
|---|---|---|---:|---:|---:|---:|---:|
|
|
745
|
-
| **loaderx** | threads | zrecord-zstd |
|
|
746
|
-
| loaderx-raw | threads | zrecord-raw |
|
|
750
|
+
| **loaderx** | threads | zrecord-zstd | 171.5 | 12.69 ms | 984 MiB | 985 MiB | 988 MiB |
|
|
751
|
+
| loaderx-raw | threads | zrecord-raw | 200.0 | 12.50 ms | 993 MiB | 993 MiB | 996 MiB |
|
|
747
752
|
| torch | fork | npy-mmap-raw | 109.4 | 32.70 ms | 1783 MiB | 1889 MiB | 6396 MiB |
|
|
748
753
|
| torch-spawn | spawn | npy-mmap-raw | 111.6 | 30.69 ms | 2835 MiB | 2913 MiB | 4880 MiB |
|
|
749
754
|
| grain | processes | arrayrecord-zstd | 46.6 | 93.48 ms | 1847 MiB | 1946 MiB | 2065 MiB |
|
|
@@ -753,8 +758,8 @@ the transform threads overlap Python-side collation with the next gather. The
|
|
|
753
758
|
memory is the source, Zrecord container and bounded in-flight batches. loaderx prefetches in
|
|
754
759
|
threads inside one process, so workers share one interpreter, one NumPy runtime
|
|
755
760
|
and one set of gather buffers. With source geometry and entropy held constant,
|
|
756
|
-
raw is 1.
|
|
757
|
-
Torch spawn and 3.
|
|
761
|
+
raw is 1.17x compressed loaderx; compressed loaderx is 1.57x Torch fork, 1.54x
|
|
762
|
+
Torch spawn and 3.68x Grain, while raw is 1.83x, 1.79x and 4.29x faster.
|
|
758
763
|
Torch's aggregate RSS is high because
|
|
759
764
|
Linux fork mappings are counted repeatedly; it is not a total-memory ratio
|
|
760
765
|
against Zrecord's unaccounted page cache. The explicit `torch-spawn` row removes
|
|
@@ -828,8 +833,8 @@ format: in the current Dense structured-vision workload, plain zstd reaches
|
|
|
828
833
|
**Loader results combine architecture and storage.** loaderx uses threads and
|
|
829
834
|
never ends an epoch, so a step pays no IPC and never waits on an epoch boundary;
|
|
830
835
|
torch uses finite shuffled epochs, worker processes and shared-memory handoff.
|
|
831
|
-
Here compressed loaderx is 1.
|
|
832
|
-
raw loaderx is 1.
|
|
836
|
+
Here compressed loaderx is 1.57x Torch fork, 1.54x Torch spawn and 3.68x Grain;
|
|
837
|
+
raw loaderx is 1.83x, 1.79x and 4.29x faster, respectively.
|
|
833
838
|
Storage also differs per loader — each reads from what it was
|
|
834
839
|
built for — so the loader table is a different comparison from either store
|
|
835
840
|
table, not a rerun.
|
|
@@ -1055,9 +1060,9 @@ license to trust storage or the operating system: native code still validates
|
|
|
1055
1060
|
normal I/O behavior, basic malformed-store rejection and native memory safety; checks that only
|
|
1056
1061
|
defend against bypassing the public Python API do not belong in zrecord.
|
|
1057
1062
|
|
|
1058
|
-
1. `RecordEngine` stores N logically ordered records.
|
|
1059
|
-
|
|
1060
|
-
|
|
1063
|
+
1. `RecordEngine` stores N logically ordered records. Payload record `i` belongs
|
|
1064
|
+
to `data_{i % data_shards}.zr`; logical ID remains the stable append position
|
|
1065
|
+
and `RecordLoc[ID]` preserves its shard-local offset. Index and slice
|
|
1061
1066
|
operations are implemented as ordered gathers over those positions.
|
|
1062
1067
|
2. It hands the container layer a dense sequence space: records are exactly
|
|
1063
1068
|
`0..N-1`. Named streams are composed dynamically by a plain Python dict;
|
|
@@ -1120,24 +1125,33 @@ the authority for irreplaceable data. Keep authoritative source data and reprodu
|
|
|
1120
1125
|
build scripts; after an interrupted build, storage failure, incompatible implementation
|
|
1121
1126
|
change, or content change, rebuild a complete container at a new path.
|
|
1122
1127
|
|
|
1123
|
-
Native storage uses a fixed file set:
|
|
1128
|
+
Native storage uses one metadata file and a create-time-fixed payload file set:
|
|
1124
1129
|
```
|
|
1125
1130
|
store/
|
|
1126
|
-
├── meta.zr 4096-byte
|
|
1127
|
-
├──
|
|
1131
|
+
├── meta.zr 4096-byte Header/schema/tails page + RecordLoc table
|
|
1132
|
+
├── data_0.zr payload records where ID % data_shards == 0
|
|
1133
|
+
├── ...
|
|
1134
|
+
├── data_{N-1}.zr final static payload shard
|
|
1128
1135
|
└── dict.zr zstd dictionary (only in dict stores)
|
|
1129
1136
|
```
|
|
1130
1137
|
|
|
1138
|
+
`data_shards` is a write-performance parameter in `1..255`, fixed by `create`
|
|
1139
|
+
and recovered automatically by `open`. The default is four; practical values are
|
|
1140
|
+
usually 2, 4, 8 or 16, near the writer lane count. More files spread positional
|
|
1141
|
+
writes across payload inodes but consume one descriptor each. This physical
|
|
1142
|
+
striping does not change record IDs, order, codec, or read results.
|
|
1143
|
+
|
|
1131
1144
|
### Metadata (meta.zr)
|
|
1132
1145
|
|
|
1133
1146
|
Files are read and written **positionally** — pread/pwrite at computed offsets,
|
|
1134
1147
|
no mmap. `meta.zr` starts with one fixed 4096-byte static page: a naturally
|
|
1135
|
-
aligned
|
|
1148
|
+
aligned 16-byte Header, 255 shard-local u64 tails at bytes 16..2055, then up to
|
|
1149
|
+
2040 bytes of opaque MsgPack schema at bytes 2056..4095.
|
|
1136
1150
|
An array of 16-byte `RecordLoc`s starts at offset 4096. Record `i` is one
|
|
1137
1151
|
pread/pwrite at `4096 + i * 16`; there is no variable table base, segment
|
|
1138
1152
|
mapping, or rollover fd table.
|
|
1139
1153
|
|
|
1140
|
-
**1. Python schema** — bytes `
|
|
1154
|
+
**1. Python schema** — bytes `2056..2056+schema_length` are exactly one immutable
|
|
1141
1155
|
MsgPack object. Both stores contain ``dtype`` and ``ndim``; Dense additionally
|
|
1142
1156
|
contains ``item_shape``. Native create persists these bytes together with
|
|
1143
1157
|
the physical container but does not decode them. Open acquires the native lifetime
|
|
@@ -1147,30 +1161,36 @@ dtype/item_shape and passed to the native handle as runtime geometry; it is not
|
|
|
1147
1161
|
independently persisted as a second authority. There is no format version or
|
|
1148
1162
|
legacy kind dispatch.
|
|
1149
1163
|
|
|
1150
|
-
**2. Physical header** — the first
|
|
1164
|
+
**2. Physical header** — the first 16 bytes of `meta.zr`. The format
|
|
1151
1165
|
deliberately carries no payload or metadata checksum.
|
|
1152
1166
|
|
|
1153
1167
|
* `codec` is the store's one compression method, stamped at creation and
|
|
1154
1168
|
immutable — there is no per-record tag anywhere.
|
|
1155
1169
|
* `length` (u64) is the physical record count; it equals logical length for both
|
|
1156
1170
|
dense stores and inline ragged stores.
|
|
1157
|
-
* `
|
|
1158
|
-
|
|
1159
|
-
|
|
1171
|
+
* `schema_length` (u16) is the occupied prefix of the static schema area and
|
|
1172
|
+
must be in `1..2040`.
|
|
1173
|
+
* `data_shards` (u8) is the static payload file count and must be in `1..255`.
|
|
1160
1174
|
|
|
1161
1175
|
```zig
|
|
1162
1176
|
const Codec = enum(u8) { raw = 0, zstd = 1, zstdict = 2, _ };
|
|
1163
1177
|
const Header = extern struct {
|
|
1164
1178
|
length: u64,
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
reserved: [11]u8,
|
|
1179
|
+
schema_length: u16,
|
|
1180
|
+
data_shards: u8,
|
|
1168
1181
|
codec: u8,
|
|
1182
|
+
reserved: [4]u8,
|
|
1169
1183
|
};
|
|
1170
1184
|
```
|
|
1171
1185
|
|
|
1172
|
-
**3.
|
|
1173
|
-
`
|
|
1186
|
+
**3. Shard frontiers** — tail slot `s` at `16 + s * 8` is the committed
|
|
1187
|
+
end of `data_s.zr`. Unused slots among the 255 fixed u64 entries are zero.
|
|
1188
|
+
Open requires every data file to be at least its persisted tail; locations may
|
|
1189
|
+
not cross that shard-local frontier.
|
|
1190
|
+
|
|
1191
|
+
**4. Record table** — contiguous 16-byte entries start at offset 4096 in
|
|
1192
|
+
`meta.zr` and grow as location windows are written. `offset` is local to
|
|
1193
|
+
`data_{ID % data_shards}.zr`;
|
|
1174
1194
|
`phys_length`/`logic_length` are the stored and original sizes. The
|
|
1175
1195
|
codec is not here: it is the header's, so a record is stored exactly the way the
|
|
1176
1196
|
store is declared.
|
|
@@ -1185,9 +1205,9 @@ const RecordLoc = extern struct {
|
|
|
1185
1205
|
|
|
1186
1206
|
There is no liveness flag. Every entry below `length` is a record.
|
|
1187
1207
|
|
|
1188
|
-
**
|
|
1189
|
-
|
|
1190
|
-
|
|
1208
|
+
**5. No fixed record-count cap.** The table and payload streams grow naturally.
|
|
1209
|
+
The practical bounds are the u64 count, supported positional file offsets,
|
|
1210
|
+
2 GiB per record, descriptor budget, and disk.
|
|
1191
1211
|
|
|
1192
1212
|
## Executor
|
|
1193
1213
|
|
|
@@ -1202,34 +1222,36 @@ while `Ragged` supplies offsets for its shape-prefixed records.
|
|
|
1202
1222
|
The private ABI turns those inputs into compile-time record sources and
|
|
1203
1223
|
destinations; the engine has one append and one gather operation. Its shared
|
|
1204
1224
|
opaque handle remains private and carries no typed-store geometry.
|
|
1205
|
-
*
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
the
|
|
1213
|
-
|
|
1214
|
-
|
|
1225
|
+
* Append validates the complete call before physical I/O, then processes bounded
|
|
1226
|
+
logical windows. Within a window, record positions are divided by
|
|
1227
|
+
`ID % data_shards` and planned as bounded shard-local chunks. Fixed Executor
|
|
1228
|
+
lanes dynamically claim those chunks, encode or pack them, briefly lock only
|
|
1229
|
+
the selected shard's tail reservation, then issue positional payload writes
|
|
1230
|
+
directly. Multiple lanes may write non-overlapping ranges of one shard; the
|
|
1231
|
+
static file set spreads that pressure across inodes without limiting codec
|
|
1232
|
+
concurrency to the shard count. Workers fill disjoint entries in one
|
|
1233
|
+
contiguous location buffer. A payload barrier precedes one contiguous
|
|
1234
|
+
`meta.zr` location write. All windows must succeed before the in-process
|
|
1235
|
+
length and per-shard tails advance. Writer `close()` truncates each data file
|
|
1236
|
+
to its committed tail and publishes the 4096-byte static page.
|
|
1237
|
+
* Compressed shard tasks lease process-bounded `ExecutionSlot` scratch and write
|
|
1238
|
+
independent frames in bounded subchunks. Python budgets the process-wide executor at three quarters
|
|
1215
1239
|
of the logical CPUs available to the process, leaving headroom for packing,
|
|
1216
1240
|
transforms, and the caller without encoding a platform-specific thread count.
|
|
1217
1241
|
Each producer configures its CCtx or shared immutable CDict once, then starts
|
|
1218
1242
|
every independent record frame with `ZSTD_compress2`.
|
|
1219
|
-
* Raw
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
`std.Io` handles short writes and maps the same positional operation to POSIX
|
|
1226
|
-
and Windows implementations.
|
|
1243
|
+
* Raw records for one shard are strided in the source. Each shard task packs a
|
|
1244
|
+
bounded subchunk into its reusable scratch and performs one contiguous
|
|
1245
|
+
positional write; a single record larger than the normal subchunk budget is
|
|
1246
|
+
written directly. There is no per-record syscall or platform-specific vectored
|
|
1247
|
+
path. The extra memory copy is the deliberate cost paid to remove concentrated
|
|
1248
|
+
single-inode writes.
|
|
1227
1249
|
**2. Read.** Fill the destination memory concurrently, in place from the Python
|
|
1228
1250
|
side (executed on async threads).
|
|
1229
1251
|
* Committed records are immutable and `length` is published through an atomic.
|
|
1230
|
-
The fixed
|
|
1231
|
-
|
|
1232
|
-
|
|
1252
|
+
The fixed metadata and static data handles require no rollover synchronization.
|
|
1253
|
+
* Every record first selects `data_{ID % data_shards}.zr`, then reads the
|
|
1254
|
+
shard-local offset its table entry records — the record table
|
|
1233
1255
|
is addressed by pure arithmetic, so random access is one pread for the
|
|
1234
1256
|
location and one for the bytes, with no batching assumptions about layout.
|
|
1235
1257
|
Each lane reads one location and immediately reads/decompresses that record;
|
|
@@ -1241,8 +1263,9 @@ budget; lanes for which the runtime cannot reserve concurrency run inline on the
|
|
|
1241
1263
|
calling thread. Python configures the process-level budget as
|
|
1242
1264
|
`max(physical cores, logical cores * 3 / 4)`, using platform topology where
|
|
1243
1265
|
available.
|
|
1244
|
-
*
|
|
1245
|
-
|
|
1266
|
+
* Gather lanes receive contiguous request blocks. Append lanes dynamically claim
|
|
1267
|
+
bounded chunks of strided shard ranges; short per-shard reservation locks keep
|
|
1268
|
+
offsets disjoint while positional writes remain concurrent.
|
|
1246
1269
|
* Each lane creates one zstd context (`ZSTD_CCtx` to write, `ZSTD_DCtx` to
|
|
1247
1270
|
read) and reuses it across every record it handles, rather than paying that
|
|
1248
1271
|
setup per record. The dictionary (`ZSTD_CDict`/`ZSTD_DDict`) is immutable, so
|
|
@@ -1252,10 +1275,10 @@ available.
|
|
|
1252
1275
|
|
|
1253
1276
|
**4. File access.**
|
|
1254
1277
|
* Metadata: one naturally growing `meta.zr`, containing the fixed Header/schema
|
|
1255
|
-
page and loc table.
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
sparse
|
|
1278
|
+
/tails page and loc table. It is intentionally not sharded because measured
|
|
1279
|
+
write pressure is in payload I/O; one coordinator writes each loc window.
|
|
1280
|
+
* Payload: a static list of naturally growing `data_<shard>.zr` files, accessed through
|
|
1281
|
+
`readPositionalAll`/`writePositionalAll`. No path depends on sparse files.
|
|
1259
1282
|
|
|
1260
1283
|
**Execution model.** Opened readers are immutable, so calls on the same reader may
|
|
1261
1284
|
gather concurrently. Creator appends are synchronous and native Storage
|