Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Performance issue when compared to zstd_safe #266

Open
fr3akX opened this issue Mar 13, 2024 · 1 comment
Open

Performance issue when compared to zstd_safe #266

fr3akX opened this issue Mar 13, 2024 · 1 comment

Comments

@fr3akX
Copy link

fr3akX commented Mar 13, 2024

zstd::stream::copy_decode is 10-50x slower then zstd_safe::decompress on my test input. And 2 zstd::stream::copy_decode
40% slower then zstd_safe::decompress.

benchmark:

use criterion::{black_box, criterion_group, criterion_main, Criterion};

fn benchmark(c: &mut Criterion) {
    let data = vec![
        176, 234, 1, 2, 3, 4, 3, 2, 2, 1, 1, 4, 1, 0, 0, 0, 0, 1, 2, 2, 1, 1, 2, 2, 1, 0, 1, 4, 1,
        0, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 1, 4, 3, 4, 1, 0, 0, 0, 1, 2, 2,
        1, 1, 4, 1, 1, 4, 1, 1, 4, 3, 4, 1, 0, 0, 0, 0, 1, 2, 0, 2, 1, 1, 4, 1, 0, 0, 0, 1, 4, 1,
        0, 0, 0, 1, 4, 3, 4, 1, 1, 2, 2, 1, 0, 0, 1, 4, 1, 1, 2, 2, 1, 0, 0, 0, 0, 0, 1, 4, 3, 4,
        1, 0, 0, 1, 4, 1, 0, 0, 1, 4, 1, 0, 1, 4, 1, 1, 2, 0, 0, 0, 0, 2, 1, 1, 4, 1, 0, 0, 0, 0,
        0, 1, 4, 1, 0, 1, 4, 1, 0, 0, 0, 1, 4, 3, 4, 1, 0, 0, 0, 0, 0, 1, 4, 1, 1, 2, 2, 3, 2, 2,
        1, 0, 0, 1, 4, 3, 2, 0, 2, 3, 4, 1, 0, 0, 0, 0, 1, 2, 0, 2, 3, 4, 1, 0, 0, 1, 4, 1, 0, 0,
        0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 1, 4, 1, 0, 0, 0, 0, 1, 4, 1, 0, 0, 1, 4, 1, 1, 4, 1, 0, 0,
        0, 1, 4, 1, 1, 2, 2, 1, 0, 0, 0, 0, 1, 2, 2, 1, 1, 4, 3, 2, 2, 1, 0, 0, 0, 1, 4, 3, 4, 3,
        2, 0, 2, 1, 0, 1, 4, 3, 4, 3, 4, 3, 4, 1, 0, 0, 0, 1, 4, 1, 0, 1, 4, 1, 0, 0, 0, 1, 2, 2,
        1, 0, 0, 0, 1, 2, 2, 3, 2, 2, 1, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 0, 1, 4, 1,
        0, 1, 4, 3, 4, 3, 2, 2, 1, 0, 0, 0, 0, 0, 0, 1, 4, 1, 0, 0, 0, 1, 4, 3, 4, 1, 1, 2, 2, 1,
        0, 1, 4, 3, 4, 1, 0, 0, 1, 4, 1, 0, 0, 1, 4, 1, 1, 4, 3, 4, 3, 2, 2, 1, 0, 0, 0, 0, 1, 2,
        2, 1, 1, 4, 1, 0, 0, 0, 1, 4, 1, 1, 4, 1, 1, 4, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
        4, 1, 0, 0, 1, 2, 0, 2, 1, 0, 0, 1, 2, 0, 2, 3, 4, 1, 0, 0, 0, 0, 0, 1, 2, 2, 1, 0, 0, 0,
        0, 0, 0, 1, 4, 1, 0, 0, 1, 4, 1, 1, 4, 3, 4, 3, 4, 1, 1, 4, 1, 0, 0, 0, 0, 0, 0, 1, 4, 1,
        0, 0,
    ];

    let compressed_data = vec![
        40, 181, 47, 253, 96, 225, 0, 45, 6, 0, 116, 3, 176, 234, 1, 2, 3, 4, 3, 2, 2, 1, 1, 4, 1,
        0, 0, 0, 0, 1, 2, 2, 1, 0, 1, 4, 3, 1, 4, 3, 0, 3, 4, 1, 1, 2, 2, 0, 2, 0, 0, 4, 0, 3, 1,
        3, 1, 1, 3, 0, 0, 0, 1, 4, 1, 0, 0, 62, 32, 32, 2, 131, 24, 195, 59, 108, 18, 105, 212,
        128, 114, 129, 138, 11, 139, 55, 80, 2, 158, 238, 53, 218, 1, 142, 0, 149, 16, 0, 76, 113,
        80, 46, 240, 237, 80, 48, 2, 171, 1, 8, 116, 235, 16, 109, 111, 88, 65, 160, 144, 1, 106,
        249, 14, 70, 34, 117, 32, 239, 246, 223, 44, 33, 43, 208, 2, 127, 2, 218, 223, 144, 92,
        192, 213, 1, 49, 0, 21, 228, 160, 132, 171, 183, 221, 153, 13, 7, 173, 12, 140, 50, 104,
        13, 96, 33, 168, 26, 2, 16, 96, 195, 88, 36, 176, 14, 244, 6, 94, 22, 184, 1, 50, 128, 135,
        3, 67, 18, 201, 228, 122, 18, 230, 167, 205, 194, 46, 111, 226, 45, 224, 11, 219, 192, 196,
        8, 91, 51, 12,
    ];

    let mut out = vec![];

    zstd_safe_level(&mut out, &data, 3).unwrap();
    // println!("zstd_safe_level: {:?}", out);
    let mut dec = vec![];
    zstd_safe_decompress(&mut dec, &out).unwrap();
    assert_eq!(data, dec);
    let mut dec2 = vec![];
    decompress_zstd(&mut dec2, &out).unwrap();
    assert_eq!(data, dec2);

    for level in vec![-5, 2, 3, 4, 5] {
        c.bench_function(format!("zstd {}", level).as_str(), |b| {
            b.iter(|| black_box(compress2(&data, level, &mut out)));
        });
        c.bench_function(
            format!("compress_zstd_safe_level {}", level).as_str(),
            |b| {
                b.iter(|| black_box(zstd_safe_level(&mut out, &data, level)));
            },
        );
    }

    c.bench_function("decompress_zstd", |b| {
        b.iter(|| black_box(decompress_zstd(&mut dec, &compressed_data)));
    });
    c.bench_function("zstd_safe_decompress", |b| {
        b.iter(|| black_box(zstd_safe_decompress(&mut dec2, &compressed_data)));
    });
}

