【问题标题】:improving C circular buffer efficiency提高 C 循环缓冲区效率
【发布时间】:2012-03-15 10:44:43
【问题描述】:

我需要一些帮助来提高我的循环缓冲区代码的效率。

我查看了stackoverflow,发现(几乎)所有关于循环缓冲区的主题都是关于这种缓冲区的使用或循环缓冲区的基本实现。我真的需要有关如何使其超级高效的信息。

计划将此缓冲器与具有单精度 FPU 的 STM32F4 微控制器一起使用。 我计划大量使用 write() 和 readn() 函数。我们在这里实际上是在谈论每秒几百万次调用,所以在这里减少几个时钟周期,真的会有所作为。

我会把最重要的代码放在这里,完整的缓冲区代码可以通过http://dl.dropbox.com/u/39710897/circular%20buffer.rar获得

谁能给我一些关于如何提高这个缓冲区效率的建议?

#define BUFF_SIZE 3             // buffer size set at compile time

typedef struct buffer{
    float buff[BUFF_SIZE];
    int readIndex;
    int writeIndex;
}buffer;

/********************************\
* void write(buffer* buffer, float value)
* writes value into the buffer
* @param buffer* buffer
*   pointer to buffer to be used
* @param float value
*   valueto be written in buffer
\********************************/
void write(buffer* buffer,float value){
    buffer->buff[buffer->writeIndex]=value;
    buffer->writeIndex++;
    if(buffer->writeIndex==BUFF_SIZE)
        buffer->writeIndex=0;
}

/********************************\
* float readn(buffer* buffer, int Xn)
* reads specified value from buffer
* @param buffer* buffer
*   pointer to buffer to be read from
* @param int Xn
*   specifies the value to be read from buffer counting backwards from the most recently written value
*   i.e. the most recently writen value can be read with readn(buffer, 0), the value written before that with readn(buffer, 1)
\********************************/
float readn(buffer* buffer, int Xn){
    int tempIndex;

    tempIndex=buffer->writeIndex-(Xn+1);
    while(tempIndex<0){
        tempIndex+=BUFF_SIZE;
    }

    return buffer->buff[tempIndex];
}

【问题讨论】:

  • readindex 确实没有在上面列出的函数中使用。它在 read() 函数中使用,可以在附加的 rar 文件中找到。此处列出的 readn() 函数用于从缓冲区中读取特定值(即倒数第二个写入的值)

标签: c embedded circular-buffer


【解决方案1】:

正如“Oli Charlesworth”所建议的那样 - 如果您的缓冲区大小是 2 的幂,您将能够简化事情。我想编写读/写函数体,这样意图就更清楚了。

#define BUFF_SIZE (4U)
#define BUFF_SIZE_MASK (BUFF_SIZE-1U)

struct buffer {
    float buff[BUFF_SIZE];
    unsigned writeIndex;
};

void write(struct buffer *buffer, float value) {
    buffer->buff[(++buffer->writeIndex) & BUFF_SIZE_MASK] = value;
}

float readn(struct buffer *buffer, unsigned Xn){
    return buffer->buff[(buffer->writeIndex - Xn) & BUFF_SIZE_MASK];
}

一些解释。请注意,根本没有分支 (if)。我们不将数组索引限制为数组边界,而是将其与掩码进行 AND-ing。

【讨论】:

  • 一旦您使用位掩码,您应该直接切换到unsigned 索引类型。那么你就不必争论 2-complement 之类的东西了。
  • 非常感谢,这使我的代码快了大约 6.5 倍!我认为这是一个相当显着的改进
  • 由于我只能将 1 个答案设置为已接受,因此即使 Oli Charlesworth 提出了类似的答案,我也会因为示例而使用这个答案。
  • 尝试使 ReadArray 和 WriteArray 函数适用于数组而不是单个值可能是有意义的。或者将写入和读取作为宏或内联过程(在 C++ 编译器的情况下)。在这种情况下,您可以避免一些堆栈工作量,这可能会给您带来 CPU 滴答声。
  • @valdo writeIndex 最终会溢出
