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

用户态网络缓冲区设计

基于数组实现的环形缓冲区:

优点

使用固定大小的连续空间做用户态缓冲区,利用了内存访问的局部性,可以提高缓存命中率,提高程序性能,在处理大量数据时,缓存的利用率性能有着很大的影响

正是基于性能的考虑,使用数组做用户态缓冲区,同时由于固定的空间大小,在使用数组时需要精妙的存取方式,另外,可以使用stl的vacotr的设计思路,动态增长数组的大小,这里暂不做实现

先总结一下环形缓冲区(ringbuffer)的优点:

  • 高效的内存管理: 环形缓冲区是由一块连续的内存区域组成的,这样可以减少内存碎片和内存分配的开销,提高内存管理的效率

  • 预先分配的内存: 因为环形缓冲区的大小是固定的,所以可以在系统启动时或者初始化时预先分配所需的内存,而不需要动态分配内存。这可以避免动态内存分配带来的性能开销和内存碎片问题

  • 简单的索引计算: 由于环形缓冲区的内存布局是连续的,所以索引计算非常简单和高效。相比之下,可变长链表等数据结构可能需要更复杂的指针操作和内存访问。

  • 更好的缓存性能: 环形缓冲区的连续内存布局可以提高缓存的命中率,因为它利用了局部性原理,使得相关的数据项在内存中更可能是相邻存放的。

代码实现

环形缓冲区结构体:

typedef struct ringbuffer_s {uint32_t size; // 缓冲区数组的大小uint32_t tail; // 尾部索引,即当前可用的数组位置索引uint32_t head; // 头部索引,当前已使用的空间的起始位置索引uint8_t * buf; // 实际缓冲区数组地址
} buffer_t;

其中 tail和head索引的设计 考虑到需要确定当前数组的空闲位置以及已使用的位置,便于添加新数据取出数据

创建一个缓冲区:

buffer_t * buffer_new(uint32_t sz) {    // 结构体和其成员的空间一起分配而不分别分配的原											因是 --> 利用局部性原理提高性能buffer_t * buf = (buffer_t *)malloc(sizeof(buffer_t) + sz); // 结构体 + 缓冲区if (!buf) {return NULL;}buf->size = sz;buf->head = buf->tail = 0;buf->buf = (uint8_t *)(buf + 1); // 可用缓冲区在结构体地址后return buf;
}

一个缓冲区的初始tail和head索引都是位于数组首部的

一些辅助函数:

static uint32_t
rb_isempty(buffer_t *r) {     // 缓冲区是否为空return r->head == r->tail;
}static uint32_t rb_isfull(buffer_t *r) {     // 缓冲区是否已满return r->size == (r->tail - r->head);
}static uint32_t rb_len(buffer_t *r) {      // 已使用空间return r->tail - r->head;
}static uint32_t rb_remain(buffer_t *r) {    // 剩余空间return r->size - r->tail + r->head;
}

向缓冲区内添加数据:

int buffer_add(buffer_t *r, const void *data, uint32_t sz) {if (sz > rb_remain(r)) // 如果剩余空间不足,添加失败 return -1;// 如果tail到数组尾部的空间不足以容纳该数据,分段添加到尾部和头部uint32_t i;i = min(sz, r->size - (r->tail & (r->size - 1))); // 计算将填入尾部的空间,最大是实际剩余空间// 如果需要分两次填入,一部分填入尾部,一部分填入头部memcpy(r->buf + (r->tail & (r->size - 1)), data, i);memcpy(r->buf, data+i, sz-i);r->tail = (r->tail + sz) % r->size; // 更新tail索引,可能移动到数组头部return 0;
}

环形缓冲区的添加操作使用了环绕索引,最大限度地利用有限的数组空间

从缓冲区中取出数据

int buffer_remove(buffer_t *r, void *data, uint32_t sz) {assert(!rb_isempty(r)); // 缓冲区为空,则移除失败uint32_t i;sz = min(sz, r->tail - r->head); // 确保要移除的长度不超过已使用的空间// 根据长度分次从尾部、头部移除i = min(sz, r->size - (r->head & (r->size - 1)));memcpy(data, r->buf+(r->head & (r->size - 1)), i);memcpy(data+i, r->buf, sz-i);r->head = (r->head + actual_sz) % r->size; // 更新head,可能移动到数组头部return sz;
}

更新head的索引也用到了环绕的方法

删除一段数据:

int buffer_drain(buffer_t *r, uint32_t sz) {if (sz > rb_len(r)) // 最多全部删除sz = rb_len(r);r->head = (r->head + sz) % r->size; // 更新索引,使用环绕的方法return sz;
}

获取当前最大可用空间的长度:

