Skip to main content

Pure in-memory ZPAQ compression for Python (real pybind11 bindings, prebuilt wheels, no C++ toolchain needed to install).

Project description

zpaq

pypi Downloads github stars

Pure in-memory ZPAQ compression for Python. I made this because every other zpaq package on PyPI is just a wrapper around the zpaq executable, they shell out to the CLI as a subprocess, which means temp files for every operation, fork overhead, and the user needing zpaq.exe on their PATH in the first place. None of them are actual bindings. I wanted real bytes->bytes zpaq from Python that just works.

import zpaq

blob = zpaq.compress(b"hello world " * 1_000, level=3)
assert zpaq.decompress(blob) == b"hello world " * 1_000

It also ended up alot faster than the official zpaq.exe itself, up to 6.6× faster on decompress and 6.3× faster on compress at 10 MB, with the same compression ratio. Multi-threaded both directions, JIT-compiled predictor on x86_64, libsais for the suffix-array pass, AVX2 auto-vec compile flag. Default threads=0 auto-scales across all CPU cores. Pass threads=1 if you want the absolute best ratio:

blob = zpaq.compress(big_data, level=5, threads=1)   # max ratio, single block

For inputs with repeated content (logs, large text corpora, similar binaries, snapshots), pass dedup=True to get fragment-level deduplication, input gets split into ~64 KB content-defined chunks and identical chunks are stored once. Matches what the zpaq a CLI produces, so the output is fully zpaq x extractable:

blob = zpaq.compress(repetitive_data, level=5, dedup=True)   # JIDAC archive

Prebuilt wheels for Windows / Linux / macOS (including Apple Silicon) across Python 3.9 through 3.13. Installing it never compiles anything. On Windows the wheel statically links the C and C++ runtimes so there's no "Visual C++ Redistributable" requirement, if Python runs, zpaq works.

Performance

benchmark

Benchmarks vs the official zpaq.exe -m5 (Ryzen-class 12-core x86_64, level 5). The mem binding wins both compress and decompress at every size, and dedup=True matches or beats the CLI on compression ratio at every scale:

workload CLI comp best mem comp CLI decomp best mem decomp mem dedup ratio vs CLI
1 MB text 2.25 s 0.56 s (4.0×) 2.23 s 0.64 s (3.5×) +0.013 pp
10 MB text 24.67 s 4.36 s (5.7×) 24.74 s 4.29 s (5.8×) +0.001 pp
100 MB text 184.36 s 55.73 s (3.3×) 185.32 s 56.02 s (3.3×) +0.078 pp (dedup) / tie (t=1)

Full breakdown by thread count below. CLI is the official zpaq.exe v7.15 invoked with -m5 (its speeds already include the -t0 default of two worker threads). mem(t=N) is zpaq.compress(data, level=5, threads=N). Times in seconds; ratio is bytes-reduced over original.

1 MB text:

algo compress decompress ratio %
zpaq.exe -m5 2.25 s 2.23 s 79.963 %
zpaq.compress(t=1) 2.15 s 2.21 s 80.073 %
zpaq.compress(dedup=True) 2.18 s 2.16 s 79.976 %
zpaq.compress(t=0) (12 cores) 0.56 s 0.64 s 77.547 %

10 MB text:

algo compress decompress ratio %
zpaq.exe -m5 24.67 s 24.74 s 84.161 %
zpaq.compress(t=1) 24.76 s 25.15 s 84.206 %
zpaq.compress(dedup=True) 24.24 s 26.67 s 84.162 %
zpaq.compress(t=0) (12 cores) 4.36 s 4.29 s 81.213 %

100 MB text:

algo compress decompress ratio %
zpaq.exe -m5 184.36 s 185.32 s 86.74 %
zpaq.compress(t=1) 279.35 s 173.33 s 86.72 %
zpaq.compress(dedup=True) 180.19 s 184.52 s 86.666 %
zpaq.compress(t=0) (12 cores) 55.73 s 56.02 s 84.56 %

Why this is faster than the official CLI

