Помогите мне улучшить этот код обработки битового буфера C++

Я пишу функцию для обработки входящего 32-битного буфера, представляющую изменяющиеся данные при сравнении с соответствующим сохраненным 32-битным буфером. Позиция изменяющегося бита представляет собой число (т. е. значение 8 означает бит 3), которое необходимо обработать, а также является ли изменение 0->1 или 1->0. Вот текущая реализация, пожалуйста, помогите мне улучшить ее! Обратите внимание, что это не настоящий код, он был упрощен, чтобы не зависеть от контекста.

uint32_t temp = oldBuffer ^ newBuffer;
uint32_t number = 0;
while (temp != 0)
{
    if (temp & 0x1)
    {
        uint32_t bitValue = 0;
        if ((newBuffer& (1 << number)) != 0) bitValue = 1;
        processNumber(number, bitValue);
    }
    number++;
    temp = temp >> 1;
}
oldBuffer = newBuffer;

Прямо сейчас это работает, но мне не нравится, что он должен проверять каждый бит, проверяя бит 1 и переключаясь через все это. Если бы была гарантирована установка только 1 бита, это было бы несложно понять, но это не так.

Редактировать: Нейлу, я думаю, я надеюсь найти способ получить позиции битов после XOR за постоянное время, вместо того, чтобы сдвигать весь буфер и проверять биты один за другим.


person Community    schedule 20.08.2009    source источник
comment
можно ли использовать стандартную библиотеку?   -  person xtofl    schedule 20.08.2009


Ответы (6)


Возможно, вы могли бы повысить производительность, используя один или несколько хаков «покрутить биты» на

В частности, алгоритмы для «нахождения целочисленного логарифмического основания 2» (положение самого старшего набора битов). Это позволит вам определить, какие биты установлены более непосредственно, чем зацикливание на каждом бите.

Если вам необходимо обрабатывать биты в порядке от младшего к старшему, вы можете использовать небольшую модификацию методов Кернигана для подсчета битов:

/* note: untested code */
while (temp) {

    uint32_t bit = temp & (~(temp & (temp - 1)); /* isolate lowest bit */

    temp &= ~bit;

    uint32_t bit_number = /* use find log base 2 hack */;

    /* etc... */

}

Это должно заставить цикл while повторяться ровно столько раз, сколько установлено битов. Ваш исходный цикл будет повторяться количество раз, равное битовой позиции самого высокого установленного бита.

Однако я был бы удивлен, если бы это имело какое-либо измеримое значение, если бы это не было сверхкритическим фрагментом кода.

person Michael Burr    schedule 20.08.2009

Как насчет использования стандартной библиотеки? Нет необходимости сдвигать или, и т. д., чтобы проверить, верны ли биты. Проверка битов в наборе битов гарантированно будет постоянным временем. И пишет намного чище и понятнее.

const std::bitset<32> oldbits( oldBuffer );
const std::bitset<32> newbits ( newBuffer );

for( size_t index = 0; index != oldbits.size(); ++index ) {
   if( oldbits[ index ] != newbits[ index ] ) {
       processNumber( index, newbits[ index ] )
   }
}

Примечание: здесь вам также не нужен XOR, так как у вас есть прямой доступ к битам. Однако вы можете использовать его для экономии производительности.

person xtofl    schedule 20.08.2009

 uint32_t temp = oldBuffer ^ newBuffer;
 uint32_t number = 0;
 uint32_t bitmask=1;
 while (temp != 0)
 {
     if (temp & 0x1)
     {
         processNumber(number, ((newBuffer & bitmask) != 0));
     }
     number++;
     temp = temp >> 1;
     bitmask <<=1;
 }
 oldBuffer = newBuffer;

2 очень маленьких изменения...

ваш код уже был достаточно эффективным

person Toad    schedule 20.08.2009

Это зависит от того, что вы ожидаете от распределения (oldBuffer ^ newBuffer). Если это совершенно случайно и полный диапазон 32 бита, то у вас есть в среднем 16 циклов.

Одним из возможных решений является создание такой таблицы

int lookup[255][8] = {
  { -1, -1, -1, -1, -1, -1, -1, -1 }, // 0 has no bits set
  {  0, -1, -1, -1, -1, -1, -1, -1 }, // 1 has only the 0th bit set
  {  1, -1, -1, -1, -1, -1, -1, -1 }, // 2 has only the 1st bit set
  {  0,  1, -1, -1, -1, -1, -1, -1 }, // 3 has the 0th, 1st bit set
  {  2, -1, -1, -1, -1, -1, -1, -1 }, // 4 has only the 2nd bit set
  ...
  {  0,  1,  2,  3,  4,  5,  6,  7 }, // 255 has all bits set
}       

При этом вам нужно выполнить цикл 4 раза (1 для каждого байта), а затем 1 раз для каждого установленного бита (в среднем 4) - эй, это 16.

Но если количество установленных битов невелико (в среднем намного меньше половины 32-битных), то поиск в таблице будет остановлен.

К сожалению, поиск по таблице добавляет умножение и сложение каждый раз, когда вы его используете, так что это не обязательно хорошо. Вы должны проверить это. Другими словами, он находит установленные биты за постоянное время, но константа может быть больше, чем цикл. Это зависит от того, сколько установленных битов вы ожидаете иметь.

person Lou Franco    schedule 20.08.2009
comment
Определенно интересный подход. Теперь, возможно, я добавлю в него некоторый код для создания таблицы поиска, потому что я не собираюсь заполнять точки в середине :) - person xtofl; 20.08.2009
comment
Определенно -- сгенерируйте таблицу. Это не только утомительно, но и чревато ошибками. - person Lou Franco; 20.08.2009
comment
Собирался предложить это ... хороший - person Justicle; 24.08.2009

