ExCodecs implements C-Blosc2-compatible chunks only (pure Rust via
blosc2-pure-rs). That means:
| You can | You cannot (with ExCodecs alone) |
|---|---|
encode(:blosc2, binary) → one chunk | Open/save .b2frame super-chunk files |
Interop with python-blosc2 compress/decompress on a buffer | B2ND multi-dim arrays / partial ND slices |
Use :blosclz, :lz4, :lz4hc, :zstd, :zlib + shuffle | cname: :snappy (use standalone :snappy codec) |
Standalone Snappy remains ExCodecs.encode(:snappy, data).
Blosc2 is a meta-compressor designed for high-performance compression of binary data, especially numerical arrays. This guide explains how Blosc2 works, its shuffle filters and internal codecs, and how to use it effectively in ExCodecs.
Overview
Blosc2 is not a compression algorithm itself. It is a meta-compressor: a framework that orchestrates other compressors (called "codecs" or "compressors" in Blosc2 terminology) and adds pre-processing and post-processing stages around them.
The key innovation in Blosc2 is combining:
- Shuffle filters that reorganize bytes to improve compressibility of typed data.
- Pluggable internal compressors — in ExCodecs: BloscLZ, LZ4, LZ4HC, Zstd, Zlib
(not Snappy as
cname; use the standalone:snappycodec for Snappy). - Block-based chunks — ExCodecs compresses one buffer to one chunk
(
nthreads: 1on the NIF; not a multi-chunk super-chunk store).
ExCodecs exposes Blosc2 as a first-class chunk codec with configurable internal compressor, compression level, shuffle mode, and typesize.
How Blosc2 Works
Blosc2 processes data in a pipeline:
Input Binary
|
v
[1] Split into internally selected blocks
|
v
[2] Apply shuffle filter (none / byte / bit)
|
v
[3] Compress each block with internal codec
|
v
[4] Assemble one Blosc2 chunk
|
v
Output BinaryDecompression reverses the pipeline:
Input Binary (Blosc2 chunk)
|
v
[1] Parse chunk header
|
v
[2] Decompress each block with internal codec
|
v
[3] Apply inverse shuffle filter
|
v
[4] Concatenate blocks
|
v
Output Binary (original)Step 1: Block Splitting
Blosc2 chunks split input into internal blocks. In the wider Blosc2 ecosystem, blocks can support parallel or partial processing. ExCodecs currently exposes neither block-size selection nor block-level access; the implementation chooses the block layout and decodes the complete chunk.
Blocks still improve cache utilization inside compression.
Step 2: Shuffle Filters
The shuffle filter is Blosc2's main advantage for typed data. It reorders bytes within elements before compression:
Byte Shuffle (shuffle: :byte):
Groups bytes by their position within each element:
Original (4x 32-bit integers):
[A0 A1 A2 A3] [B0 B1 B2 B3] [C0 C1 C2 C3] [D0 D1 D2 D3]
After byte shuffle:
[A0 B0 C0 D0] [A1 B1 C1 D1] [A2 B2 C2 D2] [A3 B3 C3 D3]High-order bytes (A3, B3, C3, D3) often have similar values (e.g., mostly zeros for positive integers). Grouping them together makes the data far more compressible.
Bit Shuffle (shuffle: :bit):
The same principle at the bit level, providing even more correlation for certain data types (e.g., floating-point values where the sign bit and exponent are similar across elements).
No Shuffle (shuffle: :none):
Skip the shuffle step. Use this for data that does not have a regular element structure.
Step 3: Internal Compression
Each (possibly shuffled) block is compressed using one of several codecs:
| Codec | Description | Speed | Ratio |
|---|---|---|---|
:blosclz | BloscLZ (C-Blosc2 pure-Rust port) | Very Fast | Moderate |
:lz4 | LZ4 (default) | Very Fast | Moderate |
:lz4hc | LZ4 high compression | Moderate | Good |
:zstd | Zstandard | Fast | High |
:zlib | Zlib (Deflate) | Moderate | Good |
The choice of internal codec determines the speed/ratio tradeoff within each block. Blosc2 adds the shuffle filtering on top, so the effective ratio can be much higher than using the same codec directly.
Step 4: Chunk Assembly
Blosc2 assembles the compressed blocks into a chunk with metadata including:
- The decompressed size.
- The block size.
- The internal codec, compression level, and shuffle mode.
- The element typesize.
This header allows decompression without any external metadata.
Using Blosc2 in ExCodecs
Basic Usage
# Default: LZ4 inner codec, level 5, byte shuffle, typesize 8
{:ok, compressed} = ExCodecs.encode(:blosc2, data)
{:ok, original} = ExCodecs.decode(:blosc2, compressed)With Zstd Inner Codec
{:ok, compressed} = ExCodecs.encode(:blosc2, data,
cname: :zstd,
clevel: 5,
shuffle: :byte,
typesize: 8
)With No Shuffle (General Binary Data)
{:ok, compressed} = ExCodecs.encode(:blosc2, data,
cname: :zstd,
clevel: 3,
shuffle: :none
)With Bit Shuffle (Maximum Ratio for Floats)
{:ok, compressed} = ExCodecs.encode(:blosc2, data,
cname: :zstd,
clevel: 9,
shuffle: :bit,
typesize: 4
)Single-threaded chunk compression
{:ok, compressed} = ExCodecs.encode(:blosc2, data,
cname: :lz4,
clevel: 5,
shuffle: :byte,
typesize: 8
)The current NIF always uses one Blosc2 worker (nthreads: 1) on a BEAM
DirtyCpu scheduler. :numthreads is not a public option.
Configuration Options
| Option | Type | Default | Description |
|---|---|---|---|
:cname | atom | :lz4 | Internal compressor (:blosclz, :lz4, :lz4hc, :zstd, :zlib) |
:clevel | integer (0-9) | 5 | Compression level (0 = no compression) |
:shuffle | atom | :byte | Shuffle filter (:none, :byte, :bit) |
:typesize | integer (1-255) | 8 | Element size in bytes for shuffle |
Choosing cname (Internal Codec)
:lz4(default): Best for speed-sensitive applications. The shuffle filter provides most of the ratio improvement.:zstd: Best ratio when combined with shuffle. Use:zstdwithclevel: 5-9for maximum compression of numerical data.:lz4hc: Higher ratio than:lz4, slower compress.:blosclz: Blosc’s own LZ codec (included for C-Blosc2 interop).:zlib: Moderate speed, good ratio. Available for compatibility.
cname: :snappy is rejected. Use the standalone registry codec
ExCodecs.encode(:snappy, data) instead.
Choosing shuffle
:byte(default): Best for most typed data. Groups bytes by position within each element. Recommended for float64, int32, and similar numeric types.:bit: Finest granularity. Groups bits by position. Can improve ratio further for floating-point data where sign/exponent bits are similar.:none: No pre-processing. Use for general binary data without regular element structure, or when the shuffle adds overhead without ratio improvement.
Choosing typesize
The typesize tells Blosc2 how many bytes each element occupies. It is critical for the shuffle filter to work correctly.
| Data Type | typesize |
|---|---|
:float64 / double | 8 |
:float32 / float | 4 |
:int64 | 8 |
:int32 | 4 |
:int16 | 2 |
:int8 | 1 |
| Raw binary (no shuffle) | 1 |
Setting typesize: 1 with shuffle: :byte is equivalent to shuffle: :none because there is no intra-element byte grouping.
Setting typesize: 8 with shuffle: :byte on float64 arrays typically provides about a 2-4x ratio improvement over compressing the raw data directly (illustrative; measure on your arrays).
Important: The data length should ideally be a multiple of typesize for the shuffle filter to work correctly. If the data length is not a multiple of typesize, Blosc2 will still work but the last partial element will not benefit from shuffling.
Choosing clevel
| clevel | Behavior |
|---|---|
| 0 | No compression (shuffle only) |
| 1 | Fastest compression |
| 5 | Default, good balance |
| 9 | Maximum compression for the chosen codec |
With clevel: 0, Blosc2 applies the shuffle filter but does not compress. This is useful when you want the reordering benefit without compression, or as a diagnostic to see how much shuffle alone helps.
Threading
On the BEAM, the NIF runs on a DirtyCpu scheduler. The pure-Rust
C-Blosc2-compatible chunk implementation uses nthreads: 1; ExCodecs does not
create an extra native thread pool. Run independent codec calls concurrently
from separate BEAM processes when workload-level parallelism is needed.
Wire format is a Blosc2 chunk (not super-chunk / B2ND / .b2frame).
Fixtures under test/fixtures/blosc2/ are produced with python-blosc2.
The Blosc2 Chunk Format
ExCodecs produces one C-Blosc2-compatible chunk per call. The chunk carries the decompressed size, compressed size, block size, typesize, codec, level, and filter metadata required for full-buffer decompression.
The public API only performs complete chunk decompression. It does not
expose partial block reads, super-chunks, B2ND arrays, .b2frame containers,
or configurable native decompression threads.
When to Use Blosc2
Use Blosc2 When
- Data is numerical arrays. Float64, int32, and other typed data benefit enormously from shuffle filters.
- You need fine-grained control. Blosc2 offers the most parameters of any codec in ExCodecs.
- Data has regular element structure. If data length is a multiple of a known typesize, Blosc2 can exploit this.
- You need C-Blosc2 chunk interoperability. ExCodecs chunks interoperate with single-buffer C/Python Blosc2 compression APIs.
Consider Alternatives When
- Data is unstructured binary. If there is no regular element structure, the shuffle filter provides no benefit, and Zstd or LZ4 directly may be simpler.
- Data is small. Blosc2's chunk-header overhead makes it less efficient for data under 256 bytes.
- You want simplicity. Blosc2 has many configuration knobs. Zstd with
level: 3is simpler and effective for most data.
Ratio Improvement from Shuffle
The shuffle filter is Blosc2's key advantage for numerical data. Illustrative ratio improvements (measure on your arrays):
| Data Type | Zstd Alone | Blosc2 + Zstd + Byte Shuffle | Improvement |
|---|---|---|---|
| float64 array | 2.5:1 | 5:1 - 10:1 | 2-4x |
| int32 array | 2.0:1 | 4:1 - 8:1 | 2-4x |
| Mixed JSON | 3.0:1 | 3.0:1 (shuffle: :none) | No change |
| Random binary | 1.01:1 | 1.01:1 | No change |
For numerical data, shuffle often helps substantially when the element layout is regular.
Comparison with Other Codecs
| Property | Blosc2 | Zstd | LZ4 |
|---|---|---|---|
| Category | Meta-compressor | Algorithm | Algorithm |
| Shuffle Filters | Yes (byte, bit) | No | No |
| Inner Codecs | 5 options | N/A | N/A |
| Incremental API | No | No | No |
| Per-call threading | Single-threaded | Single-threaded | Single-threaded |
| Best For | Numerical arrays | General data | Speed |
| Configuration | Codec/level/shuffle/typesize | Level 1-22 | None |
Best Practices
Set
typesizecorrectly. Match it to your element size (4 for float32, 8 for float64). Wrong typesize reduces or eliminates shuffle benefits.Use
shuffle: :byteas the default for typed data. It provides the most benefit for the least overhead. Switch to:bitfor floating-point data where exponent bits correlate.Use
shuffle: :nonefor unstructured data. Shuffle on unstructured data adds CPU overhead without ratio improvement.Start with
cname: :lz4, clevel: 5for speed, orcname: :zstd, clevel: 5for ratio. These are the most commonly useful configurations.Parallelize independent calls with BEAM processes. Each NIF call is single-threaded and runs on a DirtyCpu scheduler.
Benchmark with your actual data. The effectiveness of shuffle depends heavily on the data. Always measure, do not guess.
Ensure data length is a multiple of typesize. If possible, pad your data to a typesize boundary before compressing with Blosc2.