libzpaq's reference compiler ships an interpreter for the per-byte context-mixing predictor used at compression levels 3-5. The official zpaq.exe on x86_64 ships with that interpreter replaced by a JIT that translates the predictor bytecode into native machine code at archive-open time, that's where most of its speed comes from. This package's x86_64 wheels enable that same JIT path, plus a handful of additions the CLI doesn't have:

  • multi-threaded block compression via threads=N (the official CLI tops out at two cores by default)
  • multi-threaded block decompression, I scan the archive for ZPAQ locator-tag block boundaries up front, dispatch each block to a worker, and concatenate. libzpaq's decompress API is sequential, so this layer sits above it.
  • skip-checksum-by-default (verify=False); the SHA-1 per block that zpaq.exe always computes isn't free, and most "compress these bytes please" workflows don't need it
  • AVX2-enabled compile flags so the optimizer auto-vectorizes where it can (x86_64 wheels assume AVX2; CPUs from 2013+ are covered, anything older falls back to the sdist build)
  • libsais (Apache 2.0) for level-3 BWT suffix-array construction instead of the libdivsufsort-lite that libzpaq ships internally
  • a faster decompress path for archives produced by zpaq.compress that skips the JIDAC-aware per-segment buffering

Compress scales nearly linearly with thread count up to ~12 cores. Compression ratio drops slightly as threads increase (more block boundaries = less context per block); the ratio at t=1 matches or beats the CLI on every workload. For inputs with actual repetition dedup=True closes the remaining gap.

ARM / Apple Silicon wheels disable the x86-only JIT and AVX2 flags but still benefit from threading, libsais, and the fast decompress path.

API

zpaq.compress(
    data,                    # bytes-like
    level=5,                 # 0..5 (0=store, 5=strongest)
    threads=0,               # 0 (default) = auto-detect host CPU count, clamped
                             # by input size (64KB minimum chunk per worker).
                             # 1 = single-thread, deterministic, best ratio.
                             # N>1 = pin to exactly N workers.
    hints=False,             # If True, scan input for text/exe signatures + order-1
                             # redundancy, pass them to libzpaq via the method string.
                             # Slight overhead, can help ratio on mixed/binary data.
    verify=False,            # If True, compute & embed SHA-1 per segment.
                             # zpaq.exe also writes these by default; turning them off
                             # makes both us and zpaq.exe skip verification on extract.
    method=None,             # Optional raw libzpaq method-string override (e.g.
                             # "x4,4,1"). Overrides level/hints when set.
    dedup=False,             # If True, emit a JIDAC-format archive with fragment
                             # dedup. Output is `zpaq x`-extractable. Currently
                             # single-threaded encode; matches CLI ratio on repetitive
                             # inputs.
) -> bytes

zpaq.decompress(
    data,                    # bytes-like ZPAQ stream
    verify=False,            # If True, recompute SHA-1 of each segment and compare
                             # to the one in the archive. Raises zpaq.Error on
                             # mismatch.
    threads=0,               # 0 (default) = auto-detect, clamped by block count.
                             # 1 = single-thread.
) -> bytes

zpaq.Error                   # Raised on libzpaq failures (corrupt stream, bad header)

Both compress and decompress release the GIL while libzpaq runs, so this plays well with threaded workloads.

CLI interoperability