uint8_t *buffer_write_atmost(buffer_t *r) {uint32_t wpos = r->tail;uint32_t rpos = r->head;if (wpos >= rpos) {// Case 1: tail is ahead of or equal to headuint32_t first_chunk = r->size - wpos;   // Space from tail to end of bufferuint32_t second_chunk = rpos;            // Space from start of buffer to headreturn r->buf + wpos;} else {// Case 2: head is ahead of tailreturn r->buf + wpos;}}
buffer_write_atmost函数逻辑
  • 如果 tailhead 之前(即 tail < head),则从 tailhead 之间的空间是可写的,大小为 head - tail - 1
  • 如果 tailhead 之后(即 tail >= head),则从 tail 到缓冲区末尾的空间以及从缓冲区头部到 head 之间的空间都是可写的,需要分两段来计算最大可写空间,返回 first_chunk + second_chunk - 1
    head 之前(即 tail < head),则从 tailhead 之间的空间是可写的,大小为 head - tail - 1
  • 如果 tailhead 之后(即 tail >= head),则从 tail 到缓冲区末尾的空间以及从缓冲区头部到 head 之间的空间都是可写的,需要分两段来计算最大可写空间,返回 first_chunk + second_chunk - 1

至此,已经实现了环形缓冲区的创建、添加、删除操作

推荐学习 https://xxetb.xetslk.com/s/p5Ibb

相关文章:

  • JavaEE技术之分布式事务(理论、解决方案、Seata解决分布式事务问题、Seata之原理简介、断点查看数据库表数据变化)
  • 51汇编--AD和DA
  • 淄博公司商标驳回复审条件及流程
  • WPS PPT学习笔记 1 排版4原则等基本技巧整理
  • 智能农业时代:智能生态网络与数据流通的融合
  • AI三级淋巴结构·预测癌症预后和免疫疗法反应
  • 【MySQL精通之路】InnoDB(3)-MVCC多版本管理
  • 分布式理论--BASE
  • SpringBoot中注解@RestController | @ResponseBody | @Controller
  • SD00HA 集成电路IC电压负载开关USB电源降压SOT23-5封装
  • 【网站项目】SpringBoot380百天公司财务管理系统
  • GMSL图像采集卡,适用于无人车、自动驾驶、自主机器、数据采集等场景,支持定制
  • 软考 软件设计师 场景分析题 速成篇
  • linux-x86_64-musl 里面的musl是什么意思?
  • Color预设颜色测试
  • Angular js 常用指令ng-if、ng-class、ng-option、ng-value、ng-click是如何使用的?
  • Docker容器管理
  • HTTP请求重发
  • Java知识点总结(JavaIO-打印流)
  • js如何打印object对象
  • Mocha测试初探
  • Quartz实现数据同步 | 从0开始构建SpringCloud微服务(3)
  • vue+element后台管理系统,从后端获取路由表,并正常渲染
  • 将回调地狱按在地上摩擦的Promise
  • 跨域
  • 目录与文件属性:编写ls
  • 如何使用 OAuth 2.0 将 LinkedIn 集成入 iOS 应用
  • 如何用Ubuntu和Xen来设置Kubernetes?
  • 三分钟教你同步 Visual Studio Code 设置
  • 使用common-codec进行md5加密
  • 世界上最简单的无等待算法(getAndIncrement)
  • 线性表及其算法(java实现)
  • 消息队列系列二(IOT中消息队列的应用)
  • 写代码的正确姿势
  • 原生 js 实现移动端 Touch 滑动反弹
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • #NOIP 2014# day.2 T2 寻找道路
  • (LeetCode 49)Anagrams
  • (Oracle)SQL优化基础(三):看懂执行计划顺序
  • (TOJ2804)Even? Odd?
  • (超简单)构建高可用网络应用:使用Nginx进行负载均衡与健康检查
  • (附源码)spring boot基于Java的电影院售票与管理系统毕业设计 011449
  • (含react-draggable库以及相关BUG如何解决)固定在左上方某盒子内(如按钮)添加可拖动功能,使用react hook语法实现
  • (强烈推荐)移动端音视频从零到上手(上)
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (十六)一篇文章学会Java的常用API
  • (四)JPA - JQPL 实现增删改查
  • (一)基于IDEA的JAVA基础12
  • (转)Google的Objective-C编码规范
  • (转)Oracle存储过程编写经验和优化措施
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • ***微信公众号支付+微信H5支付+微信扫码支付+小程序支付+APP微信支付解决方案总结...
  • .NET CORE 3.1 集成JWT鉴权和授权2
  • .NET Core 通过 Ef Core 操作 Mysql
  • .NET Framework 4.6.2改进了WPF和安全性