以 `byteorder` 读取 register-sized 数据是否有效?

Is reading register-sized data in `byteorder` efficient?

标题中的箱子是byteorder

以下是我们如何从 std::io::BufReader. BufReader implements the std::io::Read trait. There is an implementation of byteorder::ReadBytesExt 中读取任何实现 Read 的类型的二进制数据。 ReadBytesExt 包含 read_u16 和其他读取二进制数据的方法。此实现:

    fn read_u16<T: ByteOrder>(&mut self) -> Result<u16> {
        let mut buf = [0; 2];
        self.read_exact(&mut buf)?;
        Ok(T::read_u16(&buf))
    }

它将对buf的引用传递给BufReader;我想它在堆栈中传递了 buf 的地址。因此,结果 u16BufReader (内存)的内部缓冲区传输到上面的 buf (内存),可能使用 memcpy 或其他东西。如果BufReader通过直接从其内部缓冲区读取数据来实现ReadBytesExt,效率会不会更高?还是编译器把buf优化掉了?

TL;DR:全看优化大神了,不过要有效率。

这里的关键优化是内联,和往常一样,概率在我们这边,但谁知道...

只要对 read_exact 的调用是内联的,它就应该可以正常工作。

首先,它可以内联。在 Rust 中,“内部”调用总是静态分派——没有继承——因此 self.read_exact 中的接收器类型 (self) 在编译时是已知的。因此,被调用的确切 read_exact 函数在编译时是已知的。

当然,是否无法确定它是否会被内联。实施时间相当短,所以机会很大,但这不在我们手中。

其次,如果内联会怎样?魔法!

可以看到实现here:

fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
    if self.buffer().len() >= buf.len() {
        buf.copy_from_slice(&self.buffer()[..buf.len()]);
        self.consume(buf.len());
        return Ok(());
    }

    crate::io::default_read_exact(self, buf)
}

内联后,我们有:

fn read_u16<T: ByteOrder>(&mut self) -> Result<u16> {
    let mut buf = [0; 2];

    //  self.read_exact(&mut buf)?;
    if self.buffer().len() >= buf.len() {
        buf.copy_from_slice(&self.buffer()[..buf.len()]);
        self.consume(buf.len());
        Ok(())
    } else {
        crate::io::default_read_exact(self, buf)
    }?;

    Ok(T::read_u16(&buf))
}

不用说,所有那些 buf.len() 调用都应该替换为 2

fn read_u16<T: ByteOrder>(&mut self) -> Result<u16> {
    let mut buf = [0; 2];

    //  self.read_exact(&mut buf)?;
    if self.buffer().len() >= 2 {
        buf.copy_from_slice(&self.buffer()[..2]);
        self.consume(2);
        Ok(())
    } else {
        crate::io::default_read_exact(self, buf)
    }?;

    Ok(T::read_u16(&buf))
}

所以我们剩下 copy_from_slice,一个 memcpy 调用 constant size (2).

诀窍在于 memcpy 非常特殊,它是大多数编译器的内置函数,当然在 LLVM 中也是如此。它是一个专门的内置函数,因此在特殊情况下——例如指定的常量大小恰好是寄存器大小——它的代码生成器可以专门用于……在 [= 的情况下的 mov 指令70=].

因此,只要 read_exact 是内联的,那么 buf 应该从头到尾都存在于一个寄存器中……在最理想的情况下。

冷路径中,当调用default_read_exact时,编译器将需要使用堆栈并传递一个切片。没关系。它不应该经常发生。


如果您发现自己反复执行 u16 读取序列,但是......您可能会发现读取更大的数组会更好,以避免重复 if self.buffer().len() >= 2 检查.