zpaq.compress() emits the same on-disk format libzpaq itself writes, and zpaq.decompress() understands archives produced by zpaq a (filters out the JIDAC index/hash/info segments, follows the file's fragment-ID list, etc.). Tested on ten varied real files (1 KB to 25 MB, text/image/csv/jar/png/jpg/svg/exe/binary, levels 1-5):

Direction Result
zpaq.compresszpaq.decompress 10 / 10 byte-exact
zpaq.compresszpaq x (official CLI) 10 / 10 byte-exact
zpaq a (official CLI) → zpaq.decompress 10 / 10 byte-exact
import zpaq

# Pipe to the official CLI
with open("out.zpaq", "wb") as f:
    f.write(zpaq.compress(my_bytes, level=5))
# ... later, from any machine with the zpaq executable installed:
#   $ zpaq x out.zpaq

# Read an archive someone else produced with `zpaq a`
with open("their.zpaq", "rb") as f:
    file_bytes = zpaq.decompress(f.read())

For multi-file zpaq a archives, zpaq.decompress currently returns the concatenated bytes of every file in storage order. A per-segment iterator API for addressing files by name is on the v0.3 list.

Future work

Plenty of levers I haven't pulled yet, PRs welcome:

  • PGO (profile-guided optimization). Adding /GENPROFILE + /USEPROFILE to the MSVC build (and the gcc/clang equivalent) usually adds another 5-15%. Skipped here because cibuildwheel doesn't expose a clean two-stage build hook yet.
  • AVX2 in the JIT predictor. AVX2 is on at the C++ compile-flag level. The JIT-emitted predictor already uses SSE2 SIMD (pmaddwd / paddd for the MIX dot-product, lines 4126-4170 in vendored libzpaq.cpp). Upgrading the JIT codegen to AVX2 256-bit ymm registers would handle 16 mixer inputs per iteration instead of 8, but real gain depends heavily on the per-method MIX m parameter, and the work is byte-level instruction re-encoding which is fragile. Skipped for now.
  • Smaller-chunk dedup. The current 64KB content-defined chunk size matches the official CLI for full interop, but smaller chunks (16-32KB) would find more duplicates on similar-but-not-identical files (e.g. log streams, snapshot diffs) at the cost of more index overhead.
  • Per-segment archive API. As mentioned above, let callers address individual files inside multi-file zpaq a archives by name.

License

Released under the same terms as the underlying libzpaq sources: public domain. See src/zpaq/vendor/COPYING.

The vendored libsais suffix-array library is Apache 2.0 (Ilya Grebnov). See src/zpaq/vendor/LICENSE-libsais.


Not affiliated with Matt Mahoney. libzpaq was released into the public domain by its original author; this Python package wraps those sources and is an independent community project.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zpaq-0.3.7.tar.gz (156.2 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

zpaq-0.3.7-cp313-cp313-musllinux_1_2_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.13musllinux: musl 1.2+ x86-64

zpaq-0.3.7-cp313-cp313-manylinux_2_34_x86_64.whl (410.1 kB view details)

Uploaded CPython 3.13manylinux: glibc 2.34+ x86-64

zpaq-0.3.7-cp312-cp312-musllinux_1_2_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.12musllinux: musl 1.2+ x86-64

zpaq-0.3.7-cp312-cp312-manylinux_2_34_x86_64.whl (410.0 kB view details)

Uploaded CPython 3.12manylinux: glibc 2.34+ x86-64

zpaq-0.3.7-cp311-cp311-win_amd64.whl (417.9 kB view details)

Uploaded CPython 3.11Windows x86-64

zpaq-0.3.7-cp311-cp311-musllinux_1_2_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.11musllinux: musl 1.2+ x86-64

zpaq-0.3.7-cp311-cp311-manylinux_2_34_x86_64.whl (407.6 kB view details)

Uploaded CPython 3.11manylinux: glibc 2.34+ x86-64

zpaq-0.3.7-cp310-cp310-musllinux_1_2_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.10musllinux: musl 1.2+ x86-64

zpaq-0.3.7-cp310-cp310-manylinux_2_34_x86_64.whl (406.5 kB view details)

Uploaded CPython 3.10manylinux: glibc 2.34+ x86-64

zpaq-0.3.7-cp39-cp39-musllinux_1_2_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.9musllinux: musl 1.2+ x86-64

zpaq-0.3.7-cp39-cp39-manylinux_2_34_x86_64.whl (406.9 kB view details)

Uploaded CPython 3.9manylinux: glibc 2.34+ x86-64

File details

Details for the file zpaq-0.3.7.tar.gz.

File metadata

  • Download URL: zpaq-0.3.7.tar.gz
  • Upload date:
  • Size: 156.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for zpaq-0.3.7.tar.gz
Algorithm Hash digest
SHA256 108c2aef85d58f16c59f6f197802480f3c8370e631871af793a6b713f10b60c4
MD5 0e3da00cc8cc174c3e0279500239be5b
BLAKE2b-256 0c73fe5386a5c0f79444e9ac2cbd1752d31ce6a6b84820b9962bb172b2c8df37

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp313-cp313-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp313-cp313-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 714d82cc8b6603ad66892fd99669dd14076aa90cc582f18c7979b3132f3f6af8
MD5 da435cc7baf70fc0f4d0815c5f39ba57
BLAKE2b-256 5907e8586ccd3213c406aa40eb496470444add0979fff0a7c33ff9c8d793dbe3

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp313-cp313-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp313-cp313-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 f9164059043fa6ce3b5ac64d194753cb50a1a41015f5d10ced54b968f51d6b26
MD5 6b2af07cbd97d864bb259c026300a367
BLAKE2b-256 1983dd6b97a8de4f5f1b63f47be21c6599346630141cf5ebdf6f9b87fb8f1965

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp312-cp312-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp312-cp312-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 aa2c271617ea6e1a707f9b8c569a1d0f3f33cfeeffae2f50ec5ad098683c00b7
MD5 43a67bb40c8c540aadf2eb8b8059c4dd
BLAKE2b-256 87b07b1743fe608fd1723af0ed72d457107ec3775f968d05eddc9f57e65048eb

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp312-cp312-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp312-cp312-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 c6c204ae25bb3fb93c37da7c4de457b88e1d1e8a833d35804d174fdabb429ac1
MD5 9c7b3a75106402bcc9337f6957624839
BLAKE2b-256 428e7c069325bab6c67ef6dfb519ab240000c2f0e4fe22b5a5d219c40f5363f6

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: zpaq-0.3.7-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 417.9 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.9

File hashes

Hashes for zpaq-0.3.7-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 1c14f97d5c719a5054d23a65c5bc6f7a7cd5745e9f01d863c66cb7365595bc1f
MD5 5ee4a6c581b26067892ecfe180cf34a3
BLAKE2b-256 1cb4c56e2c64daf8cbe449c400263557314e2aed73bfb49ed2c8f11f1d99a281

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp311-cp311-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp311-cp311-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 c2fe997cb19a694e09b48b6631a43351e65db60903d48b313c8a38173523e7c4
MD5 bb0df851f251e3ec57d066c315fe23fa
BLAKE2b-256 5cf51e35ee17ca3e972263672a56e7d3927520c3d5e2c704f3c69ede10eee3c2

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp311-cp311-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp311-cp311-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 0644a92f0d04288089c95ac71dc0a06d005ae85753e2946a7e37a88a8d2c44c6
MD5 42e5eeaac3d64f15b76357929745a9f6
BLAKE2b-256 9ad39a0df688e3f67e095902e3b7e14be14d749916f254d3cea95dc10f08ac60

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp310-cp310-musllinux_1_2_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp310-cp310-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 19bd5baf3696ef780ad769109f4f01949f0c8d8f8b7c6af39e9d65f63fd0472f
MD5 7920ed0f892d63e1ebca49f838432d81
BLAKE2b-256 4362b92b48041295bed4e4e8b1c24dd0e956cc2d9af41151fe3b21ef45511531

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp310-cp310-manylinux_2_34_x86_64.whl.

File metadata

File hashes

Hashes for zpaq-0.3.7-cp310-cp310-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 bb017cd31b9892b9f8531b4c5c756a93312c26c530f43435bbe5dd08d5f023a2
MD5 ab36a3023c297d85463d0ac264b35b7f
BLAKE2b-256 288926a5055cc3351ddab0f25f9da6e5f1e08844368f1e0be20035c08dd3ef1e

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp39-cp39-musllinux_1_2_x86_64.whl.

File metadata

  • Download URL: zpaq-0.3.7-cp39-cp39-musllinux_1_2_x86_64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: CPython 3.9, musllinux: musl 1.2+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for zpaq-0.3.7-cp39-cp39-musllinux_1_2_x86_64.whl
Algorithm Hash digest
SHA256 286ea824ce9d9cb6ebd4a9bf042470543eba14c3a20ff846d238d2adfd5da9a9
MD5 ca73f635842e7a18fe36b7c21b2e6cac
BLAKE2b-256 d6ea5e4d941ff08ec46fd1f10bd0a0094769c5a04c2cdd8b95ce45ed62827ab6

See more details on using hashes here.

File details

Details for the file zpaq-0.3.7-cp39-cp39-manylinux_2_34_x86_64.whl.

File metadata

  • Download URL: zpaq-0.3.7-cp39-cp39-manylinux_2_34_x86_64.whl
  • Upload date:
  • Size: 406.9 kB
  • Tags: CPython 3.9, manylinux: glibc 2.34+ x86-64
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.13

File hashes

Hashes for zpaq-0.3.7-cp39-cp39-manylinux_2_34_x86_64.whl
Algorithm Hash digest
SHA256 e3c74b79911feeba10d838c3ffe7c253f440c49d992a35b18d1102d7481243f7
MD5 33a0ac4eaafa06cf5ef41b8cb66bb966
BLAKE2b-256 7a50b671fc9ea3dfa73fefeb0229a0d94da6bf9a9f9b7e3bdbdb6038f65cbd8b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page