Skip to main content

mibz2

PyPI crates.io: mibz2 docs.rs Documentation MIT OR Apache-2.0

A bzip2 decoder built for memory-level parallelism. It is block-parallel like lbzip2. Within a block, its inverse BWT is walked as 12 interleaved chains, so a core keeps a dozen of the walk's cache-missing loads in flight instead of one.

  • Its output is byte for byte libbzip2's, and every block CRC and stream CRC is checked.
  • It runs on any x86-64 or aarch64 CPU. Its SIMD kernels (AVX2, PCLMULQDQ) are chosen at run time, and each has a portable scalar twin.
  • It depends on std only.

It was written in wikidata-pq, whose split decodes the Wikidata dump with it (WIKIDATA_BZ2_DECODER=native), and moved here with its history and journals (docs/journal/). It knows nothing of Wikidata. Its name is for "multi-chain inversion of bzip2".

Install

cargo add mibz2     # Rust
pip install mibz2   # Python 3.10+ (abi3 wheels: Linux x86-64 and aarch64, macOS, Windows x64)

What is checked, and when

  • Every block's CRC is checked before any of that block's bytes are returned. A block whose CRC does not match returns none of its bytes: the error comes instead.
  • Each stream's combined CRC is checked at the stream's end. The bytes of a stream's blocks are returned as each block is verified, before the stream's own CRC has been read. A streaming caller has the whole stream verified only once a read reaches its end without an error; until then what it has read is provisional (in Python, BZ2Reader.verified).
  • An error says where decoding failed (Error { block, bit }): which block, and the bit it starts at. A damaged bit can be found later than where it lies, since the input is only known to be wrong once something it encodes fails a check.
  • bzip2 1.0 and later output is decoded (libbzip2 1.0.8's format). Randomised blocks, written only by bzip2 0.9.0 and earlier, are refused with their own error (see Choices).

Python

import mibz2

data = mibz2.decompress(open("file.bz2", "rb").read())  # a whole .bz2, on every CPU
data = mibz2.decompress(compressed, threads=1)           # or on one thread

# a path or a binary file object, decoded as it is read (a buffered binary file)
with mibz2.open("file.bz2") as f:
    for line in f:
        ...
    assert f.raw.verified  # the end was reached and every CRC matched

A bad input raises mibz2.DecodeError (a ValueError): from decompress, or from the read at which it is found.

Rust

use std::io::Read;

// a whole .bz2 in memory (its streams one after another): on this thread, or on 4
let data = std::fs::read("file.bz2")?;
let text = mibz2::decode(&data)?;
let same = mibz2::decode_parallel(&data, 4)?;

// any Read, as a Read: on this thread, or block-parallel on 4 worker threads
let mut out = Vec::new();
mibz2::BzReader::new(std::fs::File::open("file.bz2")?).read_to_end(&mut out)?;
mibz2::BzReader::parallel(std::fs::File::open("file.bz2")?, 4).read_to_end(&mut out)?;

// the block boundaries (each block's header and Huffman symbols read, nothing decoded),
// then any range of whole blocks decoded on its own, on any thread: a caller's own
// parallelism, or a sweep over block-aligned segments
for span in mibz2::scan(&data) {
    let span = span?;
    let mut part = Vec::new();
    let range = mibz2::decode_blocks(&data, span.start_bit, span.end_bit, span.level,
                                      mibz2::Kernels::detect(), &mut part)?;
    // range.blocks: each block's (start bit, end bit, CRC); mibz2::combine(...) gives a
    // stream's combined CRC from its blocks'
}
API what
decode, decode_parallel(data, threads) a whole .bz2 in memory
BzReader::new(r), BzReader::parallel(r, threads), parallel_with(r, threads, slots, kernels) a Read adaptor; a bad stream is an io::Error (InvalidData) wrapping mibz2::Error
StreamDecoder::new(threads, slots, kernels) the push form: write compressed chunks from one thread, read decoded bytes from another, close, cancel; at most slots blocks in flight
scan(data) → BlockSpan { index, start_bit, end_bit, crc, stream, level } block boundaries, without decoding
decode_blocks(data, start_bit, end_bit, level, kernels, out) → Range a range of whole blocks, each CRC checked
Error { kind: ErrorKind, block, bit } what is wrong with the input, and where
Kernels::detect(), Kernels::scalar() the SIMD kernels, or the portable ones
bwt::InverseBwt (Segmented, the default; Plain), decode_with::<I>, StreamDecoder::with_inverse::<I> the inverse-BWT kernel, swappable and timeable as part of the whole decoder

Errors (ErrorKind) cover the following:

  • the input: Empty, NotBz2, Truncated, TrailingData;
  • each of libbzip2's block checks: BadBlockMagic, BadOrigPtr, NoSymbols, BadGroups, BadSelectors, BadSelector, BadCodeLength, BadHuffmanCode, SelectorOverrun, RunTooLong, BlockTooLong, RunUnfinished;
  • BlockCrc and StreamCrc, with the stored and computed values;
  • this decoder's refusals: Randomised, BlockTooLarge;
  • Io (from a BzReader's source), RangeOvershoot, Cancelled, and Internal (a caught panic: a bug).

The bytes before an error are exactly the stream's up to the bad block.

Choices

The format is libbzip2 1.0.8's, with every check it makes. Four choices differ from it, and each has its own error:

  • randomised blocks (written only by bzip2 0.9.0 and earlier, before 1999) are refused;
  • bytes after the last stream that do not begin another stream are an error. lbzip2 and bzip2 ignore them. Refusing them means nothing after a stream is ever silently dropped;
  • an oversubscribed Huffman code pointing past the alphabet, where libbzip2 reads a stale table entry, is an error;
  • a block whose compressed form passes 64 MiB is an error. A real block is at most about 4.2 MB.

How it works

  • The scanner. One thread reads each block's header and Huffman-coded symbols in order. This is the one sequential phase: a block's end is known only once they are read. It is roughly a tenth of a decode on well-compressed text, more on poorly compressible data.

  • The workers. Each worker finishes a block: move-to-front, the T-vector, the inverse BWT walk, RLE1 and the CRC.

  • The reader takes the blocks in order.

  • Memory is bounded: at most slots blocks are in flight, and the buffers are reused.

  • The walk (bwt::walk_segments):

    • 256 pseudo-random rows, the walk's first among them, are segment starts;
    • a bitmap of the starts ends each segment;
    • 12 lanes walk segments in lockstep;
    • the segments are joined in order.

    It equals the plain walk on every list, lists of several cycles included.

Prior work

  • The format and its checks are libbzip2's (Julian Seward, bzip2 1.0.8, decompress.c). This decoder follows its decoding rules and makes each of its checks, in its terms. Its Huffman decode tables are built as BZ2_hbCreateDecodeTables builds them, so a stream libbzip2 refuses is refused here too. It is a separate implementation in Rust, not a port of libbzip2's code. The tests use libbzip2 itself (the bzip2 crate's bundled 1.0.8) as the oracle.
  • Interleaving several traversals of one BWT inversion on one thread, so that out-of-order execution keeps their loads in flight, is Kärkkäinen, Kempa and Puglisi's idea: "Slashing the Time for BWT Inversion", DCC 2012, pp. 99–108.
    • They also use a super-alphabet (an LF² table: two characters a dependent lookup) and "Precopy" for repetitive data. Neither is used here.
    • What is particular here is only how the traversals are found in an existing bzip2 file, which carries no metadata for them: the random starts, the stop bitmap, and the joining in order.
  • Block-parallel decoding is lbzip2's (Mikołaj Izdebski) and pbzip2's approach.

Tests and benchmarks

cargo test --release      # against libbzip2: the corpora below, crafted and malformed streams
cargo bench               # decode speed on the corpora below
cargo run --release --example fuzz -- 600 1            # a mutation fuzz loop, libbzip2 the oracle
cargo build --profile fuzz --example fuzz              # (with overflow checks and debug assertions)
cargo run --release --example bzcat -- 4 < f.bz2 > f   # the parallel decoder as a command
cargo run --release --example windows -- OUT w.bin...  # blocks found in byte-range windows of a .bz2, checked

On aarch64: rustup target add aarch64-unknown-linux-gnu, then apt install qemu-user gcc-aarch64-linux-gnu, then cargo test --release --target aarch64-unknown-linux-gnu with --config .cargo/aarch64-qemu.toml (it sets the linker and the qemu runner).

The tests' corpora, none of them Wikidata (tests/common/mod.rs, corpora), each compressed by reference bzip2 (libbzip2 1.0.8):

  • a text corpus: this crate's own sources and README, repeated;
  • random bytes;
  • one byte repeated;
  • a 7-byte pattern repeated;
  • runs of random lengths;
  • binary records.

They are decoded by every entry point. Beside them are crafted blocks (random permutations, lists of several cycles, block-size bounds at every level 1–9) and malformed streams (truncation at every point, CRC flips, every header and table bit flipped, oversized runs, trailing data).

Benchmark disclosure

cargo bench (best of 3, decoded MB/s, cycles a byte at the nominal clock). The machine is a shared 4-vCPU cloud VM (Intel(R) Xeon(R) Processor @ 2.80GHz, AVX2 and PCLMULQDQ) with other jobs running, so the figures are indicative only. Between two runs, libbzip2's own figure on the text corpus moved from 18 to 34 MB/s. Corpora at the default scale (8–20 MB each), level 9:

corpus ratio libbzip2, 1 thread mibz2, 1 thread scalar kernels, 1 thread plain walk, 1 thread mibz2, 4 threads
text (sources) 19.8× 34 MB/s 84 MB/s 66 MB/s 17 MB/s 155 MB/s
random 1.1× 8 31 28 17 89
one byte 392,157× 347 2,029 1,034 2,372 831
pattern (7 bytes) 17,563× 139 93 64 210 302
runs 263× 250 1,434 594 797 666
records 1.0× 8 32 27 9 31

What the table shows:

  • Text and random data: 2.5–4× libbzip2 on one thread.
  • A short pattern repeated is the one case slower than libbzip2: 0.67× on one thread. Its walk is cache-resident and prefetchable, so the plain walk wins (210 MB/s), and the segments' bookkeeping only costs. Choosing the walk per block is open work.
  • Poorly compressible binary records do not scale past one thread. The sequential Huffman scanner is most of their decode. Random bytes scale 2.9×.
  • Data of near-infinite ratio (one byte, runs) is bound by copying its output, at 0.7–2 GB/s.
  • On the Wikidata dump's own blocks (its JSON, 12–21×), this decoder is 2.0–2.3× lbzip2 on one thread (docs/journal/2026-10-27-bz2-decoder.md). Against other decoders, on the dump's blocks at 4 threads, pbz2 0.2.0 takes 1.06× its time (a tie on the CI slice, interleaved: wall 1.00×), and lbzip2, fbz, the lbzip2 crate and indexed_bzip2 take 1.46–1.86× (docs/journal/2026-10-27-bz2-bakeoff.md). These are decoder-level figures; in wikidata-pq's split, end to end, the decoder took 20% less CPU and 16% less wall time than lbzip2 (lmmx/wikidata-pq#197).

Licence

MIT OR Apache-2.0 (LICENSE-MIT, LICENSE-APACHE), at the licensee's option.

Metadata

Release files for mibz2 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mibz2 0.1.2
File Size Uploaded Provenance
mibz2-0.1.2.tar.gz 77.5 kB Without provenance Details

Built distributions (wheels)

Table of built distributions (wheels) for mibz2 0.1.2
File
mibz2-0.1.2-cp310-abi3-win_amd64.whl CPython 3.10 abi3 Windows x86-64 Details
mibz2-0.1.2-cp310-abi3-manylinux_2_28_aarch64.whl CPython 3.10 abi3 Linux glibc 2.28+ ARM64 Details
mibz2-0.1.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.10 abi3 Linux glibc 2.17+ x86-64 Details
mibz2-0.1.2-cp310-abi3-macosx_11_0_arm64.whl CPython 3.10 abi3 macOS 11.0+ ARM64 Details
mibz2-0.1.2-cp310-abi3-macosx_10_12_x86_64.whl CPython 3.10 abi3 macOS 10.12+ x86-64 Details

Total release size: 1.6 MB

Release files / mibz2-0.1.2.tar.gz

Download URL mibz2-0.1.2.tar.gz
Size 77.5 kB
Tags Source
SHA-256 checksum
How to use checksums
d1ec7cf9949638346dcb4f1ea430e0cccf9f6bc4abef02e127424f0e19bee06a
BLAKE2b-256 checksum
How to use checksums
94b13ad8f32b690ea049081739a263ad0fea14979f740aaa07d3a445afbc0a1b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / mibz2-0.1.2-cp310-abi3-win_amd64.whl

Download URL mibz2-0.1.2-cp310-abi3-win_amd64.whl
Size 241.6 kB
Tags CPython 3.10 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
42e5cc47f4cc6ebf2697bca4e0bcc56b8bf3133b5a4c4541522f007d62ae4b08
BLAKE2b-256 checksum
How to use checksums
edb5906f056cfd8e848193938fe5db4b151ddfa628a0e9ae46cc6834f9fdf6ee
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / mibz2-0.1.2-cp310-abi3-manylinux_2_28_aarch64.whl

Download URL mibz2-0.1.2-cp310-abi3-manylinux_2_28_aarch64.whl
Size 309.5 kB
Tags CPython 3.10 Linux glibc 2.28+ ARM64 abi3
SHA-256 checksum
How to use checksums
8c865165fef69131c273640e7134876c12491dd692ea26bd574567aec1d0f696
BLAKE2b-256 checksum
How to use checksums
3b08f86c4ec4d9a13dd62f940e72bf18439faf78cfbaa69e1c2888d08a28e17f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / mibz2-0.1.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL mibz2-0.1.2-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 325.2 kB
Tags CPython 3.10 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
2c3f6cdffdd8e2dd6f8d8906f9e2ec1ec3e681568ebc36aeef807b96aaf138fb
BLAKE2b-256 checksum
How to use checksums
ad4282c3048cfd5ca1bcb4ce08fae70a7fc884c922d92c3e750e295884b1058d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / mibz2-0.1.2-cp310-abi3-macosx_11_0_arm64.whl

Download URL mibz2-0.1.2-cp310-abi3-macosx_11_0_arm64.whl
Size 291.3 kB
Tags CPython 3.10 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
25593acaa45a4d8b9e0f794eaf549e5d7ca023724b2e6b881ba5bfa0f00cc40f
BLAKE2b-256 checksum
How to use checksums
41b32c52bb45ff1b5a92845a77f9c23e50e702fa75e8ea8997200efc719f0340
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Release files / mibz2-0.1.2-cp310-abi3-macosx_10_12_x86_64.whl

Download URL mibz2-0.1.2-cp310-abi3-macosx_10_12_x86_64.whl
Size 306.2 kB
Tags CPython 3.10 abi3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
e4854cab6475e258da9441cb0733d6f2fd6ba544eaca49d15010ff3f97da9a48
BLAKE2b-256 checksum
How to use checksums
33be0f4da4ff0a7a1f535a2ce0ee7b20d139306b903fcb33e0c44d6557dbb060
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via maturin/1.15.0

Security for mibz2 0.1.2

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page