rust 如何有效地检查一个Vec< u8>,看看它是否全为零?

ldioqlga  于 2023-10-20  发布在  其他
关注(0)|答案(4)|浏览(135)

我有许多4KiB的缓冲区,它们有50%的机会只包含零值。非零缓冲区通常在缓冲区早期具有非零字节。

fn is_zero(buf: &Vec<u8>) -> bool {
    for byte in buf.into_iter() {
        if *byte != 0 {
            return false;
        }
    }
    return true;
}

这是一种使用--release签入Rust的高性能方法吗?(我正在处理许多GB的数据。
(In在C版本中,我在检查之前将缓冲区转换为unsigned long long。这可能不是我能做的最好的,考虑到SSE等。

yks3o0rb

yks3o0rb1#

您可以使用align_tou8的切片转换为u128的切片,使比较更有效:

fn is_zero(buf: &[u8]) -> bool {
    let (prefix, aligned, suffix) = unsafe { buf.align_to::<u128>() };

    prefix.iter().all(|&x| x == 0)
        && suffix.iter().all(|&x| x == 0)
        && aligned.iter().all(|&x| x == 0)
}

在我的机器上运行一个简单的基准测试显示了16倍的性能增益!

#![feature(test)]
extern crate test;

fn v() -> Vec<u8> {
    std::iter::repeat(0).take(1000000).collect()
}

fn is_zero(buf: &[u8]) -> bool {
    buf.into_iter().all(|&b| b == 0)
}

fn is_zero_aligned(buf: &[u8]) -> bool {
    let (prefix, aligned, suffix) = unsafe { buf.align_to::<u128>() };

    prefix.iter().all(|&x| x == 0)
        && suffix.iter().all(|&x| x == 0)
        && aligned.iter().all(|&x| x == 0)
}

#[bench]
fn bench_is_zero(b: &mut test::Bencher) {
    let v = test::black_box(v());
    b.iter(|| is_zero(&v[..]))
}

#[bench]
fn bench_is_zero_aligned(b: &mut test::Bencher) {
    let v = test::black_box(v());
    b.iter(|| is_zero_aligned(&v[..]))
}
running 2 tests
test tests::bench_is_zero         ... bench:     455,975 ns/iter (+/- 414)
test tests::bench_is_zero_aligned ... bench:      28,615 ns/iter (+/- 116)

根据您的计算机,不同的整数类型(u64)可能会产生更好的性能。

  • 感谢Rust discord服务器上的@Globi提出了这个想法 *
bwleehnv

bwleehnv2#

通过一次阅读u64,在我的笔记本电脑上使用byteorder获得了4倍的加速。

lib.rs

extern crate byteorder;

use byteorder::{NativeEndian, ReadBytesExt};
use std::io::Cursor;

pub fn one(buf: &[u8]) -> bool {
    buf.into_iter().all(|&byte| byte == 0)
}

pub fn two(buf: &[u8]) -> bool {
    let mut cur = Cursor::new(buf);
    while let Ok(val) = cur.read_u64::<NativeEndian>() {
        if val != 0 {
            return false;
        }
    }
    while let Ok(val) = cur.read_u8() {
        if val != 0 {
            return false;
        }
    }
    true
}

benches/benches.rs

#![feature(test)]

extern crate test;
extern crate zero_slice_8;

use zero_slice_8::{one, two};

fn v() -> Vec<u8> {
    let mut result = vec![];
    for _ in 0..100000 {
        result.push(0);
    }
    result
}

#[bench]
fn bench_one(b: &mut test::Bencher) {
    let v = v();
    b.iter(|| one(&v[..]))
}

#[bench]
fn bench_two(b: &mut test::Bencher) {
    let v = v();
    b.iter(|| two(&v[..]))
}
bxjv4tth

bxjv4tth3#

下面的函数是纯保存 Rust

fn is_zero ( slice : &[u8] ) -> bool {
    for i in (0..slice.len()).step_by(16) {
        if slice.len() - i >= 16 {
            let arr : [u8; 16] = slice[i..i+16].try_into().expect("this should always succeed");
            if u128::from_be_bytes(arr) != 0 {
                return false;
            }
        } else {
            for i in i..slice.len() {
                if slice[i] != 0 {
                    return false;
                }
            }
        }
    }
    return true;
}

具体来说,它使用u128::from_be_bytes函数将[u8; 16]数组转换为u128作为non-op,并使用TryInto trait将适当长度的[u8]转换为[u8; 16]-其余部分相当简单。可以手动展开内部循环来转换它,但我怀疑这是否会成为一个显著的性能瓶颈,因为构成列表尾部的u8不是16字节。
根据处理器的不同,使用u64甚至u32可能会更快,人们必须自己分析。

byqmnocz

byqmnocz4#

您可以使用rayon,这是一个数据并行库,看起来非常适合您的用例。用途:只需将buf.iter()更改为buf.par_iter(),Rayon就会完成其余的工作:

use rayon::prelude::*;

fn is_zero_par(buf: &[u8]) -> bool {
    buf.par_iter().all(|&b| b == 0)
}

对于2000万个元素的矢量,人造丝显示出7倍的性能提升:

#![feature(test)]
use rayon::prelude::*;
extern crate test;

fn v() -> Vec<u8> {
    std::iter::repeat(0).take(20000000).collect()
}

fn is_zero(buf: &[u8]) -> bool {
    buf.into_iter().all(|&b| b == 0)
}

fn is_zero_par(buf: &[u8]) -> bool {
    buf.par_iter().all(|&b| b == 0)
}

#[bench]
fn bench_is_zero(b: &mut test::Bencher) {
    let v = test::black_box(v());
    b.iter(|| is_zero(&v[..]))
}

#[bench]
fn bench_is_zero_par(b: &mut test::Bencher) {
    let v = test::black_box(v());
    b.iter(|| is_zero_par(&v[..]))
}
running 2 tests
test tests::bench_is_zero     ... bench:   7,217,686 ns/iter (+/- 478,845)
test tests::bench_is_zero_par ... bench:   1,080,959 ns/iter (+/- 111,692)

请注意,多线程的性能影响取决于工作负载(元素数量),较小的工作负载可能会受到负面影响。

相关问题