loaderx 1.7.2__tar.gz → 1.7.7__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-1.7.2/loaderx.egg-info → loaderx-1.7.7}/PKG-INFO +85 -50
- {loaderx-1.7.2 → loaderx-1.7.7}/README.md +84 -49
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx/__init__.py +1 -1
- {loaderx-1.7.2 → loaderx-1.7.7/loaderx.egg-info}/PKG-INFO +85 -50
- {loaderx-1.7.2 → loaderx-1.7.7}/scripts/_bench_common.py +219 -87
- loaderx-1.7.7/scripts/bench_ragged.py +632 -0
- loaderx-1.7.2/scripts/bench_ragged.py +0 -375
- {loaderx-1.7.2 → loaderx-1.7.7}/LICENSE +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/MANIFEST.in +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/build.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/build.zig.zon +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx/_lib.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx/_store.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx/dataloader.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx/utils.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx/zrecord.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx/zsampler.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx.egg-info/SOURCES.txt +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx.egg-info/dependency_links.txt +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx.egg-info/requires.txt +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/loaderx.egg-info/top_level.txt +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/pyproject.toml +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/scripts/bench.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/scripts/bench_dense.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/scripts/build_wheels.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/scripts/test_loaderx.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/setup.cfg +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/setup.py +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/record/codec.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/record/engine.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/record/exec.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/record/format.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/record/storage.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/store/common.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/store/dense.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/store/ragged.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/store.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/zsampler.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/src/zstd/c.zig +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/COPYING +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/LICENSE +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/allocations.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/bits.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/bitstream.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/compiler.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/cpu.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/debug.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/debug.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/entropy_common.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/error_private.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/error_private.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/fse.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/fse_decompress.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/huf.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/mem.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/pool.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/pool.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/portability_macros.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/threading.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/threading.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/xxhash.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/xxhash.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/zstd_common.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/zstd_deps.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/zstd_internal.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/common/zstd_trace.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/clevels.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/fse_compress.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/hist.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/hist.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/huf_compress.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress_internal.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress_literals.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress_literals.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress_sequences.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress_sequences.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress_superblock.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_compress_superblock.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_cwksp.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_double_fast.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_double_fast.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_fast.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_fast.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_lazy.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_lazy.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_ldm.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_ldm.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_ldm_geartab.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_opt.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstd_opt.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstdmt_compress.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/compress/zstdmt_compress.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/decompress/huf_decompress.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/decompress/zstd_ddict.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/decompress/zstd_ddict.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/decompress/zstd_decompress.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/decompress/zstd_decompress_block.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/decompress/zstd_decompress_block.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/decompress/zstd_decompress_internal.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/dictBuilder/cover.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/dictBuilder/cover.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/dictBuilder/divsufsort.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/dictBuilder/divsufsort.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/dictBuilder/fastcover.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/dictBuilder/zdict.c +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/zdict.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/zstd.h +0 -0
- {loaderx-1.7.2 → loaderx-1.7.7}/vendor/zstd/lib/zstd_errors.h +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loaderx
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.7
|
|
4
4
|
Summary: Zrecord typed persistent stores and the Loaderx data loader for single-machine AI pipelines
|
|
5
5
|
Author-email: Ben0i0d <ben0i0d@foxmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -74,6 +74,11 @@ loaderx is built around several core principles:
|
|
|
74
74
|
list. Neither is padded, and equal length is never treated as a special case
|
|
75
75
|
of variable length.
|
|
76
76
|
|
|
77
|
+
## 设计文档
|
|
78
|
+
|
|
79
|
+
- [性能优化与工程决策记录](docs/性能优化.md)
|
|
80
|
+
- [分布式架构草案](docs/分布式架构.md)
|
|
81
|
+
|
|
77
82
|
## Quick Start
|
|
78
83
|
```python
|
|
79
84
|
from loaderx.zrecord import DenseStore
|
|
@@ -397,19 +402,25 @@ For practical integration examples, please refer to the **[Data2Latent](https://
|
|
|
397
402
|
|
|
398
403
|
Dense and Ragged are measured separately because they expose different contracts
|
|
399
404
|
and have different useful metrics. `scripts/bench_dense.py` measures fixed-shape
|
|
400
|
-
random gather, `scripts/bench_ragged.py` measures variable-
|
|
405
|
+
random gather, `scripts/bench_ragged.py` measures variable-shape records, and
|
|
401
406
|
`scripts/bench.py` covers machine, sampler, and the original dense loader
|
|
402
407
|
comparison. Every path runs through the public Python binding, so CFFI, NumPy
|
|
403
408
|
allocation, and Ragged list/shape reconstruction are inside the timings.
|
|
404
409
|
|
|
405
410
|
### Methodology
|
|
406
411
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
The `1.7.7` Dense and Ragged snapshots below are each one qualitative pass on a
|
|
413
|
+
warm page cache. Within each benchmark, every backend receives
|
|
414
|
+
identical source records and random index plans. Before timing, the benchmark
|
|
415
|
+
sweeps every record and validates every planned result for exact
|
|
416
|
+
dtype, shape, order, and values. `logical write` and `logical gather` divide
|
|
417
|
+
uncompressed NumPy payload bytes by elapsed time; they measure bytes accepted or
|
|
418
|
+
returned by the public API, not physical storage bandwidth. Write is page-cache
|
|
419
|
+
ingestion with durability deferred consistently across backends; call `sync()`
|
|
420
|
+
explicitly when measuring Zrecord durability. Disk size is allocated blocks,
|
|
421
|
+
not sparse apparent size.
|
|
422
|
+
Every backend name states its actual codec; the full default set is required
|
|
423
|
+
rather than silently skipped when a package is missing.
|
|
413
424
|
|
|
414
425
|
**Machine** — one local workstation (AMD Ryzen AI 9 HX PRO 370, 12 cores / 24
|
|
415
426
|
threads):
|
|
@@ -453,46 +464,67 @@ comparison; other record sizes are a `--shape` away.
|
|
|
453
464
|
|
|
454
465
|
Realistic records — 256 KiB per record, 64 MiB per batch:
|
|
455
466
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
|
459
|
-
|
|
460
|
-
| zrecord-
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
467
|
+
Dense snapshot: `1.7.7`.
|
|
468
|
+
|
|
469
|
+
| store | logical write | logical gather | on disk | ratio |
|
|
470
|
+
|----------------------------|--------------:|---------------:|--------:|------:|
|
|
471
|
+
| zrecord-zstd | 1073 MiB/s | 4572 MiB/s | 555 MiB | 1.13x |
|
|
472
|
+
| zrecord-zstdict | 48 MiB/s | 2051 MiB/s | 388 MiB | 1.61x |
|
|
473
|
+
| zrecord-raw | 2325 MiB/s | 7996 MiB/s | 625 MiB | 1.00x |
|
|
474
|
+
| npy-mmap-raw | 2824 MiB/s | 3880 MiB/s | 625 MiB | 1.00x |
|
|
475
|
+
| hdf5-raw | 2385 MiB/s | 2155 MiB/s | 625 MiB | 1.00x |
|
|
476
|
+
| hdf5-gzip | 50 MiB/s | 234 MiB/s | 445 MiB | 1.40x |
|
|
477
|
+
| lmdb-raw | 794 MiB/s | 3406 MiB/s | 635 MiB | 0.98x |
|
|
478
|
+
| arrow-ipc-raw | 1657 MiB/s | 2601 MiB/s | 625 MiB | 1.00x |
|
|
479
|
+
| arrow-ipc-zstd | 311 MiB/s | 104 MiB/s | 418 MiB | 1.49x |
|
|
480
|
+
| parquet-raw | 1220 MiB/s | 442 MiB/s | 625 MiB | 1.00x |
|
|
481
|
+
| parquet-zstd | 311 MiB/s | 91 MiB/s | 418 MiB | 1.49x |
|
|
482
|
+
| arrayrecord-raw | 1878 MiB/s | 1873 MiB/s | 625 MiB | 1.00x |
|
|
483
|
+
| arrayrecord-zstd | 481 MiB/s | 1530 MiB/s | 567 MiB | 1.10x |
|
|
484
|
+
| tiledb-raw | 818 MiB/s | 780 MiB/s | 625 MiB | 1.00x |
|
|
485
|
+
| tiledb-zstd | 812 MiB/s | 811 MiB/s | 553 MiB | 1.13x |
|
|
465
486
|
|
|
466
487
|
At 256 KiB per record, a 64 MiB batch exceeds L3 and the raw paths are primarily
|
|
467
|
-
DRAM-bandwidth-bound. Zrecord-raw reaches 7.
|
|
468
|
-
plain zstd gathers at
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
|
484
|
-
|
|
485
|
-
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
488
|
+
DRAM-bandwidth-bound. Zrecord-raw reaches 7.8 GiB/s and is 2.1x npy-mmap-raw here;
|
|
489
|
+
plain zstd gathers at 4.5 GiB/s. LMDB and Arrow IPC are competitive raw record
|
|
490
|
+
stores, while codecs tied to whole IPC batches or Parquet row groups pay read
|
|
491
|
+
amplification on random gathers. DenseStore demonstrates that
|
|
492
|
+
typed record ownership and per-record compression do not turn fixed tensors
|
|
493
|
+
into an object-store slow path.
|
|
494
|
+
|
|
495
|
+
### RaggedStore: Variable-Shape Records
|
|
496
|
+
|
|
497
|
+
This workload contains 2,500 variable-resolution CHW RGB images. Height and
|
|
498
|
+
width are independently lognormal and clipped to 64..512 (observed medians
|
|
499
|
+
223 and 224), totaling 417.1 MiB of logical uint8 payload. Each of 50 random
|
|
500
|
+
batches contains 256 records. Every backend persists payload plus exact shape
|
|
501
|
+
and must return an ordered `list[np.ndarray]` of `(3, H, W)` arrays; a flat byte
|
|
502
|
+
list or a one-dimensional variable-length abstraction is not enough.
|
|
503
|
+
|
|
504
|
+
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
505
|
+
|----------------------------|--------------:|---------------:|-----------:|----:|-----:|------:|
|
|
506
|
+
| zrecord-zstd | 2095 MiB/s | 5738 MiB/s | 34.6 | 8.17 ms | 27.1 MiB | 15.42x |
|
|
507
|
+
| zrecord-zstdict | 38 MiB/s | 6271 MiB/s | 37.8 | 7.64 ms | 17.8 MiB | 23.38x |
|
|
508
|
+
| zrecord-raw | 1493 MiB/s | 7017 MiB/s | 42.3 | 7.16 ms | 417.3 MiB | 1.00x |
|
|
509
|
+
| hdf5-raw | 1055 MiB/s | 1258 MiB/s | 7.6 | 40.55 ms | 418.0 MiB | 1.00x |
|
|
510
|
+
| hdf5-gzip | 243 MiB/s | 119 MiB/s | 0.7 | 406.67 ms | 30.0 MiB | 13.92x |
|
|
511
|
+
| lmdb-raw | 914 MiB/s | 6965 MiB/s | 42.0 | 6.63 ms | 422.2 MiB | 0.99x |
|
|
512
|
+
| arrow-ipc-raw | 1448 MiB/s | 3950 MiB/s | 23.8 | 13.03 ms | 417.2 MiB | 1.00x |
|
|
513
|
+
| arrow-ipc-zstd | 529 MiB/s | 181 MiB/s | 1.1 | 268.28 ms | 26.0 MiB | 16.02x |
|
|
514
|
+
| parquet-raw | 977 MiB/s | 466 MiB/s | 2.8 | 100.94 ms | 417.2 MiB | 1.00x |
|
|
515
|
+
| parquet-zstd | 510 MiB/s | 165 MiB/s | 1.0 | 288.08 ms | 26.0 MiB | 16.03x |
|
|
516
|
+
| arrayrecord-raw | 1417 MiB/s | 2612 MiB/s | 15.8 | 18.69 ms | 417.6 MiB | 1.00x |
|
|
517
|
+
| arrayrecord-zstd | 861 MiB/s | 1449 MiB/s | 8.7 | 39.32 ms | 27.3 MiB | 15.27x |
|
|
518
|
+
| tiledb-raw | 419 MiB/s | 50 MiB/s | 0.3 | 919.16 ms | 417.2 MiB | 1.00x |
|
|
519
|
+
| tiledb-zstd | 618 MiB/s | 124 MiB/s | 0.8 | 367.07 ms | 27.1 MiB | 15.40x |
|
|
520
|
+
|
|
521
|
+
LMDB is the fastest raw record contender and Arrow IPC is the strongest raw
|
|
522
|
+
typed-file alternative. Zrecord-zstd delivers 5.6 GiB/s of logical payload while
|
|
523
|
+
reducing the corpus to 27.1 MiB. HDF5, Arrow IPC, Parquet, ArrayRecord and TileDB
|
|
524
|
+
show the same framework/codec tradeoffs in both tables; compressed batch, chunk
|
|
525
|
+
and row-group formats pay read amplification on random records. Disk
|
|
494
526
|
includes shape records, both loc entries, codec frames, dictionaries, and global
|
|
495
|
-
headers
|
|
527
|
+
headers where the format uses them.
|
|
496
528
|
|
|
497
529
|
### DataLoader: Fixed-Shape Pipeline
|
|
498
530
|
|
|
@@ -529,7 +561,9 @@ and `--transform augment`); they are not part of the default `all`:
|
|
|
529
561
|
* **Sampler** — zsampler draws ~1.9–2x faster than free-threaded numpy (the
|
|
530
562
|
256-row run was a noisy 2.46x), the same broad margin as on the GIL build: a
|
|
531
563
|
batch is one Zig call either way.
|
|
532
|
-
* **Store** —
|
|
564
|
+
* **Historical Store snapshot** — these older rows predate the symmetric
|
|
565
|
+
benchmark matrix and are retained only for the GIL/free-threaded comparison.
|
|
566
|
+
The free-threaded numbers track the GIL table closely.
|
|
533
567
|
`zrecord-raw` gathers 8.9 vs 8.8 GiB/s and `zrecord-zstd` 4.7 vs 4.6 GiB/s.
|
|
534
568
|
The no-GIL table was run without blosc2 so its import could not change the
|
|
535
569
|
process mode. blosc2 was measured separately at 678 MiB/s; its extension
|
|
@@ -540,10 +574,10 @@ and `--transform augment`); they are not part of the default `all`:
|
|
|
540
574
|
|--------------|--------------------|---------------------|
|
|
541
575
|
| zrecord-raw | 8803 MiB/s | 8878 MiB/s |
|
|
542
576
|
| zrecord-zstd | 4603 MiB/s | 4707 MiB/s |
|
|
543
|
-
| npy-mmap
|
|
544
|
-
| hdf5
|
|
545
|
-
| blosc2
|
|
546
|
-
| tensorstore
|
|
577
|
+
| npy-mmap-raw | 4408 MiB/s | 3974 MiB/s |
|
|
578
|
+
| hdf5-raw | 2288 MiB/s | 2210 MiB/s |
|
|
579
|
+
| blosc2-zstd | 646 MiB/s | 678 MiB/s (GIL enabled) |
|
|
580
|
+
| tensorstore-zarr3-gzip | 299 MiB/s | 323 MiB/s |
|
|
547
581
|
* **Loader, identity** — unchanged on both interpreters at 64 MiB per batch:
|
|
548
582
|
|
|
549
583
|
| loader | CPython 3.14 (GIL) | free-threaded 3.14t |
|
|
@@ -685,6 +719,7 @@ explicit memory bound while zrecord independently caps filesystem writes.
|
|
|
685
719
|
zig build # host shared objects, into loaderx/lib/
|
|
686
720
|
zig build test # native store suite, in both Debug and ReleaseFast
|
|
687
721
|
python3 scripts/test_loaderx.py # Python integration suite against the real build
|
|
722
|
+
uv pip install -r scripts/requirements-bench.txt
|
|
688
723
|
python3 scripts/bench_dense.py # fixed-shape store comparison
|
|
689
724
|
python3 scripts/bench_ragged.py # variable-length store comparison
|
|
690
725
|
python3 scripts/bench.py # machine, sampler, and dense loader layers
|
|
@@ -27,6 +27,11 @@ loaderx is built around several core principles:
|
|
|
27
27
|
list. Neither is padded, and equal length is never treated as a special case
|
|
28
28
|
of variable length.
|
|
29
29
|
|
|
30
|
+
## 设计文档
|
|
31
|
+
|
|
32
|
+
- [性能优化与工程决策记录](docs/性能优化.md)
|
|
33
|
+
- [分布式架构草案](docs/分布式架构.md)
|
|
34
|
+
|
|
30
35
|
## Quick Start
|
|
31
36
|
```python
|
|
32
37
|
from loaderx.zrecord import DenseStore
|
|
@@ -350,19 +355,25 @@ For practical integration examples, please refer to the **[Data2Latent](https://
|
|
|
350
355
|
|
|
351
356
|
Dense and Ragged are measured separately because they expose different contracts
|
|
352
357
|
and have different useful metrics. `scripts/bench_dense.py` measures fixed-shape
|
|
353
|
-
random gather, `scripts/bench_ragged.py` measures variable-
|
|
358
|
+
random gather, `scripts/bench_ragged.py` measures variable-shape records, and
|
|
354
359
|
`scripts/bench.py` covers machine, sampler, and the original dense loader
|
|
355
360
|
comparison. Every path runs through the public Python binding, so CFFI, NumPy
|
|
356
361
|
allocation, and Ragged list/shape reconstruction are inside the timings.
|
|
357
362
|
|
|
358
363
|
### Methodology
|
|
359
364
|
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
365
|
+
The `1.7.7` Dense and Ragged snapshots below are each one qualitative pass on a
|
|
366
|
+
warm page cache. Within each benchmark, every backend receives
|
|
367
|
+
identical source records and random index plans. Before timing, the benchmark
|
|
368
|
+
sweeps every record and validates every planned result for exact
|
|
369
|
+
dtype, shape, order, and values. `logical write` and `logical gather` divide
|
|
370
|
+
uncompressed NumPy payload bytes by elapsed time; they measure bytes accepted or
|
|
371
|
+
returned by the public API, not physical storage bandwidth. Write is page-cache
|
|
372
|
+
ingestion with durability deferred consistently across backends; call `sync()`
|
|
373
|
+
explicitly when measuring Zrecord durability. Disk size is allocated blocks,
|
|
374
|
+
not sparse apparent size.
|
|
375
|
+
Every backend name states its actual codec; the full default set is required
|
|
376
|
+
rather than silently skipped when a package is missing.
|
|
366
377
|
|
|
367
378
|
**Machine** — one local workstation (AMD Ryzen AI 9 HX PRO 370, 12 cores / 24
|
|
368
379
|
threads):
|
|
@@ -406,46 +417,67 @@ comparison; other record sizes are a `--shape` away.
|
|
|
406
417
|
|
|
407
418
|
Realistic records — 256 KiB per record, 64 MiB per batch:
|
|
408
419
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
|
412
|
-
|
|
413
|
-
| zrecord-
|
|
414
|
-
|
|
|
415
|
-
|
|
|
416
|
-
|
|
|
417
|
-
|
|
|
420
|
+
Dense snapshot: `1.7.7`.
|
|
421
|
+
|
|
422
|
+
| store | logical write | logical gather | on disk | ratio |
|
|
423
|
+
|----------------------------|--------------:|---------------:|--------:|------:|
|
|
424
|
+
| zrecord-zstd | 1073 MiB/s | 4572 MiB/s | 555 MiB | 1.13x |
|
|
425
|
+
| zrecord-zstdict | 48 MiB/s | 2051 MiB/s | 388 MiB | 1.61x |
|
|
426
|
+
| zrecord-raw | 2325 MiB/s | 7996 MiB/s | 625 MiB | 1.00x |
|
|
427
|
+
| npy-mmap-raw | 2824 MiB/s | 3880 MiB/s | 625 MiB | 1.00x |
|
|
428
|
+
| hdf5-raw | 2385 MiB/s | 2155 MiB/s | 625 MiB | 1.00x |
|
|
429
|
+
| hdf5-gzip | 50 MiB/s | 234 MiB/s | 445 MiB | 1.40x |
|
|
430
|
+
| lmdb-raw | 794 MiB/s | 3406 MiB/s | 635 MiB | 0.98x |
|
|
431
|
+
| arrow-ipc-raw | 1657 MiB/s | 2601 MiB/s | 625 MiB | 1.00x |
|
|
432
|
+
| arrow-ipc-zstd | 311 MiB/s | 104 MiB/s | 418 MiB | 1.49x |
|
|
433
|
+
| parquet-raw | 1220 MiB/s | 442 MiB/s | 625 MiB | 1.00x |
|
|
434
|
+
| parquet-zstd | 311 MiB/s | 91 MiB/s | 418 MiB | 1.49x |
|
|
435
|
+
| arrayrecord-raw | 1878 MiB/s | 1873 MiB/s | 625 MiB | 1.00x |
|
|
436
|
+
| arrayrecord-zstd | 481 MiB/s | 1530 MiB/s | 567 MiB | 1.10x |
|
|
437
|
+
| tiledb-raw | 818 MiB/s | 780 MiB/s | 625 MiB | 1.00x |
|
|
438
|
+
| tiledb-zstd | 812 MiB/s | 811 MiB/s | 553 MiB | 1.13x |
|
|
418
439
|
|
|
419
440
|
At 256 KiB per record, a 64 MiB batch exceeds L3 and the raw paths are primarily
|
|
420
|
-
DRAM-bandwidth-bound. Zrecord-raw reaches 7.
|
|
421
|
-
plain zstd gathers at
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
|
437
|
-
|
|
438
|
-
|
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
441
|
+
DRAM-bandwidth-bound. Zrecord-raw reaches 7.8 GiB/s and is 2.1x npy-mmap-raw here;
|
|
442
|
+
plain zstd gathers at 4.5 GiB/s. LMDB and Arrow IPC are competitive raw record
|
|
443
|
+
stores, while codecs tied to whole IPC batches or Parquet row groups pay read
|
|
444
|
+
amplification on random gathers. DenseStore demonstrates that
|
|
445
|
+
typed record ownership and per-record compression do not turn fixed tensors
|
|
446
|
+
into an object-store slow path.
|
|
447
|
+
|
|
448
|
+
### RaggedStore: Variable-Shape Records
|
|
449
|
+
|
|
450
|
+
This workload contains 2,500 variable-resolution CHW RGB images. Height and
|
|
451
|
+
width are independently lognormal and clipped to 64..512 (observed medians
|
|
452
|
+
223 and 224), totaling 417.1 MiB of logical uint8 payload. Each of 50 random
|
|
453
|
+
batches contains 256 records. Every backend persists payload plus exact shape
|
|
454
|
+
and must return an ordered `list[np.ndarray]` of `(3, H, W)` arrays; a flat byte
|
|
455
|
+
list or a one-dimensional variable-length abstraction is not enough.
|
|
456
|
+
|
|
457
|
+
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
458
|
+
|----------------------------|--------------:|---------------:|-----------:|----:|-----:|------:|
|
|
459
|
+
| zrecord-zstd | 2095 MiB/s | 5738 MiB/s | 34.6 | 8.17 ms | 27.1 MiB | 15.42x |
|
|
460
|
+
| zrecord-zstdict | 38 MiB/s | 6271 MiB/s | 37.8 | 7.64 ms | 17.8 MiB | 23.38x |
|
|
461
|
+
| zrecord-raw | 1493 MiB/s | 7017 MiB/s | 42.3 | 7.16 ms | 417.3 MiB | 1.00x |
|
|
462
|
+
| hdf5-raw | 1055 MiB/s | 1258 MiB/s | 7.6 | 40.55 ms | 418.0 MiB | 1.00x |
|
|
463
|
+
| hdf5-gzip | 243 MiB/s | 119 MiB/s | 0.7 | 406.67 ms | 30.0 MiB | 13.92x |
|
|
464
|
+
| lmdb-raw | 914 MiB/s | 6965 MiB/s | 42.0 | 6.63 ms | 422.2 MiB | 0.99x |
|
|
465
|
+
| arrow-ipc-raw | 1448 MiB/s | 3950 MiB/s | 23.8 | 13.03 ms | 417.2 MiB | 1.00x |
|
|
466
|
+
| arrow-ipc-zstd | 529 MiB/s | 181 MiB/s | 1.1 | 268.28 ms | 26.0 MiB | 16.02x |
|
|
467
|
+
| parquet-raw | 977 MiB/s | 466 MiB/s | 2.8 | 100.94 ms | 417.2 MiB | 1.00x |
|
|
468
|
+
| parquet-zstd | 510 MiB/s | 165 MiB/s | 1.0 | 288.08 ms | 26.0 MiB | 16.03x |
|
|
469
|
+
| arrayrecord-raw | 1417 MiB/s | 2612 MiB/s | 15.8 | 18.69 ms | 417.6 MiB | 1.00x |
|
|
470
|
+
| arrayrecord-zstd | 861 MiB/s | 1449 MiB/s | 8.7 | 39.32 ms | 27.3 MiB | 15.27x |
|
|
471
|
+
| tiledb-raw | 419 MiB/s | 50 MiB/s | 0.3 | 919.16 ms | 417.2 MiB | 1.00x |
|
|
472
|
+
| tiledb-zstd | 618 MiB/s | 124 MiB/s | 0.8 | 367.07 ms | 27.1 MiB | 15.40x |
|
|
473
|
+
|
|
474
|
+
LMDB is the fastest raw record contender and Arrow IPC is the strongest raw
|
|
475
|
+
typed-file alternative. Zrecord-zstd delivers 5.6 GiB/s of logical payload while
|
|
476
|
+
reducing the corpus to 27.1 MiB. HDF5, Arrow IPC, Parquet, ArrayRecord and TileDB
|
|
477
|
+
show the same framework/codec tradeoffs in both tables; compressed batch, chunk
|
|
478
|
+
and row-group formats pay read amplification on random records. Disk
|
|
447
479
|
includes shape records, both loc entries, codec frames, dictionaries, and global
|
|
448
|
-
headers
|
|
480
|
+
headers where the format uses them.
|
|
449
481
|
|
|
450
482
|
### DataLoader: Fixed-Shape Pipeline
|
|
451
483
|
|
|
@@ -482,7 +514,9 @@ and `--transform augment`); they are not part of the default `all`:
|
|
|
482
514
|
* **Sampler** — zsampler draws ~1.9–2x faster than free-threaded numpy (the
|
|
483
515
|
256-row run was a noisy 2.46x), the same broad margin as on the GIL build: a
|
|
484
516
|
batch is one Zig call either way.
|
|
485
|
-
* **Store** —
|
|
517
|
+
* **Historical Store snapshot** — these older rows predate the symmetric
|
|
518
|
+
benchmark matrix and are retained only for the GIL/free-threaded comparison.
|
|
519
|
+
The free-threaded numbers track the GIL table closely.
|
|
486
520
|
`zrecord-raw` gathers 8.9 vs 8.8 GiB/s and `zrecord-zstd` 4.7 vs 4.6 GiB/s.
|
|
487
521
|
The no-GIL table was run without blosc2 so its import could not change the
|
|
488
522
|
process mode. blosc2 was measured separately at 678 MiB/s; its extension
|
|
@@ -493,10 +527,10 @@ and `--transform augment`); they are not part of the default `all`:
|
|
|
493
527
|
|--------------|--------------------|---------------------|
|
|
494
528
|
| zrecord-raw | 8803 MiB/s | 8878 MiB/s |
|
|
495
529
|
| zrecord-zstd | 4603 MiB/s | 4707 MiB/s |
|
|
496
|
-
| npy-mmap
|
|
497
|
-
| hdf5
|
|
498
|
-
| blosc2
|
|
499
|
-
| tensorstore
|
|
530
|
+
| npy-mmap-raw | 4408 MiB/s | 3974 MiB/s |
|
|
531
|
+
| hdf5-raw | 2288 MiB/s | 2210 MiB/s |
|
|
532
|
+
| blosc2-zstd | 646 MiB/s | 678 MiB/s (GIL enabled) |
|
|
533
|
+
| tensorstore-zarr3-gzip | 299 MiB/s | 323 MiB/s |
|
|
500
534
|
* **Loader, identity** — unchanged on both interpreters at 64 MiB per batch:
|
|
501
535
|
|
|
502
536
|
| loader | CPython 3.14 (GIL) | free-threaded 3.14t |
|
|
@@ -638,6 +672,7 @@ explicit memory bound while zrecord independently caps filesystem writes.
|
|
|
638
672
|
zig build # host shared objects, into loaderx/lib/
|
|
639
673
|
zig build test # native store suite, in both Debug and ReleaseFast
|
|
640
674
|
python3 scripts/test_loaderx.py # Python integration suite against the real build
|
|
675
|
+
uv pip install -r scripts/requirements-bench.txt
|
|
641
676
|
python3 scripts/bench_dense.py # fixed-shape store comparison
|
|
642
677
|
python3 scripts/bench_ragged.py # variable-length store comparison
|
|
643
678
|
python3 scripts/bench.py # machine, sampler, and dense loader layers
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loaderx
|
|
3
|
-
Version: 1.7.
|
|
3
|
+
Version: 1.7.7
|
|
4
4
|
Summary: Zrecord typed persistent stores and the Loaderx data loader for single-machine AI pipelines
|
|
5
5
|
Author-email: Ben0i0d <ben0i0d@foxmail.com>
|
|
6
6
|
License: MIT License
|
|
@@ -74,6 +74,11 @@ loaderx is built around several core principles:
|
|
|
74
74
|
list. Neither is padded, and equal length is never treated as a special case
|
|
75
75
|
of variable length.
|
|
76
76
|
|
|
77
|
+
## 设计文档
|
|
78
|
+
|
|
79
|
+
- [性能优化与工程决策记录](docs/性能优化.md)
|
|
80
|
+
- [分布式架构草案](docs/分布式架构.md)
|
|
81
|
+
|
|
77
82
|
## Quick Start
|
|
78
83
|
```python
|
|
79
84
|
from loaderx.zrecord import DenseStore
|
|
@@ -397,19 +402,25 @@ For practical integration examples, please refer to the **[Data2Latent](https://
|
|
|
397
402
|
|
|
398
403
|
Dense and Ragged are measured separately because they expose different contracts
|
|
399
404
|
and have different useful metrics. `scripts/bench_dense.py` measures fixed-shape
|
|
400
|
-
random gather, `scripts/bench_ragged.py` measures variable-
|
|
405
|
+
random gather, `scripts/bench_ragged.py` measures variable-shape records, and
|
|
401
406
|
`scripts/bench.py` covers machine, sampler, and the original dense loader
|
|
402
407
|
comparison. Every path runs through the public Python binding, so CFFI, NumPy
|
|
403
408
|
allocation, and Ragged list/shape reconstruction are inside the timings.
|
|
404
409
|
|
|
405
410
|
### Methodology
|
|
406
411
|
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
412
|
+
The `1.7.7` Dense and Ragged snapshots below are each one qualitative pass on a
|
|
413
|
+
warm page cache. Within each benchmark, every backend receives
|
|
414
|
+
identical source records and random index plans. Before timing, the benchmark
|
|
415
|
+
sweeps every record and validates every planned result for exact
|
|
416
|
+
dtype, shape, order, and values. `logical write` and `logical gather` divide
|
|
417
|
+
uncompressed NumPy payload bytes by elapsed time; they measure bytes accepted or
|
|
418
|
+
returned by the public API, not physical storage bandwidth. Write is page-cache
|
|
419
|
+
ingestion with durability deferred consistently across backends; call `sync()`
|
|
420
|
+
explicitly when measuring Zrecord durability. Disk size is allocated blocks,
|
|
421
|
+
not sparse apparent size.
|
|
422
|
+
Every backend name states its actual codec; the full default set is required
|
|
423
|
+
rather than silently skipped when a package is missing.
|
|
413
424
|
|
|
414
425
|
**Machine** — one local workstation (AMD Ryzen AI 9 HX PRO 370, 12 cores / 24
|
|
415
426
|
threads):
|
|
@@ -453,46 +464,67 @@ comparison; other record sizes are a `--shape` away.
|
|
|
453
464
|
|
|
454
465
|
Realistic records — 256 KiB per record, 64 MiB per batch:
|
|
455
466
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
|
459
|
-
|
|
460
|
-
| zrecord-
|
|
461
|
-
|
|
|
462
|
-
|
|
|
463
|
-
|
|
|
464
|
-
|
|
|
467
|
+
Dense snapshot: `1.7.7`.
|
|
468
|
+
|
|
469
|
+
| store | logical write | logical gather | on disk | ratio |
|
|
470
|
+
|----------------------------|--------------:|---------------:|--------:|------:|
|
|
471
|
+
| zrecord-zstd | 1073 MiB/s | 4572 MiB/s | 555 MiB | 1.13x |
|
|
472
|
+
| zrecord-zstdict | 48 MiB/s | 2051 MiB/s | 388 MiB | 1.61x |
|
|
473
|
+
| zrecord-raw | 2325 MiB/s | 7996 MiB/s | 625 MiB | 1.00x |
|
|
474
|
+
| npy-mmap-raw | 2824 MiB/s | 3880 MiB/s | 625 MiB | 1.00x |
|
|
475
|
+
| hdf5-raw | 2385 MiB/s | 2155 MiB/s | 625 MiB | 1.00x |
|
|
476
|
+
| hdf5-gzip | 50 MiB/s | 234 MiB/s | 445 MiB | 1.40x |
|
|
477
|
+
| lmdb-raw | 794 MiB/s | 3406 MiB/s | 635 MiB | 0.98x |
|
|
478
|
+
| arrow-ipc-raw | 1657 MiB/s | 2601 MiB/s | 625 MiB | 1.00x |
|
|
479
|
+
| arrow-ipc-zstd | 311 MiB/s | 104 MiB/s | 418 MiB | 1.49x |
|
|
480
|
+
| parquet-raw | 1220 MiB/s | 442 MiB/s | 625 MiB | 1.00x |
|
|
481
|
+
| parquet-zstd | 311 MiB/s | 91 MiB/s | 418 MiB | 1.49x |
|
|
482
|
+
| arrayrecord-raw | 1878 MiB/s | 1873 MiB/s | 625 MiB | 1.00x |
|
|
483
|
+
| arrayrecord-zstd | 481 MiB/s | 1530 MiB/s | 567 MiB | 1.10x |
|
|
484
|
+
| tiledb-raw | 818 MiB/s | 780 MiB/s | 625 MiB | 1.00x |
|
|
485
|
+
| tiledb-zstd | 812 MiB/s | 811 MiB/s | 553 MiB | 1.13x |
|
|
465
486
|
|
|
466
487
|
At 256 KiB per record, a 64 MiB batch exceeds L3 and the raw paths are primarily
|
|
467
|
-
DRAM-bandwidth-bound. Zrecord-raw reaches 7.
|
|
468
|
-
plain zstd gathers at
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
|
484
|
-
|
|
485
|
-
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
488
|
+
DRAM-bandwidth-bound. Zrecord-raw reaches 7.8 GiB/s and is 2.1x npy-mmap-raw here;
|
|
489
|
+
plain zstd gathers at 4.5 GiB/s. LMDB and Arrow IPC are competitive raw record
|
|
490
|
+
stores, while codecs tied to whole IPC batches or Parquet row groups pay read
|
|
491
|
+
amplification on random gathers. DenseStore demonstrates that
|
|
492
|
+
typed record ownership and per-record compression do not turn fixed tensors
|
|
493
|
+
into an object-store slow path.
|
|
494
|
+
|
|
495
|
+
### RaggedStore: Variable-Shape Records
|
|
496
|
+
|
|
497
|
+
This workload contains 2,500 variable-resolution CHW RGB images. Height and
|
|
498
|
+
width are independently lognormal and clipped to 64..512 (observed medians
|
|
499
|
+
223 and 224), totaling 417.1 MiB of logical uint8 payload. Each of 50 random
|
|
500
|
+
batches contains 256 records. Every backend persists payload plus exact shape
|
|
501
|
+
and must return an ordered `list[np.ndarray]` of `(3, H, W)` arrays; a flat byte
|
|
502
|
+
list or a one-dimensional variable-length abstraction is not enough.
|
|
503
|
+
|
|
504
|
+
| backend | logical write | logical gather | krecords/s | p95 | disk | ratio |
|
|
505
|
+
|----------------------------|--------------:|---------------:|-----------:|----:|-----:|------:|
|
|
506
|
+
| zrecord-zstd | 2095 MiB/s | 5738 MiB/s | 34.6 | 8.17 ms | 27.1 MiB | 15.42x |
|
|
507
|
+
| zrecord-zstdict | 38 MiB/s | 6271 MiB/s | 37.8 | 7.64 ms | 17.8 MiB | 23.38x |
|
|
508
|
+
| zrecord-raw | 1493 MiB/s | 7017 MiB/s | 42.3 | 7.16 ms | 417.3 MiB | 1.00x |
|
|
509
|
+
| hdf5-raw | 1055 MiB/s | 1258 MiB/s | 7.6 | 40.55 ms | 418.0 MiB | 1.00x |
|
|
510
|
+
| hdf5-gzip | 243 MiB/s | 119 MiB/s | 0.7 | 406.67 ms | 30.0 MiB | 13.92x |
|
|
511
|
+
| lmdb-raw | 914 MiB/s | 6965 MiB/s | 42.0 | 6.63 ms | 422.2 MiB | 0.99x |
|
|
512
|
+
| arrow-ipc-raw | 1448 MiB/s | 3950 MiB/s | 23.8 | 13.03 ms | 417.2 MiB | 1.00x |
|
|
513
|
+
| arrow-ipc-zstd | 529 MiB/s | 181 MiB/s | 1.1 | 268.28 ms | 26.0 MiB | 16.02x |
|
|
514
|
+
| parquet-raw | 977 MiB/s | 466 MiB/s | 2.8 | 100.94 ms | 417.2 MiB | 1.00x |
|
|
515
|
+
| parquet-zstd | 510 MiB/s | 165 MiB/s | 1.0 | 288.08 ms | 26.0 MiB | 16.03x |
|
|
516
|
+
| arrayrecord-raw | 1417 MiB/s | 2612 MiB/s | 15.8 | 18.69 ms | 417.6 MiB | 1.00x |
|
|
517
|
+
| arrayrecord-zstd | 861 MiB/s | 1449 MiB/s | 8.7 | 39.32 ms | 27.3 MiB | 15.27x |
|
|
518
|
+
| tiledb-raw | 419 MiB/s | 50 MiB/s | 0.3 | 919.16 ms | 417.2 MiB | 1.00x |
|
|
519
|
+
| tiledb-zstd | 618 MiB/s | 124 MiB/s | 0.8 | 367.07 ms | 27.1 MiB | 15.40x |
|
|
520
|
+
|
|
521
|
+
LMDB is the fastest raw record contender and Arrow IPC is the strongest raw
|
|
522
|
+
typed-file alternative. Zrecord-zstd delivers 5.6 GiB/s of logical payload while
|
|
523
|
+
reducing the corpus to 27.1 MiB. HDF5, Arrow IPC, Parquet, ArrayRecord and TileDB
|
|
524
|
+
show the same framework/codec tradeoffs in both tables; compressed batch, chunk
|
|
525
|
+
and row-group formats pay read amplification on random records. Disk
|
|
494
526
|
includes shape records, both loc entries, codec frames, dictionaries, and global
|
|
495
|
-
headers
|
|
527
|
+
headers where the format uses them.
|
|
496
528
|
|
|
497
529
|
### DataLoader: Fixed-Shape Pipeline
|
|
498
530
|
|
|
@@ -529,7 +561,9 @@ and `--transform augment`); they are not part of the default `all`:
|
|
|
529
561
|
* **Sampler** — zsampler draws ~1.9–2x faster than free-threaded numpy (the
|
|
530
562
|
256-row run was a noisy 2.46x), the same broad margin as on the GIL build: a
|
|
531
563
|
batch is one Zig call either way.
|
|
532
|
-
* **Store** —
|
|
564
|
+
* **Historical Store snapshot** — these older rows predate the symmetric
|
|
565
|
+
benchmark matrix and are retained only for the GIL/free-threaded comparison.
|
|
566
|
+
The free-threaded numbers track the GIL table closely.
|
|
533
567
|
`zrecord-raw` gathers 8.9 vs 8.8 GiB/s and `zrecord-zstd` 4.7 vs 4.6 GiB/s.
|
|
534
568
|
The no-GIL table was run without blosc2 so its import could not change the
|
|
535
569
|
process mode. blosc2 was measured separately at 678 MiB/s; its extension
|
|
@@ -540,10 +574,10 @@ and `--transform augment`); they are not part of the default `all`:
|
|
|
540
574
|
|--------------|--------------------|---------------------|
|
|
541
575
|
| zrecord-raw | 8803 MiB/s | 8878 MiB/s |
|
|
542
576
|
| zrecord-zstd | 4603 MiB/s | 4707 MiB/s |
|
|
543
|
-
| npy-mmap
|
|
544
|
-
| hdf5
|
|
545
|
-
| blosc2
|
|
546
|
-
| tensorstore
|
|
577
|
+
| npy-mmap-raw | 4408 MiB/s | 3974 MiB/s |
|
|
578
|
+
| hdf5-raw | 2288 MiB/s | 2210 MiB/s |
|
|
579
|
+
| blosc2-zstd | 646 MiB/s | 678 MiB/s (GIL enabled) |
|
|
580
|
+
| tensorstore-zarr3-gzip | 299 MiB/s | 323 MiB/s |
|
|
547
581
|
* **Loader, identity** — unchanged on both interpreters at 64 MiB per batch:
|
|
548
582
|
|
|
549
583
|
| loader | CPython 3.14 (GIL) | free-threaded 3.14t |
|
|
@@ -685,6 +719,7 @@ explicit memory bound while zrecord independently caps filesystem writes.
|
|
|
685
719
|
zig build # host shared objects, into loaderx/lib/
|
|
686
720
|
zig build test # native store suite, in both Debug and ReleaseFast
|
|
687
721
|
python3 scripts/test_loaderx.py # Python integration suite against the real build
|
|
722
|
+
uv pip install -r scripts/requirements-bench.txt
|
|
688
723
|
python3 scripts/bench_dense.py # fixed-shape store comparison
|
|
689
724
|
python3 scripts/bench_ragged.py # variable-length store comparison
|
|
690
725
|
python3 scripts/bench.py # machine, sampler, and dense loader layers
|