bytes库Rust网络编程中使用比较频繁,其提供了和字节数组相关的一组抽象,能够极大避免内存拷贝。提供的主要组件如下:
- Bytes结构: 底层封装了一段连续的内存,提供了引用计数跟踪内存的生命周期,类似
&'static [u8]
,可供多个实例共享。 - Buf/BufMut trait: 对逻辑上连续,但是底层表示可能不连续的Buffer抽象,分别类似
&[&[u8]]
和&[&mut[u8]]
。 - BytesMut结构:封装了一段可以修改但是后续可以生成Bytes共享出去的的连续内存。
下面主要对Bytes进行分析,Buf/BufMut本身只是trait定义,一看就懂;为减少API,加快稳定,后面可能会移除掉BytesMut,所以有空再作分析。
Bytes内存布局
下面是Bytes的大致内存布局图。底层的连续内存使用引用计数,每个实例持一个引用,以及记录本身所占有的区间。
![ea4bfc2decccffdab9240b05e321b8ad.png](https://img-blog.csdnimg.cn/img_convert/ea4bfc2decccffdab9240b05e321b8ad.png)
最简单的实现差不多是:
struct Bytes {
raw: Arc<[u8]>,
data: usize,
len: usize,
}
不过考虑到还有两类可优化的情况:
- 底层的内存来自于
&'static [u8]
,那么本身就是可共享的,拷贝进Arc里,再进行引用计数,太浪费。 - 底层的内存来自
Vec<u8>
或者Box<[u8]>
,且还没有发生共享只有一个Bytes实例的情况,如果这种情况能优化掉,那么Bytes的使用开销和Vec就几乎无差别了。
对这两种情况的优化是Bytes的主要看点。
Bytes结构定义
要考虑到底层内存的多样性,必然要使用多态。Bytes采用自定义虚表的方式实现多态。
pub struct Bytes {
// Bytes可访问的起始地址
ptr: *const u8,
// Bytes可访问的数据长度
len: usize,
// 底层的对象指针
data: AtomicPtr<()>,
// 底层对象的虚表指针
vtable: &'static Vtable,
}
pub(crate) struct Vtable {
/// fn(data, ptr, len)
pub clone: unsafe fn(&AtomicPtr<()>, *const u8, usize) -> Bytes,
/// fn(data, ptr, le