Повышение эффективности кольцевого буфера C

Я хотел бы помочь улучшить эффективность моего кода циклического буфера.

Я осмотрел stackru и обнаружил, что (почти) все темы циклических буферов касаются использования такого буфера или базовой реализации циклического буфера. Мне действительно нужна информация о том, как сделать его супер эффективным.

План состоит в том, чтобы использовать этот буфер с микроконтроллером STM32F4, который имеет один FPU точного разрешения. Я планирую интенсивно использовать функции 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];
}

4 ответа

Решение

Как предположил "Оли Чарльзуорт" - вы могли бы упростить вещи, если бы размер буфера был степенью 2. Я хотел бы написать тела функций чтения / записи, чтобы цель была более ясной.

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

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

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

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

Несколько объяснений. Обратите внимание, что нет разветвления (if) совсем. Мы не ограничиваем индекс массива границами массива, вместо этого мы используем И маску.

В readn вместо расчета индекса доступа как

writeIndex - (Xn+1)

мы используем:

writeIndex + (~Xn)

Это работает, предполагая арифметику с 2 дополнениями. То есть, чтобы сделать целое число отрицательным, мы делаем НЕ для всех его битов, а затем добавляем 1. Но так как вы хотите вычесть 1 из числа - делать просто НЕ - это все, что нужно.

Если вы можете сделать размер буфера степенью 2, то проверка на ноль может быть заменена безусловной битовой маскировкой. На большинстве процессоров это должно быть быстрее.

Это может показаться не элегантным, но эффективным. Доступ к элементам структуры через указатель занимает много инструкций. Почему бы не удалить структуру вообще и сделать buffer а также writeIndex как глобальные переменные? Это значительно уменьшит размер вашего readn а также write функции.

Я попытался в 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

Без структуры. т.е. buffer а также writeIndex как глобальный

_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

Отслеживание начала и конца циклического буфера с помощью указателей, вероятно, немного быстрее, чем индексирование массива, поскольку в случае последнего адрес будет вычисляться во время выполнения. Попробуйте заменить readIndex и writeIndex на float* вместо. Код будет тогда

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

buffer + BUFF_SIZE останется постоянным выражением, и компилятор переведет его на фиксированный адрес во время компиляции.

Принятый ответ содержит неверный код и вызовет неопределенное поведение. Исправление ниже:

#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];
}

Ошибка в исходном ответе состояла в том, что предполагалось, что in t будет меняться. Использование двоичных масок с in t также неразумно.

Другие вопросы по тегам