将环形缓冲区(循环缓冲区)高效重新格式化为连续阵列

时间:2017-07-05 15:00:37

标签: c++ arrays pointers buffer circular-buffer

我已经成功实现了一个函数,它可以从环形缓冲区中的任意点开始复制任意数量的值到连续数组,但我想提高它的效率。这是我的代码的最小示例:

#include <string.h>
#include <iostream>
#include <chrono>
#include <thread>

using namespace std;


/*Foo: a function*/
void Foo(int * print_array, int print_amount){
    /*Simulate overhead*/
    this_thread::sleep_for(chrono::microseconds(1000));
    int sum = 0;
    for (int i = 0; i < print_amount; i++){
        sum += print_array[i];          //Linear operation
//      cout << print_array[i] << " "; //Uncomment to check if correct funtionality
    }
}

/*Example function*/
int main(){
    /*Initialze ring buffer*/
    int ring_buffer_elements = 32;                              //A largeish size
    int ring_buffer_size = ring_buffer_elements * sizeof(int);
    int * ring_buffer = (int *) malloc(ring_buffer_size);
    for (int i = 0; i < ring_buffer_elements; i++)
        ring_buffer[i] = i;                                     //Fill buffer with ordered numbers

    /*Initialze array*/
    int array_elements = 16;                                    //A smaller largeish size
    int array_size = array_elements * sizeof(int);
    int * array = (int *) malloc(array_size);

    /*Set reference pointers*/
    int * start_pointer = ring_buffer;
    int * end_pointer = ring_buffer + ring_buffer_elements;

    /*Set moving copy pointer*/
    int * copy_pointer = start_pointer;

    /*Set "random" amount to be copied at each iteration*/  
    int copy_amount = 11;

    /*Set loop amount to check functionality or run time*/
    int loop_amount = 1000;                                 //Set lower if checking functionality

    /***WORKING METHOD***/
    /*Start timer*/
    auto start_time = chrono::high_resolution_clock::now();

    /*"Continuous" loop*/
    for (int i = 0; i < loop_amount; i++){
        /*Copy loop*/
        for (int j = 0; j < copy_amount; j++){
            array[j] = *copy_pointer;           //Copy value from ring buffer
            copy_pointer++;                     //Move pointer
            if (copy_pointer >= end_pointer)    
                copy_pointer = start_pointer;   //Reset pointer if reached end of ring buffer
        }
        Foo(array, copy_amount);    //Call a function
    }

    /*Check run time*/
    chrono::duration<double> run_time_ticks = chrono::high_resolution_clock::now() - start_time;
    double run_time = run_time_ticks.count();

    /*Print result*/
    cout << endl << run_time << endl;


/***NAIVE METHOD***/
    /*Reset moving pointer*/
    copy_pointer = start_pointer;

    /*Start timer*/
    start_time = chrono::high_resolution_clock::now();

    /*"Continuous" loop*/
    for (int i = 0; i < loop_amount; i++){
        /*Compute how many elements must be copied after reaching end of ring buffer*/
        int copy_remainder = copy_pointer + copy_amount - end_pointer; //Ugly pointer arithmetic?
        /*Check if we need to loop back or not*/
        if (copy_remainder <= 0){
            Foo(copy_pointer, copy_amount); //Call function
            copy_pointer += copy_amount;    //Move pointer
        } else {
            Foo(copy_pointer, copy_amount-copy_remainder);  //Call function with part of values from copy pointer
            Foo(start_pointer, copy_remainder);             //Call function with remainder of values from start of ring buffer
            copy_pointer = start_pointer + copy_remainder;  //Move pointer
        }
    }

    /*Check run time*/
    run_time_ticks = chrono::high_resolution_clock::now() - start_time;
    run_time = run_time_ticks.count();

    /*Print result*/
    cout << endl << run_time << endl;


    /***memcpy METHOD***/
    /*Reset moving pointer*/
    copy_pointer = start_pointer;

    /*Initialize size reference*/
    int int_size = (int) sizeof(int);

    /*Start timer*/
    start_time = chrono::high_resolution_clock::now();

    /*"Continuous" loop*/
    for (int i = 0; i < loop_amount; i++){
        /*Compute how many elements must be copied after reaching end of ring buffer*/
        int copy_remainder = copy_pointer + copy_amount - end_pointer; //Ugly pointer arithmetic?
        /*Check if we need to loop back or not*/
        if (copy_remainder <= 0){
            memcpy(array, copy_pointer, copy_amount*int_size);  //Use memcpy
            copy_pointer += copy_amount;                        //Move pointer
        } else {
            memcpy(array, copy_pointer, (copy_amount-copy_remainder)*int_size);                 //Use memcpy with part of values from copy pointer
            memcpy(array+(copy_amount-copy_remainder), start_pointer, copy_remainder*int_size); //Use memcpy wih remainder of values from start of ring buffer
            copy_pointer = start_pointer + copy_remainder;                                      //Move pointer
        }
        /*Call a function*/
        Foo(array, copy_amount);
    }

    /*Check run time*/
    run_time_ticks = chrono::high_resolution_clock::now() - start_time;
    run_time = run_time_ticks.count();

    /*Print result*/
    cout << endl << run_time << endl;

}

环形缓冲区用于不断更新的音频数据流,因此必须将引入的延迟量保持在最低限度,为什么我要尝试改进它。

我在考虑复制WORKING METHOD中的值是多余的,应该可以只通过原始的环形缓冲区数据。我这样做的天真方法是使用原始数据进行写入,并且每当数据循环再次写入时(参见NAIVE IMPROVEMENT)。

实际上,在这个最小的例子中,这种改进的速度要快几个数量级。但是,在我的实际应用程序中, Foo 被替换为写入硬件缓冲区并具有相当大的开销的函数 - 最终结果比WORKING METHOD代码慢,这意味着我永远不应该使用它(或在这种情况下为Foo)不止一次(每次写入音频数据)。 (编辑模拟开销被添加到Foo以准确描述此问题。)

因此,我的问题是,是否有更快捷的方法将数据从环形缓冲区复制到单个连续数组?

(另外,每次写入时,环形缓冲区永远不需要循环回多次:copy_amount总是小于ring_buffer_elements)

谢谢!

修改 根据Passer By的建议,用最少的例子替换原始代码片段。

编辑2 根据duong_dajgja的建议添加了模拟开销和memcpy。在该示例中,memcpy方法和工作方法具有基本相同的性能(后者具有某种优势)。在我的应用程序中,当使用最小的缓冲区时,memcpy比工作方法快3-4%。如此之快,但令人遗憾的是远非重要。

1 个答案:

答案 0 :(得分:0)

我想建议一个类似于环形缓冲区的结构,实际上是一个数组。

由于您的数据是音频流,所以我认为数据会按顺序排列(例如时间序列数据)。

假设buff具有100个元素的容量。一旦buff变满并且你想在结尾添加一个新元素,你首先必须使用memmove将buff从缓冲区[1]左移到buff [99],然后简单地将新元素放在抛光轮[99]。这样做可以确保您的环形缓冲区始终是具有正确顺序的数组(从buff [0]到buff [99])。现在,根据需要简单memcpy从buff [0]到buff [99]到目标数组的整个区域。