gzp

repository·main·Indexed 41 days ago

https://github.com/sstadick/gzp

A high-performance Rust crate for multi-threaded data compression and decompression. It provides parallelized versions of Gzip, Zlib, Raw Deflate, Snappy, BGZF, and Mgzip, designed as drop-in replacements for standard synchronous writers. Version 2.0.4 includes tools like ZBuilder for parallel writers, ParCompressBuilder for Mgzip, and BgzfSyncReader/Writer for block Gzip formats.

Tokens
9.3K
Snippets
32
Records
43
Agent score
51%

What's inside gzp

  1. Overview of gzp multi-threaded encoding

    main

    gzp is a Rust crate providing multi-threaded encoding and decoding. It acts as a near drop-in replacement for std::io::Write by compressing chunks of data in parallel and writing them to an underlying writer in the original byte order. This enables significantly faster compression for large datasets.

    Supported encodings include:

    • Gzip (via flate2)
    • Zlib (via flate2)
    • Raw Deflate (via flate2)
    • Snappy (via rust-snappy)
    • BGZF (block compression format limited to 64 Kb blocks)
    • Mgzip (block compression format with no block size limit)
  2. Configure gzp features in Cargo.toml

    main

    By default, gzp enables deflate_default and libdeflate features, which use the best performing zlib implementation and libdeflater for block gzip formats. You can customize your dependency based on your requirements:

    • Default (High performance): Uses libdeflate and flate2.
    • Rust-only backend: Disables Zlib support by using deflate_rust.
    • Snappy only: Disables all other encodings to use only Snappy.
    # Default (Deflate default)
    [dependencies]
    gzp = { version = "*" }
    
    # Rust backend (Zlib will not be available)
    [dependencies]
    gzp = { version = "*", default-features = false, features = ["deflate_rust"] }
    
    # Snappy only
    [dependencies]
    gzp = { version = "*", default-features = false, features = ["snap_default"] }
  3. Compress data using the Snap format

    main

    The Snap struct implements the FormatSpec and SyncWriter traits, allowing it to be used within the gzp framework for Snappy compression.

    Key characteristics:

    • Format: Uses the FrameEncoder format where each block is a frame.
    • Compression Level: The Compression level is ignored for the Snap format as Snappy does not use levels in this implementation.
    • Dictionary Support: needs_dict() always returns false, meaning dictionaries are not supported for this format.
    • Parallelization: Can be used with ParCompressBuilder for multi-threaded compression or SyncZBuilder for synchronous compression.
  4. Supported DEFLATE compression formats

    main

    The gzp crate provides implementations for several DEFLATE-based compression formats:

    • Gzip: Standard Gzip stream with gzip header and footer (uses Crc32 checksum).
    • Zlib: Zlib stream with zlib header and footer (uses Adler32 checksum). Requires any_zlib feature.
    • Raw Deflate: Contiguous raw deflate stream without headers or footers.
    • Mgzip: A multi-member Gzip format.
    • Bgzf: Block Gzip format (BGZF), often used in bioinformatics.
  5. Use ParCompress for parallel compression

    main

    ParCompress provides a multi-threaded compression pipeline. It uses a builder pattern to configure the number of threads, buffer size, and compression level, then wraps a Write target to compress data in parallel.

    Key Concepts

    • Parallelism: It spins up num_threads background threads to compress chunks of data. One additional thread is used for writing the compressed results to the target.
    • Buffer Management: Data is accumulated into a buffer of buffer_size. Once the buffer is full, it is sent to the compression pipeline.
    • Lifecycle: You must call .finish() to ensure all buffered data is compressed, written, and that all threads are joined correctly. While Drop attempts to call finish(), it is best practice to call it explicitly to handle potential errors.
    • Thread Pinning: You can optionally pin compression threads to specific CPU cores using .pin_threads(Some(usize)) to optimize performance on supported platforms.
    use gzp::{par::compress::{ParCompress, ParCompressBuilder}, deflate::Gzip, ZWriter};
    use std::io::Write;
    
    let mut writer = vec![];
    // Create a parallel compressor using Gzip format
    let mut parz: ParCompress<Gzip, _> = ParCompressBuilder::new()
        .from_writer(writer);
    
    parz.write_all(b"This is a first test line\n").unwrap();
    parz.write_all(b"This is a second test line\n").unwrap();
    
    // finish() is required to flush buffers and join threads
    parz.finish().unwrap();
  6. Use ZBuilder for unified parallel or synchronous compression

    main

    The ZBuilder provides a unified API to create either a parallel or a synchronous compression writer. If num_threads is greater than 1, it returns a parallel compressor (similar to pigz). If num_threads is 0 or 1, it returns a synchronous writer. This allows you to write code that works regardless of the available thread count.

    # #[cfg(feature = "deflate")] {
    use std::io::Write;
    use gzp::{deflate::Gzip, ZBuilder, ZWriter};
    
    let mut writer = vec![];
    let mut parz = ZBuilder::<Gzip, _>::new()
        .num_threads(0)
        .from_writer(writer);
    parz.write_all(b"This is a first test line\n").unwrap();
    parz.write_all(b"This is a second test line\n").unwrap();
    parz.finish().unwrap();
    # }
  7. Use ZBuilder for parallel compression

    main

    The ZBuilder provides a way to create a parallel writer. It returns a trait object that is transparent over ParZ or SyncZ. You can specify the number of threads using .num_threads(), where 0 typically indicates using all available threads.

    Example using Gzip encoding:

    use std::{env, fs::File, io::Write};
    
    use gzp::{deflate::Gzip, ZBuilder, ZWriter};
    
    fn main() {
        let mut writer = vec![];
        // ZBuilder will return a trait object that transparent over `ParZ` or `SyncZ`
        let mut parz = ZBuilder::<Gzip, _>::new()
            .num_threads(0)
            .from_writer(writer);
        parz.write_all(b"This is a first test line\n").unwrap();
        parz.write_all(b"This is a second test line\n").unwrap();
        parz.finish().unwrap();
    }
  8. Use ParCompress for Mgzip block compression

    main

    For Mgzip (multi-threaded Gzip with no block size limit), use ParCompressBuilder to wrap an underlying writer. This is useful for streaming large amounts of data from stdin to stdout in parallel chunks.

    use gzp::{
        ZWriter,
        deflate::Mgzip,
        par::{compress::{ParCompress, ParCompressBuilder}}
    };
    use std::io::{Read, Write};
    
    fn main() {
        let chunksize = 64 * (1 << 10) * 2;
    
        let stdout = std::io::stdout();
        let mut writer: ParCompress<Mgzip> = ParCompressBuilder::new().from_writer(stdout);
    
        let stdin = std::io::stdin();
        let mut stdin = stdin.lock();
    
        let mut buffer = Vec::with_capacity(chunksize);
        loop {
            let mut limit = (&mut stdin).take(chunksize as u64);
            limit.read_to_end(&mut buffer).unwrap();
            if buffer.is_empty() {
                break;
            }
            writer.write_all(&buffer).unwrap();
            buffer.clear();
        }
        writer.finish().unwrap();
    }
  9. Use ParZ for Snappy compression

    main

    For Snappy encoding, use ParZBuilder to create a ParZ<Snap> writer. This follows a similar chunked processing pattern as the Mgzip example.

    use gzp::{parz::{ParZ, ParZBuilder}, snap::Snap};
    use std::io::{Read, Write};
    
    fn main() {
        let chunksize = 64 * (1 << 10) * 2;
    
        let stdout = std::io::stdout();
        let mut writer: ParZ<Snap> = ParZBuilder::new().from_writer(stdout);
    
        let stdin = std::io::stdin();
        let mut stdin = stdin.lock();
    
        let mut buffer = Vec::with_capacity(chunksize);
        loop {
            let mut limit = (&mut stdin).take(chunksize as u64);
            limit.read_to_end(&mut buffer).unwrap();
            if buffer.is_empty() {
                break;
            }
            writer.write_all(&buffer).unwrap();
            buffer.clear();
        }
        writer.finish().unwrap();
    }
  10. Handle errors in ParDecompress

    main

    Errors occurring in background decompression threads are propagated to the main thread when calling .read() or .finish().

    Common error types related to decompression include:

    • GzpError::BufferSize: Returned if the requested buffer size is smaller than DICT_SIZE.
    • GzpError::NumThreads: Returned if num_threads() is called with 0.
    • GzpError::InvalidCheck: Returned if the checksum of a decompressed block does not match the expected value in the footer.
    • GzpError::Io: Wraps standard std::io::Error instances.
  11. Compress data using SyncZBuilder

    main

    For synchronous (single-threaded) compression, use SyncZBuilder. This is useful when you want to specify a compression level and initialize a writer from an existing std::io::Write implementation.

    use std::io::Write;
    use gzp::{deflate::Gzip, syncz::SyncZBuilder, ZWriter};
    
    let mut z = SyncZBuilder::<Gzip, _>::new()
        .compression_level(Compression::new(6))
        .from_writer(out_writer);
    
    z.write_all(input).unwrap();
    z.finish().unwrap();