循环buffer,即环形缓冲区,设有固定的大小,被定义成一个环形,新数据会覆盖旧的数据,减少内存拷贝,提高程序的性能。适用于通信上接收流式数据,然后进行分片、组包。

    逻辑示意图:

buffer python应用 buffer for_数据

图1

    小C自己实现的环形Buffer,“一写一读”,支持多线程且无锁设计,支持随机长度读写。测试性能:500M 数据用时=1382110us(1.38秒)  速率=2.83Gbps。

    程序设计:

    虽然该程序被称为环形Buffer,实际上在内存中就是一个大的数组,通过对指向数组的位置进行管理,设计成一个环形的队列。

    注:下文所述的指针不是常规C、C++上的(char* p, int* pi)这种指针,而是相对于一个数组起始位置的偏移。例如char buf[32], 偏移10个字节,用一个int pos变量去指向第10字节位置,这个pos在文中被称为指针。

//例如:
char buffer[32];
int pos=10;
buffer[10] == *(buffer+pos)//这两个是完全相等的//这个pos在文中被称为指针,即偏移,此处指针值为10
  1. 写入数据设计示意图
        如图2所示,当我们要写入数据时,我们会关注写指针的位置,还有能写入的空间,图中的白色与黄色区域均是可以写入数据的,黄色区域已经读取了数据,即可被覆盖。
  1. 写指针在前(相对于读指针来看)

buffer python应用 buffer for_数据结构_02

图2 写指针在前

        2.写指针在后(相对于读指针位置来看)

    如图3所示,此时写指针已经写的大小是10+3个区域大小,即已经走了一圈且再次走到3这个位置了。现在可以写入的数据大小即是黄色的区域。

buffer python应用 buffer for_buffer python应用_03

图3 写指针在后

综上两种情况,要计算每次可写入数据的长度方式如下:

int wpos;//写指针
int rpos;//读指针
int buf_size; //整个buffer的大小
int available_len = ((rpos - wpos) +buf_size) % buf_size;
//计算可写入数据长度
//使用读指针位置减去写指针的位置加上总长,就保证该值是一个正数
//然后再对这个数进行取余,保证整个大小是在buf_size的范围内
//这样即算出可使用的空间大小
/******************************************************************/
//写函数实现
/******************************************************************/
int Ringbuffer::write(uint8_t* buffer, int len)
{
    register int available_len = ((rpos-wpos)+buffer_size)&buffer_size_mask;
    if(available_len <= len){
        if(!is_init){
            is_init = true;
            goto run;
        }
        return -1;
    }
run:
    register int left_len = MIN(len, buffer_size-wpos);
    ::memcpy(m_buffer+wpos, buffer, left_len);
    ::memcpy(m_buffer,buffer+left_len, len-left_len);
    
    smp_mb();
    wpos += len;
    wpos &= buffer_size_mask;
    return len;
}
//wpos &= buffer_size_mask;  ==> wpos %= buffer_size_mask;
//因为buffer长度传入时候处理为2的次幂,因此进行区域的使用可以使用&进行操作
//例如:pos % 8 ==> pos&(8-1) 即 pos&7;  
//buffer_size_mask = buffer_size -1;

    2.读入数据设计示意图

    i.读指针在后(相对于写指针位置)

        图4是读指针在后,此时可读的数据长度是2-6,即图中绿色区域。

buffer python应用 buffer for_c++_04

图4 读指针在写指针之后

    ii.读指针在后(相对于写指针位置)

        图5是读指针在前,此时可读区域7-10、0-3,即图中绿色区域。

buffer python应用 buffer for_算法_05

图5 读指针在写指针之前

(大家有没有发现图4、5 与 图2、3一样,对!就是一样的,只是现在我们要站在读指针的位置去看)

综上两种情况,要计算每次可写入数据的长度方式如下:

int wpos;//写指针
int rpos;//读指针
int buf_size; //整个buffer的大小
int available_len = ((wpos - rpos) +buf_size) % buf_size;
//计算可写入数据长度
//使用写指针位置减去读指针的位置加上总长,就保证该值是一个正数
//然后再对这个数进行取余,保证整个大小是在buf_size的范围内
//这样即算出可使用的空间大小
注:与上述计算可写长度类似,但区别是此处(写-读)
/*****************************************************************/
//读函数实现
/*****************************************************************/
int Ringbuffer::read(uint8_t* buffer, int len)
{
    register int available_len = ((wpos-rpos)+buffer_size)&buffer_size_mask;
    if(available_len <= len) return -1;
    
    register int left_len=MIN(len, buffer_size-rpos);
    ::memcpy(buffer, m_buffer+rpos, left_len);
    ::memcpy(buffer+left_len, m_buffer, len-left_len);
    smp_mb();
    rpos += len;
    rpos &= buffer_size_mask;
    return len;
}


测试思路:    

  1. 代码测试:
    使用随机生成一个500M的源数据文件,开启两个线程,一个线程从数据文件中随机长度读取数据,然后往循环buffer里面写数据,一个线程从循环buffer里面随机长度读取出数据,然后写入文件,最后使用Beyond Compare软件对两个文件进行比较,查看数据是否相同。
    测试结果:两个500M的文件数据一样,
  1. 测试速率:

buffer python应用 buffer for_数据_06

 

代码目录简介:

testfile文件夹:生成随机的源数据文件src.dat,已经测试生成的out.dat数据文件

main.cpp文件:主要程序入口

主要功能实现:ringbuffer.cpp ringbuffer.h

buffer python应用 buffer for_c++_07

 

使用:

    请先进入testfile目录使用gcc generate_bin_file.cpp -o test编译,然后执行./test,生成src.dat源数据文件。然后回到上级目录,执行make,生成mainApp, 执行mainApp便可以测试程序。运行环境:Linux系统的任意发行版本都可以。