Найти самый значимый бит (самый левый), который установлен в битовом массиве - PullRequest
37 голосов
/ 07 апреля 2010

У меня есть реализация массива битов, где 0-й индекс - это MSB первого байта в массиве, 8-й индекс - это MSB второго байта и т. Д.

Какой быстрый способнайти первый бит, который установлен в этом массиве бит?Все соответствующие решения, которые я искал, находят первый наименее значимый бит, но мне нужен первый самый важный.Итак, учитывая 0x00A1, я хочу 8 (поскольку это 9-й бит слева).

Ответы [ 17 ]

1 голос
/ 08 июля 2011

Вот фрагмент кода, объясняющий __builtin_clz ()

////// go.c ////////
#include <stdio.h>

unsigned NUM_BITS_U = ((sizeof(unsigned) << 3) - 1);
#define POS_OF_HIGHESTBITclz(a) (NUM_BITS_U - __builtin_clz(a)) /* only works for a != 0 */

#define NUM_OF_HIGHESTBITclz(a) ((a)                                \
                             ? (1U << POS_OF_HIGHESTBITclz(a))      \
                             : 0)


int main()
{
  unsigned ui;

  for (ui = 0U; ui < 18U; ++ui)
    printf("%i \t %i\n", ui, NUM_OF_HIGHESTBITclz(ui));

  return 0;
}
1 голос
/ 19 февраля 2019

x86 имеет инструкцию BSR, которая возвращает битовый индекс (а не число ведущих нулей выше it).

Но, к сожалению, нет переносимого свойства, которое эффективно предоставляет его всем компиляторам. GNU C обеспечивает __builtin_clz, но unsigned bitidx = 31 - __builtin_clz(x); не оптимизирует обратно только до BSR с текущими GCC и ICC. (Это относится к clang, который доказывает, что выражение эквивалентно, поэтому может ).


Следующее определяет макросы или функции BSR32() и BSR64(), которые эффективно компилируются в просто a bsr инструкцию для x86. (Вывод результата «мусор», если входные данные были нулевыми. При использовании встроенных функций невозможно воспользоваться преимуществами поведения инструкции asm, не изменяя назначение для ввода = 0.)

Переносимость не-x86 потребует дополнительных #ifdef например. отступить к 31-__builtin_clz. Большинство не-x86 ISA, если у них вообще есть бит с нулем в начале, считают начальные нули вместо того, чтобы дать вам битовый индекс. Вот почему GNU C определяет __builtin_clz как портативный встроенный модуль. (Если в целевой системе нет поддержки HW, встроенная программа будет компилироваться в программную эмуляцию, обычно вызывая вспомогательную функцию libgcc.)

#include <stdint.h>

// define BSR32() and BSR64()
#if defined(_MSC_VER) || defined(__INTEL_COMPILER)
    #ifdef __INTEL_COMPILER
        typedef unsigned int bsr_idx_t;
    #else
        #include <intrin.h>   // MSVC
        typedef unsigned long bsr_idx_t;
    #endif

    static inline
    unsigned BSR32(unsigned long x){
        bsr_idx_t idx;
        _BitScanReverse(&idx, x); // ignore bool retval
        return idx;
    }
    static inline
    unsigned BSR64(uint64_t x) {
        bsr_idx_t idx;
        _BitScanReverse64(&idx, x); // ignore bool retval
        return idx;
    }
#elif defined(__GNUC__)

  #ifdef __clang__
    static inline unsigned BSR64(uint64_t x) {
        return 63-__builtin_clzll(x);
      // gcc/ICC can't optimize this back to just BSR, but clang can and doesn't provide alternate intrinsics
    }
  #else
    #define BSR64 __builtin_ia32_bsrdi
  #endif

    #include <x86intrin.h>
    #define BSR32(x) _bit_scan_reverse(x)

#endif

bsf, вероятно, не требуется такой большой помощи для компиляторов, потому что встроенная функция соответствует поведению инструкции asm по возвращению битового индекса LSB, то есть количества завершающих нулей.

Вызывающий тест unsigned test32(unsigned x) { return BSR32(x); } указывает на 1 инструкцию для всех основных компиляторов x86, в проводнике компилятора Godbolt . BSR64 также встроен в версию с 64-битным операндом. См. Также Существует ли инструкция x86 / x86_64, которая обнуляет все биты ниже старшего значащего бита? для примеров использования.

;; x64 MSVC 19.16 -O2
unsigned int test32(unsigned int) PROC                                    ; test32, COMDAT
        bsr     eax, ecx
        ret     0
unsigned int test32(unsigned int) ENDP                                    ; test32
# clang -O3 -march=haswell   is too "smart?" for its own good:
test32(unsigned int):
        lzcnt   eax, edi
        xor     eax, 31
        ret
# gcc8.2 -O3 -march=haswell
test32(unsigned int):
        bsr     eax, edi
        ret
# ICC19 -O3 -march=haswell
test32(unsigned int):
        bsr       eax, edi                                      #15.9
        ret                                                     #41.12

Смысл этого состоит в том, чтобы избежать медленного кода из портативной (не MSVC) версии:

#ifdef __GNUC__
unsigned badgcc(uint64_t x) {
    return 63 - __builtin_clzll(x);
}
#endif

Без -march=haswell мы получаем только BSR от Clang, но:

