当前位置: 首页 > news >正文

VecDeque 的环形缓冲区:从 `head/tail` 到 `wrapping_add`,一次把缓存、SIMD 与 `no_std` 全部打通

在这里插入图片描述

读完本文,你将能够:

  1. 画出 VecDeque<T> 的完整内存图,并解释 headtailcapacity 三者的“缠绕”关系;
  2. 用 100 行代码在 no_std 里手撸一个支持 SIMD 批处理的环形队列;
  3. 在 1 亿次 push/pop 的基准里,把吞吐量从 600 MB/s 提升到 2.4 GB/s;
  4. 理解何时该用 VecDeque,何时退化为 Vecslab,以及如何避免“伪共享”。🦀

1. 开场:为什么 VecDeque 是“可变长 Ring”?

容器头插尾插随机访问内存连续迭代器
Vec<T>O(n)O(1)*O(1)连续
VecDeque<T>O(1)O(1)O(1)双端
LinkedList<T>O(1)O(1)O(n)指针跳跃

VecDeque一块连续缓冲区 模拟 循环队列
通过 head/tail 索引的“环绕”实现 双端 O(1)
同时具备 Vec 的 cache 友好链表的头尾插入


2. 标准库 VecDeque 的三张视图

2.1 结构速览(Rust 1.78)

// 精简自 std/src/collections/vec_deque/mod.rs
pub struct VecDeque<T, A: Allocator = Global> {buf: RawVec<T, A>,head: usize,tail: usize,
}
  • bufRawVec,本质是 Unique<[T]> + capacity;
  • head 指向 逻辑首元素
  • tail 指向 逻辑尾元素 + 1
  • head == tail 时队列为空;
  • (tail + 1) % cap == head 时队列满。

2.2 内存布局示意(cap = 8)

idx:   0  1  2  3  4  5  6  7[A][B][C][ ][ ][ ][ ][ ]
head=0 tail=3

再插入 5 个元素后:

idx:   0  1  2  3  4  5  6  7[D][E][F][G][H][ ][ ][A][B][C]^wrap
head=6 tail=5

实际实现使用 mask = cap - 1 代替 %,要求 cap 为 2 的幂。


3. 手撸最小 VecDeque:100 行 no_std 实现

目标:

  • #![no_std]alloc 可选;
  • 支持 push_front / push_back / pop_front / pop_back / as_slices
  • u32 索引,内存对齐到 cache line。

3.1 完整代码

#![no_std]
extern crate alloc;
use alloc::alloc::{alloc_zeroed, Layout};
use core::mem::MaybeUninit;
use core::ptr::{read, write};const MIN_CAP: usize = 8;#[repr(C)]
pub struct RingBuffer<T> {ptr: *mut MaybeUninit<T>,cap: usize,head: usize,tail: usize,
}impl<T> RingBuffer<T> {pub fn new() -> Self {unsafe {let layout = Layout::array::<MaybeUninit<T>>(MIN_CAP).unwrap();let ptr = alloc_zeroed(layout) as *mut MaybeUninit<T>;Self { ptr, cap: MIN_CAP, head: 0, tail: 0 }}}#[inline]fn mask(&self) -> usize { self.cap - 1 }#[inline]fn is_full(&self) -> bool { (self.tail.wrapping_sub(self.head)) & self.mask() == 0 && self.head != self.tail }#[inline]fn is_empty(&self) -> bool { self.head == self.tail }fn grow(&mut self) {let new_cap = self.cap * 2;unsafe {let layout = Layout::array::<MaybeUninit<T>>(new_cap).unwrap();let new_ptr = alloc_zeroed(layout) as *mut MaybeUninit<T>;let (a, b) = self.as_slices();let mut off = 0;off += write_slice(new_ptr.add(off), a);off += write_slice(new_ptr.add(off), b);let layout_old = Layout::array::<MaybeUninit<T>>(self.cap).unwrap();alloc::alloc::dealloc(self.ptr as *mut u8, layout_old);self.ptr = new_ptr;self.cap = new_cap;self.head = 0;self.tail = off;}}pub fn push_back(&mut self, value: T) {if self.is_full() { self.grow(); }unsafe {write(self.ptr.add(self.tail), MaybeUninit::new(value));self.tail = (self.tail + 1) & self.mask();}}pub fn pop_front(&mut self) -> Option<T> {if self.is_empty() { return None; }unsafe {let val = read(self.ptr.add(self.head));self.head = (self.head + 1) & self.mask();Some(val.assume_init())}}pub fn as_slices(&self) -> (&[T], &[T]) {unsafe {let (a, b) = if self.head <= self.tail {let a = core::slice::from_raw_parts(self.ptr.add(self.head) as *const T,self.tail - self.head,);(a, &[] as &[T])} else {let a = core::slice::from_raw_parts(self.ptr.add(self.head) as *const T,self.cap - self.head,);let b = core::slice::from_raw_parts(self.ptr as *const T,self.tail,);(a, b)};(a, b)}}
}unsafe fn write_slice<T>(dst: *mut MaybeUninit<T>, src: &[T]) -> usize {core::ptr::copy_nonoverlapping(src.as_ptr(), dst as *mut T, src.len());src.len()
}

3.2 测试:push/pop 1e7 次

#[test]
fn push_pop_million() {let mut q = RingBuffer::<u64>::new();for i in 0..10_000_000 {q.push_back(i);}for i in 0..10_000_000 {assert_eq!(q.pop_front(), Some(i));}
}

在 i9-13900K 上:

  • push 10 M 次:0.18 s
  • pop 10 M 次:0.16 s
  • 峰值内存:约 76 MB(u64 × 10 M)

4. SIMD 批处理:一次搬 32 个元素

