【问题标题】:How to cast an [u8] array larger than 8 bytes to an integer? [closed]如何将大于 8 字节的 [u8] 数组转换为整数? [关闭]
【发布时间】:2020-12-28 17:33:00
【问题描述】:

由于数组的长度,我不能使用i32::from_ne_bytes(),但当然,以下工作尤其有效,因为代码只能在支持非对齐访问的 cpu 架构上运行(或者因为长度小,整个数组可能会存储在多个 cpu 寄存器中)。

fn main() {
    let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
    println!("1 == {}", unsafe{std::ptr::read(&buf[1])} as i32);
}

但是有没有更简洁的方法可以在不复制数组的同时做到这一点?

【问题讨论】:

    标签: arrays rust x86-64


    【解决方案1】:

    提取一个 4 字节的 &[u8] 切片并使用 try_into()convert it&[u8; 4] 数组引用中。然后你可以拨打i32::from_ne_bytes()

    use std::convert::TryInto;
    
    fn main() {
        let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
        println!("{}", i32::from_ne_bytes((&buf[1..5]).try_into().unwrap()));
    }
    

    输出:

    302055424
    

    Playground

    【讨论】:

    • 如果 copy 是指从内存加载到寄存器中Compiler explorer 表明,在检查缓冲区足够大之后,它实际上只是一条 lea 指令。
    • @user2284570 您是否包含了use std::convert::TryInto 语句?它在playground using nightly 上运行良好。
    【解决方案2】:

    TL;DR:实际上,只需使用 John Kugelman 的解决方案,复制 4 个字节是不可测量的。

    最大的“测量”差异是 0.09 ps (239.79 - 239.70)。那是 90 飞秒,或 0.00009 纳秒。再次运行基准测试,将产生截然不同的结果(在皮秒范围内。)

    以复制 4 个字节来衡量某些内容是不现实的。我们远远低于纳秒,这是纯粹的噪音。

    test #[bench] criterion
    try_into 0 ns 239.79 ps
    reinterpret 0 ns 239.70 ps
    bit unpack 0 ns 239.74 ps
    b.iter(|| 1) 240.18 ps
    b.iter(|| 1) 239.73 ps
    b.iter(|| 1) 239.68 ps

    为了好玩,将所有测试更改为b.iter(|| 1),您将收到类似的结果,波动在皮秒内。

    b.iter(|| 1) 测试的最大差异是 0.5 ps (240.18 - 239.68)。这是 0.5 ps 的“测量”差异。那是 500 飞秒,或 0.0005 纳秒。

    与我们做“实际”“工作”时相比,这确实是一个更大的差异。这是纯粹的噪音。


    您说的是复制 4 个字节。即使“每微秒都很重要”,这也无法衡量。仅此一项无法以微秒为单位进行测量,也无法以纳秒为单位。

    (我将避免重复在 cmets 中已经说过的话。)

    如果你不想使用TryInto,那么你可以使用一些好的旧位解包和位移。 (越界访问会引起恐慌。)

    let i = (buf[1] as i32) |
            (buf[2] as i32) <<  8 |
            (buf[3] as i32) << 16 |
            (buf[4] as i32) << 24;
    println!("{}", i);
    // Prints `302055424`
    

    或者,您也可以将buf 重新解释为*const i32 指针并取消引用它。但是,取消引用指针是unsafe。 (同样,越界访问可能引起恐慌。)

    // let i = unsafe { &*((buf.as_ptr().add(1)) as *const i32) };
    let i = unsafe { &*((buf.as_ptr().offset(1)) as *const i32) };
    println!("{:?}", i);
    // Prints `302055424`
    

    因此,您需要复制 4 个字节的最佳性能解决方案。好的,让我们采用 John Kugelman 的解决方案和前 2 个解决方案并对其进行基准测试。

    // benches/bench.rs
    #![feature(test)]
    
    extern crate test;
    use test::Bencher;
    
    use std::convert::TryInto;
    
    #[bench]
    fn bench_try_into(b: &mut Bencher) {
        b.iter(|| {
            let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
            i32::from_ne_bytes((&buf[1..5]).try_into().unwrap())
        });
    }
    
    #[bench]
    fn bench_reinterpret(b: &mut Bencher) {
        b.iter(|| {
            let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
            unsafe { &*((buf.as_ptr().offset(1)) as *const i32) }
        });
    }
    
    #[bench]
    fn bench_bit_unpack(b: &mut Bencher) {
        b.iter(|| {
            let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
            (buf[1] as i32) | (buf[2] as i32) << 8 | (buf[3] as i32) << 16 | (buf[4] as i32) << 24
        });
    }
    

    现在让我们通过执行cargo +nightly bench 来进行基准测试。

    running 3 tests
    test bench_bit_unpack  ... bench:           0 ns/iter (+/- 0)
    test bench_reinterpret ... bench:           0 ns/iter (+/- 0)
    test bench_try_into    ... bench:           0 ns/iter (+/- 0)
    

    就像我推测的那样,复制 4 个字节是不可测量的。


    现在,让我们尝试使用criterion 进行基准测试。也许test crate 是(现实的并且)限于纳秒,谁知道呢。

    // benches/bench.rs
    use criterion::{criterion_group, criterion_main, Criterion};
    use std::convert::TryInto;
    
    fn criterion_benchmark(c: &mut Criterion) {
        c.bench_function("try_into", |b| {
            b.iter(|| {
                let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
                i32::from_ne_bytes((&buf[1..5]).try_into().unwrap())
            })
        });
    
        c.bench_function("reinterpret", |b| {
            b.iter(|| {
                let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
                unsafe { &*((buf.as_ptr().offset(1)) as *const i32) }
            })
        });
    
        c.bench_function("bit_unpack", |b| {
            b.iter(|| {
                let buf: [u8; 10] = [0, 0, 0, 1, 0x12, 14, 50, 120, 250, 6];
                (buf[1] as i32) | (buf[2] as i32) << 8 | (buf[3] as i32) << 16 | (buf[4] as i32) << 24
            })
        });
    }
    
    criterion_group!(benches, criterion_benchmark);
    criterion_main!(benches);
    
    # Cargo.toml
    [dev-dependencies]
    criterion = "0.3.3"
    
    [[bench]]
    name = "bench"
    harness = false
    

    现在,让我们通过执行cargo bench 来进行基准测试。

    try_into                time:   [239.69 ps 239.79 ps 239.91 ps]
                            change: [+0.0101% +0.0700% +0.1316%] (p = 0.02 < 0.05)
                            Change within noise threshold.
    Found 14 outliers among 100 measurements (14.00%)
      3 (3.00%) low mild
      4 (4.00%) high mild
      7 (7.00%) high severe
    
    reinterpret             time:   [239.63 ps 239.70 ps 239.78 ps]
                            change: [-0.7006% -0.2163% +0.0525%] (p = 0.45 > 0.05)
                            No change in performance detected.
    Found 11 outliers among 100 measurements (11.00%)
      4 (4.00%) high mild
      7 (7.00%) high severe
    
    bit_unpack              time:   [239.65 ps 239.74 ps 239.84 ps]
                            change: [-0.0768% +0.0775% +0.2867%] (p = 0.45 > 0.05)
                            No change in performance detected.
    Found 12 outliers among 100 measurements (12.00%)
      1 (1.00%) low mild
      3 (3.00%) high mild
      8 (8.00%) high severe
    

    test #[bench] criterion
    try_into 0 ns 239.79 ps
    reinterpret 0 ns 239.70 ps
    bit unpack 0 ns 239.74 ps

    因此,平均测量值为 239.79 ps、239.70 ps 和 239.74 ps。所以最大的“测量”差异是 0.09 ps。那是 90 飞秒,或 0.00009 纳秒。再次运行基准测试,将产生不同的结果。以复制 4 个字节来衡量独立的东西是不现实的。

    当然,在那一刻“重新解释”是“最快的”,但我们远远低于纳秒,这只是纯粹的噪音。

    使用您喜欢的解决方案,它们之间没有任何可衡量或显着的性能差异。


    为了好玩,将所有测试更改为b.iter(|| 1),您将收到类似的结果,在皮秒内波动。

    c.bench_function("1", |b| b.iter(|| 1_i32));
    c.bench_function("2", |b| b.iter(|| 1_i32));
    c.bench_function("3", |b| b.iter(|| 1_i32));
    

    运行基准测试将产生类似的结果。我运行了一次,得到了 240.18 ps、239.73 ps 和 239.68 ps。这是 0.5 ps 的“测量”差异。那是 500 飞秒,或 0.0005 纳秒。

    与我们做“实际”“工作”时相比,这确实是一个更大的差异。同样,这是纯粹的噪音。在任何重要的方面,这都不足以衡量“工作”。

    同样,使用您喜欢的解决方案,它们之间没有任何可衡量或显着的性能差异。

    【讨论】:

    • 是的,我知道,但通常问题是在分配后访问新内存时(这是有多少类似克隆的功能起作用),那么在这种情况下,将物理内存分配给虚拟内存的延迟内存在数百纳秒范围内,因此在高频交易中发现的零分配编程模型是我真正感兴趣的东西。但是对这个问题的密切投票证实,我应该坚持当前的简化而不是迷惑读者
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-10-28
    • 2014-04-14
    • 2013-08-23
    • 1970-01-01
    • 2016-02-06
    • 1970-01-01
    相关资源
    最近更新 更多