# gcc8.2 -O3
badgcc(unsigned long):
        bsr     rdi, rdi
        mov     eax, 63
        xor     rdi, 63
        sub     eax, edi
        ret
# ICC19.0.1 -O3
badgcc(unsigned long):
        mov       rax, -1                                       #46.17
        bsr       rdx, rdi                                      #46.17
        cmove     rdx, rax                                      #46.17
        neg       rdx                                           #46.17
        add       rdx, 63                                       #46.17
        neg       edx                                           #46.17
        add       edx, 63                                       #46.17
        mov       eax, edx                                      #46.17
        ret                                                     #46.17

Это просто противно. (Интересно видеть, что ICC выполняет CMOV для получения -1, если вход равен нулю. BSR устанавливает ZF в соответствии со своим входом , в отличие от большинства инструкций, которые устанавливают флаги в соответствии с результатом.)

С -march=haswell (или другим способом, позволяющим использовать инструкции BMI1), это не так плохо, но все же не так хорошо, как просто BSR. Выходные зависимости по модулю, которые компиляторы в основном работают, чтобы избежать для lzcnt, но, как ни странно, не для BSR. (Где выходная зависимость является зависимостью true из-за поведения input = 0.) Почему нарушение «выходной зависимости» LZCNT имеет значение?

1 голос
/ 18 июля 2017

Я добавлю один!

typedef unsigned long long u64;
typedef unsigned int       u32;
typedef unsigned char      u8;


u8 findMostSignificantBit (u64 u64Val)
{
  u8 u8Shift;
  u8 u8Bit = 0;

  assert (u64Val != 0ULL);

  for (u8Shift = 32 ; u8Shift != 0 ; u8Shift >>= 1)
  {
    u64 u64Temp = u64Val >> u8Shift;
    if (u64Temp)
    {
      u8Bit |= u8Shift; // notice not using +=
      u64Val = u64Temp;
    }
  }

  return u8Bit;
}

Конечно, это работает с 64-битным числом (без знака long long), а не с массивом. Кроме того, многие люди указали на встроенные функции g ++, о которых я не знал. Как интересно.

Так или иначе, это находит самый значимый бит в 6 итерациях и дает подтверждение, если вы передали 0 в функцию. Не самая лучшая функция, если у вас есть доступ к инструкции на чипсете.

Я также использую | = вместо + =, потому что это всегда степени двойки, и ИЛИ (классически) быстрее сложения. Поскольку я добавляю только уникальные способности 2, у меня никогда не бывает переворачивания.

Это двоичный поиск, который означает, что он всегда находит результат в 6 итерациях.

Опять же, это лучше:

u8 findMostSignificantBit2 (u64 u64Val)
{
  assert (u64Val != 0ULL);

  return (u8) (__builtin_ctzll(u64Val));
}
0 голосов
/ 30 мая 2019

Для Java я использую это:

static public final int msb(int n) {
    n |= n >>> 1;  
    n |= n >>> 2; 
    n |= n >>> 4; 
    n |= n >>> 8; 
    n |= n >>> 16; 
    n >>>= 1;
    n += 1; 
    return n;
}

И

static public final int msb_index(int n) {

    final int[] multiply_de_bruijn_bit_position = {
        0, 1, 28, 2, 29, 14, 24, 3, 30, 22, 20, 15, 25, 17, 4, 8, 
        31, 27, 13, 23, 21, 19, 16, 7, 26, 12, 18, 6, 11, 5, 10, 9
    };
    return multiply_de_bruijn_bit_position[(msb(n) * 0x077CB531) >>> 27];
}
0 голосов
/ 07 апреля 2010

Вот простой алгоритм грубой силы для массива байтов произвольного размера:

int msb( unsigned char x);  // prototype for function that returns 
                            //  most significant bit set

unsigned char* p;

for (p = arr + num_elements; p != arr;) {
    --p;
    if (*p != 0) break;
}

// p is with pointing to the last byte that has a bit set, or
//  it's pointing to the first byte in the array

if (*p) {
    return ((p - arr) * 8) + msb( *p);
}

// what do you want to return if no bits are set?
return -1;

Я оставлю в качестве упражнения для читателя придумать соответствующую функцию msb(), а также оптимизацию для работы с порциями данных размером int или long long.

0 голосов
/ 07 апреля 2010

Хм, ваш тег указывает на 32 бита, но похоже, что вы используете 16-битные значения. Если вы имели в виду 32 бит, то я думаю, что ответ для 0x00a1 должен быть 24, а не 8.

Предполагая, что вы ищете битовый индекс MSB с левой стороны и знаете, что вы будете иметь дело только с uint32_t, вот очевидный простой алгоритм:

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>

int main()
{
    uint32_t test_value = 0x00a1;
    int i;

    for (i=0; i<32; ++i)
    {
        if (test_value & (0x80000000 >> i))
        {
            printf("i = %d\n", i);
            exit(0);
        }
    }

    return 0;
}
0 голосов
/ 27 марта 2013
#define FFS(t)  \
({ \
register int n = 0; \
            \ 
if (!(0xffff & t)) \
    n += 16; \
         \
if (!((0xff << n) & t)) \
    n += 8; \
        \
if (!((0xf << n) & t)) \
    n += 4; \
        \
if (!((0x3 << n) & t)) \
    n += 2; \
        \
if (!((0x1 << n) & t)) \
    n += 1; \
        \
n; \
})
...