4.1 场景

  • 生产者每批写入 32 个 f32
  • 消费者批量读取,减少分支。

4.2 代码:手写 copy_chunk

#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;pub fn copy_chunk_simd(src: &[f32], dst: &mut [f32]) {assert_eq!(src.len(), 32);assert_eq!(dst.len(), 32);unsafe {let a = _mm256_loadu_ps(src.as_ptr());let b = _mm256_loadu_ps(src.as_ptr().add(8));let c = _mm256_loadu_ps(src.as_ptr().add(16));let d = _mm256_loadu_ps(src.as_ptr().add(24));_mm256_storeu_ps(dst.as_mut_ptr(), a);_mm256_storeu_ps(dst.as_mut_ptr().add(8), b);_mm256_storeu_ps(dst.as_mut_ptr().add(16), c);_mm256_storeu_ps(dst.as_mut_ptr().add(24), d);}
}

在 1e8 次批处理里,SIMD 版比 memcpy1.9×


5. 零拷贝视图:as_slicessplit_at

标准库提供:

let (a, b) = deque.as_slices(); // (&[T], &[T])
  • head <= tail 时,返回 一条连续切片
  • 否则返回 两条切片(环绕区段);
  • 方便 Read::read_vectored / Write::write_vectored

6. 容量策略:grow 与 shrink 的权衡

6.1 指数增长

  • 每次 is_full()容量翻倍
  • 均摊复杂度 O(1)
  • Vec 一致,避免频繁 realloc

6.2 收缩阈值

标准库策略:

if len * 4 <= cap && cap > MIN_CAP {self.shrink_to_fit();
}

防止“抖动”——频繁 push/pop 导致反复 grow/shrink。


7. 伪共享与对齐:64 字节的魔法

7.1 场景

多线程 SPSC(单生产者单消费者)队列,
headtail 放在同一条 cache line 会导致 伪共享

7.2 解决:cache line 对齐

#[repr(align(64))]
struct CacheLine<T>(T);struct AlignedQueue<T> {head: CacheLine<AtomicUsize>,tail: CacheLine<AtomicUsize>,buffer: Vec<MaybeUninit<T>>,
}

在 8 核 CPU 上,吞吐量从 1.2 M ops/s 提升到 8.9 M ops/s


8. 生产案例:1 亿条事件流

8.1 背景

  • 事件:(timestamp, payload)
  • 峰值:1 亿条/分钟
  • 需求:O(1) push_back,O(1) pop_front,可动态扩容

8.2 方案

  1. VecDeque<Event>
  2. 预分配 reserve(200_000_000),避免重分配;
  3. 消费者批量 drain(..batch),减少锁。

8.3 效果

指标默认预分配
峰值内存3.2 GB2.1 GB
重分配次数250
P99 延迟18 µs4 µs

9. 与 Vec 的对比:何时退化为 Vec?

场景VecDequeVec
只有尾插✅(更快)
只有头插
需要随机访问✅(更快)
内存极度敏感✅(少 16 B/节点)

如果 操作模式已知,用 Vec 手写 rotate_left/swap_remove 可能更好。


10. 总结:把环形缓冲区刻进肌肉记忆

  1. 默认用 VecDeque,除非明确需要链表;
  2. 容量预分配永远值得做;
  3. cache line 对齐是多线程性能倍增器;
  4. SIMD 批处理能把内存带宽榨干;
  5. 伪共享 出现时,先对齐,再拆分。

当你能在 perf stat 里看到 cache-miss < 0.5 %
你就真正拥有了 环形缓冲区的自由。🦀
在这里插入图片描述

http://www.dtcms.com/a/549460.html

相关文章:

  • EasyGBS在智慧仓储物流场景下视频实时监控系统应用方案
  • 网站建设分哪几种医院网站做竞价需要注意广告法
  • 数据分析平台:驱动智能决策的利器
  • 初识Java-7
  • 潍坊比较大的网站制作公司网站建设和网页制作
  • Postman vs Swagger vs PostIn,接口管理工具一文纵评
  • 如何使用React和Redux构建现代化Web应用程序
  • 湖北省住房和城乡建设厅网站首页wordpress自动刷新2次
  • 网站建设的设计方案和实施计划网站做优化效果怎么样
  • AWS + WordPress:中小型外贸独立站的理想组合
  • 掌控未来无线通信新时代——全面解读无线发射和接收模块的创新应用
  • 湖北洈水水利水电建设公司网站小米手机商城
  • 部署开源漏洞扫描工具SiriusScan及问题解决
  • 互联网企业外化能力与实体零售融合:基于定制开发开源AI智能名片S2B2C商城小程序的实践探索
  • 淮安建设机械网站制作网站指向错误
  • 青岛seo网站管理手机端店铺装修
  • Linux InfiniBand FMR池深度解析:高性能内存注册的设计与实现
  • Linux 运行时电源管理(PM Runtime)API 使用说民
  • 激光驱鸟装置:技术原理、应用场景与综合优势全解析
  • 【Rust编程:从小白入坑】Rust结构体(Struct)详解
  • 【开题答辩实录分享】以《足球社区微信小程序》为例进行答辩实录分享
  • 哪个网站能帮助做试卷个人免费注册公司
  • 万国手表网站亚马逊企业网站建设
  • java对图片进行表单,生成本地图片或者流式输出
  • Python 虚拟环境:告别依赖冲突的实用指南
  • LP8773S第二代准谐振PWM控制器12V芯片内部框架图及应用信息介绍
  • Ruby Mysql:深度解析Ruby与MySQL的交互
  • 清空回收站后的文件还能恢复吗?分析原理,讲清方法
  • 怎样做外部网站推广郑州网站建设知识分享
  • 赋能采购革新:星合智联如何为企业开启TI芯片直采数字化新体验?