Вы можете сделать это рекурсивно, как B-Tree :)

go(oldBuffer ^ newBuffer, 16, 0, newBuffer);
...

void go(temp, half, pos, bitValue)
{
    if (half > 1) {
      uint32_t mask = (1 << half) - 1;
      if (temp & mask)    go(temp & mask, half/2, pos, bitValue & mask);
      temp >>= half;
      if (temp & mask)    go(temp & mask, half/2, pos + half, (bitValue >> half) & mask);
    } else {
      if (temp & 1) processNumber(pos, bitValue&1);
      if (temp & 2) processNumber(pos+1, bitValue/2&1);
    }
}
person zxcat    schedule 24.08.2009

person    schedule
comment
+1, некоторые пробелы будут иметь большое значение для улучшения читаемости; D - person user7116; 20.08.2009
comment
Приятно - одна (чрезвычайно) маленькая вещь: тест на b<32 не нужен. Остановки цикла, когда test становится 0, достаточно (не знаю, хватит ли компилятору ума понять это во время оптимизации). - person Michael Burr; 20.08.2009
comment
разве это не тот же код, только в более сжатой форме. Другими словами, разве я не ожидал, что это будет работать с той же скоростью (когда b‹32 вынут). Он имеет такое же количество циклов, сдвигов, сравнений и т. д. Я как бы предполагаю, что оптимизатор поможет OP, если ((newBuffer & ... строка и поместит битовое значение в регистр. - person Lou Franco; 20.08.2009
comment
Верно, Михаил, спасибо. Я отредактирую это. И, шестибуквенные переменные, это дело вкуса. Я нахожу более короткий код более читаемым, потому что я вижу больше, не двигая глазами ;-). И пробелы улучшают читаемость для меня только тогда, когда я действительно разделяю логику (например, пробелы вокруг && выше). - person Michael Krelin - hacker; 20.08.2009
comment
Лу, количество сдвигов одинаковое, но количество бит отличается. - person Michael Krelin - hacker; 20.08.2009
comment
Сдвиг 1 и сдвиг N должны быть эквивалентны — оба просто зашиты в кремний, верно? Есть ли быстрая инструкция shiftRightOne? Я думаю, что если и есть улучшение, то оно заключается в том, что оптимизатор не будет иметь дело с переменной bitValue. В любом случае, код стал проще — так что в этом смысле — определенно улучшение. - person Lou Franco; 20.08.2009
comment
У Лу вполне может быть что-то там, но мы, конечно, копаемся в некоторых микро-оптимизациях (но иногда это просто хороший отдых, даже если это не великая инженерия). - person Michael Burr; 20.08.2009
comment
Лу, это зависит от архитектуры, но в целом ты прав. Я не думаю, что исходный код нуждается в какой-либо оптимизации по соображениям производительности. Но я понимаю, почему эстетика Рича страдает, когда он смотрит на код 1<<number ;-) Так что, по сути, ваш первоначальный комментарий был правильным — это не сильно повышает производительность. - person Michael Krelin - hacker; 20.08.2009