pub fn decompress_zstd(dst: &mut Vec<u8>, src: &[u8]) -> codecs::Result<()> {
    zstd::stream::copy_decode(src, dst)?;
    Ok(())
}

fn compress2(data: &[u8], level: i32, out: &mut Vec<u8>) {
    zstd::stream::copy_encode(data, out, level).unwrap();
}

fn zstd_safe_decompress(dst: &mut Vec<u8>, src: &[u8]) -> anyhow::Result<()> {
    let len = dst.len();
    let cap = dst.capacity();

    if cap > len {
        match zstd_safe::decompress(dst, src) {
            Ok(_) => {
                return Ok(());
            }
            Err(_code) => {
                //continue with resize
            }
        }
    }

    let cp = zstd_safe::decompress_bound(src)
        .map_err(|e| anyhow::anyhow!(format!("zstd: {}", zstd_safe::get_error_name(e))))?
        as usize;
    dst.reserve(cp);
    zstd_safe::decompress(dst, src)
        .map_err(|e| anyhow::anyhow!(format!("zstd: {}", zstd_safe::get_error_name(e))))?;
    Ok(())
}

fn zstd_safe_level(dst: &mut Vec<u8>, src: &[u8], level: i32) -> anyhow::Result<()> {
    let len = dst.len();
    let cap = dst.capacity();

    if cap > len {
        match zstd_safe::compress(dst, src, level) {
            Ok(_) => {
                return Ok(());
            }
            Err(_code) => {
                //continue with resize
            }
        }
    }

    let cp = zstd_safe::compress_bound(src.len());

    dst.reserve(cp);
    zstd_safe::compress(dst, src, level)
        .map_err(|e| anyhow::anyhow!(format!("zstd: {}", zstd_safe::get_error_name(e))))?;
    Ok(())
}

criterion_group!(benches, benchmark);
criterion_main!(benches);

results:

zstd -5                                        time:   [1.9967 µs 2.0104 µs 2.0246 µs]
compress_zstd_safe_level -5  time:   [1.8046 µs 1.8093 µs 1.8142 µs]

zstd 2                                         time:   [22.949 µs 23.126 µs 23.310 µs]
compress_zstd_safe_level 2.  time:   [2.2266 µs 2.2318 µs 2.2371 µs]

zstd 3                                       time:   [49.526 µs 50.020 µs 50.530 µs]
compress_zstd_safe_level 3 time:   [1.9953 µs 2.0016 µs 2.0079 µs]

zstd 4                                       time:   [108.99 µs 110.95 µs 113.69 µs]
compress_zstd_safe_level 4 time:   [3.7159 µs 4.3152 µs 5.0711 µs]

zstd 5                                      time:   [145.64 µs 148.66 µs 152.30 µs]
compress_zstd_safe_level 5 time:   [3.3225 µs 3.3312 µs 3.3411 µs]

decompress_zstd.            time:   [1.4370 µs 1.7105 µs 2.0412 µs]
zstd_safe_decompress    time:   [841.48 ns 1.0689 µs 1.3248 µs]
@gyscos
Copy link
Owner

gyscos commented Mar 21, 2024

Hi, and thanks for the report!

zstd::stream::copy_decode currently uses the streaming API, which has indeed some overhead compared to zstd_safe::decompress, which takes the entire input data in memory at once. Especially for small input data, I'd expect the overhead to be significant.

It would be nice to expose zstd_safe::decompress in the top-level zstd API to allow avoiding this overhead for small input.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants