Найти первый неустановленный бит в буфере (оптимизация) - PullRequest
4 голосов
/ 30 июля 2010

Какой самый быстрый / чистый способ найти смещение по битам первого невыбранного бита в массиве произвольной длины?

Предположим, что прототип вашей функции выглядит примерно так size_t first_unset_bit(char unsigned const *buf, size_t bit_count, size_t start_bit); и что он может быть вызван несколько раз подряд в одном и том же буфере. Если вы можете дать лучший прототип, пожалуйста, обоснуйте.

Если вы используете сборку какой-либо сборки, предоставьте образец x86, который будет работать на core2 или новее. Я награжу ответ решением, которое обеспечивает наилучшее сочетание скорости и красоты.

Update0

Вот моя наивная реализация. Я понятия не имею, правильно ли это на самом деле, но еще не используется в реальной системе.

static size_t first_unset_bit(char unsigned const *buf, size_t bit_count, size_t start_bit)
{
    for (; start_bit < bit_count; ++start_bit)
    {
        size_t buf_index = start_bit / CHAR_BIT;
        int bit_index = start_bit % CHAR_BIT;
        if (!((buf[buf_index] >> bit_index) & 1))
            return start_bit;
    }
    return -1;
}

Ответы [ 9 ]

3 голосов
/ 04 августа 2010

Часто пропускаемый, strings.h (да, этот стандартный заголовок) содержит набор функций: ffs , ffsl и тому подобное, см. здесь для получения дополнительной информации. По крайней мере, для gcc и x86 это компилируется в соответствующую инструкцию one-cycle , например BSFL.

Я бы поэтому предложил:

  1. добавить страж 0xFFFF в конце вашего массива
  2. разделите bit_count на 4 (так что ваша итерация по 4-байтным блокам вместо байтов)
  3. использовать цикл while, чтобы найти блок с первым установленным битом

Например:

cursor = start_pos;
while(position = ffsl(buf))
  cursor++;
return (cursor - startpos) * 32 + pos;

(За исключением того, что вы должны проверить, достигли ли вы дозорного, в этом случае буфер пуст.)

Хотя вы должны воспринимать это с недоверием, потому что я не претендую на звание эксперта по сборке ... вы бы в основном использовали чуть более 3 циклов на каждые 32 бита (одно увеличение, одно сравнение, один BSFL инструкции), и представьте, что вы можете добиться большего успеха, используя длинную версию функции.

2 голосов
/ 30 июля 2010

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

2 голосов
/ 30 июля 2010

На x-86 ассемблере,

REPE SCAS 0xFFFFFFFF

... вероятно, станет важной частью ответа!

У вас нет выбора, кроме как исследовать каждый бит, предшествующий первому неустановленному биту, поэтому все зависит только от того, насколько быстро вы сможете это сделать. Сравнение 32 битов за раз - это хорошее начало, и как только вы узнаете, какое WORD содержит первый ненастроенный бит, вы можете использовать комбинацию сдвигов / таблиц поиска, чтобы найти первый бит в этом слове.

1 голос
/ 30 июля 2010

Без использования какого-либо языка ассемблера, но со встроенными GCC и предположением, что bit_count кратно числу битов в long, что-то вроде этого должно работать.Я изменил вашу функцию, чтобы принимать void* буферный аргумент, чтобы избежать проблем с наложением.Полностью не проверенный, я, возможно, испортил математику, особенно в ведущем блоке if (start_bit% LONG_BIT).

#include <stddef.h>
#include <limits.h>
#define LONG_BIT (CHAR_BIT * sizeof(unsigned long))

size_t
first_unset_bit(const void *buf, size_t bit_count, size_t start_bit)
{
    size_t long_count = bit_count / LONG_BIT;
    size_t start_long = start_bit / LONG_BIT;

    const unsigned long *lbuf = (const unsigned long *)buf;

    if (start_bit % LONG_BIT)
    {
        size_t offset = start_bit % LONG_BIT;
        unsigned long firstword = lbuf[start_long];
        firstword = ~(firstword | ~((1UL << offset) - 1));
        if (firstword)
            return start_bit - offset + __builtin_clzl(firstword);

        start_long += 1;
    }

    for (size_t i = start_long; i < long_count; i++)
    {
        unsigned long word = lbuf[i];
        if (~word)
            return i*LONG_BIT + __builtin_clzl(~word);
    }
    return bit_count + 1; // not found
}
1 голос
/ 30 июля 2010

В Linux есть то, что я представляю как хорошо настроенную реализацию под именем "find_first_zero_bit".

0 голосов
/ 31 июля 2010

используйте встроенный эквивалент gcc Microsoft _BitScanReverse, я использую что-то вроде этого, чтобы найти первый свободный бит (представляющий использование блока) для моей системы памяти:

        __forceinline DWORD __fastcall GetNextFreeBlockIndex(PoolBlock* pPoolBlock)
        {
            DWORD dwIndex;
            DWORD dwOffset = 0;
            DWORD* pUsage = &pPoolBlock->fUsage[0];
            while(dwOffset < MMANAGER_BLOCKS_PER_POOL)
            {
                DWORD dwUsage = *pUsage;
                if(dwUsage != 0xFFFFFFFF && _BitScanForward(&dwIndex,~dwUsage))
                {
                    #if !( MMANAGER_ATOMIC_OPS )
                        pPoolBlock->pSync.Enter();
                    #endif

                    ATOMIC_Write(DWORD,pPoolBlock->dwFreeIndex,dwOffset);
                    ATOMIC_Write(DWORD*,pPoolBlock->pFreeUsage,pUsage);

                    #if !( MMANAGER_ATOMIC_OPS )
                        pPoolBlock->pSync.Leave();
                    #endif

                    return dwIndex + dwOffset;
                }

                pUsage++;
                dwOffset += 32;
            }

            return 0xFFFFFFFF;
        }

        __forceinline DWORD __fastcall GetFreeBlockIndex(PoolBlock* pPoolBlock)
        {
            DWORD dwIndex;
            DWORD dwUsage = *pPoolBlock->pFreeUsage;
            if(dwUsage == 0xFFFFFFFF)
                return GetNextFreeBlockIndex(pPoolBlock);

            if(_BitScanForward(&dwIndex,~dwUsage))
                return dwIndex + pPoolBlock->dwFreeIndex;

            return 0xFFFFFFFF;
        }

извините за табуляции, это прямонекоторый # if / # endif VS код.ofc этот код сделан только для DWORDS, вы можете просто сделать block_size & 3, чтобы найти, есть ли какие-то нечетные байты, скопировать эти нечетные байты в DWORD и отсканировать DWORD, а затем вырезать любые результаты, превышающие (block_size & 3) << 3

0 голосов
/ 31 июля 2010

Как уже упоминали другие, язык ассемблера может дать лучшую производительность.Если это не вариант, вы можете рассмотреть следующую (непроверенную) процедуру.Это не совсем то, о чем вы просили, но оно должно быть достаточно близко, чтобы вы могли адаптировать его к вашим потребностям.

size_t findFirstNonFFbyte (
    unsigned char const *buf,       /* ptr to buffer in which to search */
    size_t               bufSize,   /* length of the buffer */
    size_t               startHint  /* hint for the starting byte (<= bufSize) */
    ) {
    unsigned char * pBuf = buf + startHint;
    size_t          bytesLeft;

    for (bytesLeft = bufSize - startHint;
         bytesLeft > 0;
         bytesLeft = startHint, pBuf = buf) {
        while ((bytesLeft > 0) && (*pBuf == 0xff)) {
            *pBuf++;
            bytesLeft--;
        }

        if (bytesLeft > 0) {
            return ((int) (pBuf - buf));
        }
    }
    return (-1);
}

Для использования ...

index = findFirstNonFFbyte (...);
bit_index = index + bitTable[buffer[index]];

Дополнительные примечания:

Приведенный выше код будет проверять 8 бит за раз.Если вы знаете, что ваш буфер будет выровнен по 4 байтам, а его длина будет кратна 4 байтам, то вы можете одновременно тестировать 32-битный код с небольшим изменением (не забудьте вычисление возвращаемого значения).

Если ваш начальный бит не является подсказкой, а является абсолютным, вы можете пропустить цикл for.

Вам потребуется предоставить собственную таблицу поиска битов.Это должен быть массив длиной 256 байт.Каждая запись идентифицирует первый бит сброса байта, который индексирует эту запись.Личный опыт подсказывает мне, что разные люди будут по-разному считать эти биты.Некоторые называют бит 0 самым значимым битом байта;другие будут называть бит 0 наименее значимым битом байта.Какой бы стиль вы ни выбрали, будьте последовательны.

Надеюсь, это поможет.

0 голосов
/ 30 июля 2010

Я предполагаю, что ваш буфер выровнен, например, буфер, возвращаемый malloc.Если нет, вам сначала нужно будет отсканировать часть без выравнивания.

uint32_t *p = (void *)buf;
while (!(*p+1)) p++;
size_t cnt = (unsigned char *)p - buf << CHAR_BIT;
if (*p>=0xFFFF0000)
  if (*p>=0xFFFFFF00)
    if (*p>=0xFFFFFFF0)
      if (*p>=0xFFFFFFFC)
        if (*p>=0xFFFFFFFE) cnt+=31;
        else cnt+=30;
      else
        if (*p>=0xFFFFFFF9) cnt+=29;
        else cnt+=28;
    else
      if (*p>=0xFFFFFFC0)
        if (*p>=0xFFFFFFE0) cnt+=27;
        else cnt+=26;
      else
        if (*p>=0xFFFFFF90) cnt+=25;
        else cnt+=24;
  else
    ...

Я оставлю заполнение оставшейся части двоичного поиска вам.

0 голосов
/ 30 июля 2010

Очевидное решение состоит в том, чтобы просто пройти от start_bit до тех пор, пока вы не доберетесь до конца массива или не найдете неустановленный бит.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...