【解决方案2】:

如果您可以将缓冲区大小设置为 2 的幂,则可以用无条件位掩码代替对零的检查。在大多数处理器上,这应该更快。

【讨论】:

  • 同意。此外,根本不需要检查索引溢出/不足。只需增加和减少它而无需任何检查,只需 AND 屏蔽它以进行实际元素访问
  • 我将对此进行调查,使其成为 2 的幂很容易(只需将 BUFF_SIZE 设置为 4)。不过,我将不得不对位掩码进行一些研究
  • @Gurba 我假设最终的应用程序将有更大的缓冲区大小?否则,我首先会强烈质疑环形缓冲区 ADT 的使用。
【解决方案3】:

这可能看起来不优雅,但很有效。通过指针访问结构元素会占用大量指令。为什么不完全删除结构并将bufferwriteIndex 作为全局变量?这将大大减小 readnwrite 函数的大小。

我在 gcc 中尝试过,这是带有和不带有结构的输出

有结构

_write:
    pushl   %ebp
    movl    %esp, %ebp
    movl    8(%ebp), %ecx
    movl    8(%ebp), %eax
    movl    16(%eax), %edx
    movl    12(%ebp), %eax
    movl    %eax, (%ecx,%edx,4)
    movl    8(%ebp), %eax
    incl    16(%eax)
    movl    8(%ebp), %eax
    cmpl    $3, 16(%eax)
    jne L1
    movl    8(%ebp), %eax
    movl    $0, 16(%eax)
L1:
    popl    %ebp
    ret

没有结构。即使bufferwriteIndex 成为全局

_write:
    pushl   %ebp
    movl    %esp, %ebp
    movl    _writeIndex, %edx
    movl    8(%ebp), %eax
    movl    %eax, _buff(,%edx,4)
    incl    _writeIndex
    cmpl    $3, _writeIndex
    jne L1
    movl    $0, _writeIndex
L1:
    popl    %ebp
    ret

【讨论】:

  • 启用优化后应该不会有这么大的差异。 buffwriteIndex的地址可以解析一次,其余的都一样。 OTOH 没有优化任何访问,如buff-&gt;... 都会很长
  • 如果 OP 需要多个缓冲区实例,这将不起作用。
  • @valdo 嗯.. 但是编译器似乎在做其他事情。我试过-O2,结果代码似乎比上面的代码大得多:O
  • @OliCharlesworth 你是对的。然后,他将不得不创建更多的全局缓冲区/索引,这将变得越来越笨拙。这就是为什么我在答案中添加了not so elegant 短语:)
  • 我会记住这一点,但我怀疑我会使用它。我确实需要多个实例,这对我来说似乎无法维护。我会经常感到困惑
【解决方案4】:

使用指针跟踪循环缓冲区的开始和结束可能比数组索引快一点,因为如果是后者,地址将在运行时计算。尝试将 readIndex 和 writeIndex 替换为 float*。然后代码将是

*buffer->writeIndex = value;
buffer->writeIndex++;
if(buffer->writeIndex == buffer + BUFF_SIZE)
  buffer->writeIndex=buffer->buff;

buffer + BUFF_SIZE 仍将是一个常量表达式,编译器将在编译时将其转换为固定地址。

【讨论】:

    【解决方案5】:

    接受的答案包含不正确的代码,将调用未定义的行为。更正如下:

    #define BUFF_SIZE (4U)
    #define BUFF_SIZE_MASK (BUFF_SIZE-1U)
    
    struct buffer {
        float buff[BUFF_SIZE];
        unsigned writeIndex;
    };
    
    void write(struct buffer *buffer, float value) {
        buffer->buff[(++buffer->writeIndex) & BUFF_SIZE_MASK] = value;
    }
    
    float readn(struct buffer *buffer, unsigned Xn){
        return buffer->buff[(buffer->writeIndex - Xn) & BUFF_SIZE_MASK];
    }
    

    原始答案中的错误是假设 'int' 会环绕。使用带 int 的二进制掩码也是不明智的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-04-18
      • 2015-10-08
      • 2019-11-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多