Проверьте все смежные биты - PullRequest
2 голосов
/ 24 марта 2019

Я строю код LUT префикса Хаффмана путем обхода дерева. Я использую регистр для отслеживания текущего префикса.

Мое условие для выхода из алгоритма, который анализирует дерево, использует следующие условия, которые должны выполняться:

  1. текущий элемент в дереве должен быть листом

  2. текущий код префикса имеет все биты, установленные без ложных битов.

Для второго условия я также отслеживаю текущую длину (в битах) строки префикса.

Как проверить, что в регистре установлено более 1 бита и что все установленные биты смежны друг с другом?

РЕДАКТИРОВАТЬ: группа установленных битов должна начинаться с бита 0 и иметь длину префикса (хранится в другом регистре)

Ответы [ 2 ]

2 голосов
/ 25 марта 2019

Строительным блоком для этого будет: добавление 1 к младшему биту смежной группы очистит все эти биты с выносом, оставив 1 бит установленным над группой.например, 0xff + 1 = 0x100 .

Если какие-либо биты не установлены, перенос не будет распространяться до конца, например, 0b01101 + 1 = 0b01110, а не установка бита # 4.(И если оставить некоторые из существующих битов установленными, то x & (x+1) будет истинным.)

Это работает для битовых групп в нижней части регистра (добавление 1) или в любой более высокой позиции(выделите младший установленный бит с помощью (-x) & x и добавьте его, например, с помощью BMI1 blsi или mov / neg / and).

Связанный битхак - это тест y & (y-1) дляцелое число, имеющее только один установленный бит (путем очистки самого младшего установленного бита): https://graphics.stanford.edu/~seander/bithacks.html#DetermineIfPowerOf2. Но поскольку мы производим y с x+1, мы можем оптимизировать его до x & (x+1), чтобы обнаружитьнепрерывная маска внизу регистра.


Ваш конкретный случай очень прост:

  • нижняя часть битового диапазона должна быть битом 0
  • битовый диапазон в точности равен n битам (длина префикса)

Эти ограничения означают, что есть ровно 1 целое число, которое соответствует обоим требованиям , поэтому вы должны предварительно- вычислите его и просто сравните на равенство с cmp/je.Число с n битами, установленными внизу, равно prefix_mask = (1<<n) - 1.Перенос (заимствование) путем вычитания устанавливает все биты ниже этого старшего разряда и очищает исходный бит.Биты выше этого значения остаются неустановленными, потому что этот старший бит удовлетворяет заимствованию.

Учитывая длину префикса n, вы можете вычислить 1<<n, используя bts (что является однопроцессным в IntelПроцессоры, или 2 мопа для AMD, https://agner.org/optimize/).

;; input: prefix length in EDX
;; output: prefix mask in ESI
xor    esi, esi
bts    esi, edx         ; set bit n;  eax |= 1<<n
dec    esi              ; set all the bits BELOW that power of 2

 ; then later, inside a loop: input in EAX
  cmp   eax, esi
  je    all_bits_set_up_to_prefix

@ fuz предложили LUT для этого, но это звучит как плохая идея, даже если вам приходится очень часто учитывать разные длины префиксов.Если не считать регистров, вы можете распределить их в стеке памяти после вычислений и использовать что-то вроде cmp [rsp+16], edx вместо статического LUT при циклическом преобразовании с той же длиной префикса.

Или выливать длину префикса в память, если вы этого не сделаетенужно некоторое время в регистре и просто сохранить маску.

Вы даже можете перевести маску обратно в длину префикса с помощью lea edx, [eax+1] / bsr edx,edx, чтобы найти битовый индекс самого высокого наборабит mask+1. (Или если возможна длина префикса 32, но ноль не возможен, то bsr / inc. BSR с input = 0 оставляет назначение неизменным и устанавливаетZF. AMD документирует это, говорят в документации Intel"undefined", но их текущее HW оставляет место назначения без изменений, поэтому инструкция имеет "ложную" зависимость от вывода.)


или без предварительного вычисления

Non-деструктивный тест EDX для всех младших битов n, а сам бит #n равен 0. (При добавлении 1 стираются младшие биты и устанавливается бит n , если это имело место).Вы можете использовать inc edx вместо LEA для копирования и добавления, если вы не используете его после.

;;; check the low n bits, ignoring whether higher bits are set or not
;;; inputs: prefix length in ECX, candidate in EDX
    lea  eax, [rdx+1]             
    bt   eax, ecx
;;; output: CF = 1 if all the bits from 0..len-1 were set, else 0

Если вы также хотите исключить любые старшие битыпри установке вам потребуется еще одна инструкция, но это может быть инструкция test, которая будет сливаться с макросом jcc, поэтому на процессорах Intel это не требует дополнительных мопов.На процессорах AMD, где btr равен 2 мопам против bt, равному 1, это стоит 1 дополнительный моп.(test / jcc может объединиться в семействе AMD Bulldozer и более поздних версиях.)

;;; input: prefix length in ECX, candidate in EDX
   lea     eax, [rdx+1]      ; produces a single set bit?
   btr     eax, ecx          ; reset that bit, leaving eax=0 if no other bits were set
   test    eax, eax          ; compare against zero
;;; output: ZF=1 (and eax=0) if EDX == (1<<ECX)-1 with no higher bits set.

   jz     contiguous_bitmask_of_length_ecx

Это стоит всего 3 мопа для Intel (4 для AMD), включая тестирование с макросом test / jz, дляветка на этом условии .И это не разрушает входной регистр.


Мы можем проверить наличие единой непрерывной группы битов неизвестной длины в нижней части регистра с помощью x & (x+1), чтообнаруживает, были ли установлены более высокие битыЕсли есть старший бит, который не перевернут переносом переноса, AND или TEST выдаст ненулевой результат.

Но это обрабатывает 0 и 1 так же, как многобитовые группыкак 0b0111.Возможно, вы захотите cmp eax, 3 / jb not_multibit_prefix до этого теста.

; check for a contiguous bit-group at the bottom of a reg of arbitrary length, including 0
;;; input: candidate in EDX
   lea   eax, [rdx+1]              ; carry-out clears all the set bits at the bottom
   test  eax, edx                  ; set flags from x & (x+1)
;;; output: ZF=1 if the only set bits in EDX were a contiguous group at the bottom

Я посмотрел на странный взлом парциальных флагов lea eax, [rdx+1] / test eax, edx (ZF = 1: были установлены только смежные младшие биты) / bt eax, ecx (CF = 1: он закончился в той позиции, которую мы хотим). Но у x86 нет условия jcc , для которого требуется CF = 1 и ZF = 1. ja берется, если (CF=0 and ZF=0), jbe берется, если (CF=1 or ZF=1), поэтому ни один из них не работает. И, конечно, это было бы ужасно для процессоров без эффективного объединения частичных флагов, что приводило к остановке частичных флагов.


Общий случай: битгруппе не обязательно начинать снизу.

Это исключает простые предварительные вычисления.

Как упоминалось выше, мы можем выделить младший установленный бит с помощью (-x) & x. ИМТ1 добавил инструкцию для этого, blsi. Поэтому, если вы можете принять на себя поддержку BMI1, вы можете сделать это неразрушающим образом за 1 моп. В противном случае это займет 3.

unsigned bits_contiguous(unsigned x) {
    unsigned lowest_set = (-x) & x;  // isolate lowest set bit
    unsigned add = x + lowest_set;   // carry flips all the contiguous set bits
    return (add & x) == 0;           // did add+carry leave any bits un-flipped?
}

Я поместил это в проводник компилятора Godbolt, чтобы увидеть, заметил ли gcc или clang какие-либо оптимизации, о которых я не думал. Конечно, вы на самом деле не хотите материализовать целое число 0/1, как мы просим компилятор, но, поскольку они решили использовать test / setcc, мы можем просто посмотреть, что они делают, чтобы создать правильное состояние флага.

Мы можем написать несколько тестовых функций, чтобы убедиться, что логика верна для некоторых констант времени компиляции с #define TEST(n) int test##n(){return bits_contiguous(n);} (и посмотреть, является ли asm xor eax,eax или mov eax,1). См. C + asm в проводнике компилятора Godbolt . Некоторые интересные случаи TEST(0) = 1, потому что условие в основном проверяет наличие нескольких битовых групп. (Таким образом, для этой проверки нулевые битовые группы аналогичны 1-битной группе.) TEST(0xFFFFFFFF) = 1 также: наличие x+1 = 0 не является проблемой.

С gcc8.3 -O3 получаем

# gcc8.3 -O3 -march=haswell  (enables BMI1 and BMI2)
bits_contiguous:
    blsi    eax, edi
    add     eax, edi
    test    eax, edi         # x & (x+lowest_set)

    sete    al
    movzx   eax, al
    ret

Без ИМТ1 нам нужно 3 инструкции вместо 1 для blsi:

    mov     eax, edi
    neg     eax
    and     eax, edi         # eax = isolate_lowest(x)
    add     eax, edi
    test    eax, edi

Чтобы также проверить конкретную длину битовой группы, у @fuz была хорошая идея: popcnt, чтобы убедиться, что установлено правильное количество бит (и отдельно проверить, что они смежный). Popcnt не является базовой линией, у процессоров до Nehalem его нет и он выйдет из строя, если они попытаются запустить его.

;input: prefix len in ECX, candidate in EDX
;clobbers: EAX
    popcnt  eax, edx
    cmp     eax, ecx
    jne     wrong_number_of_bits_set  ; skip the contiguousness test

    blsi    eax, edi
    add     eax, edi
    test    eax, edi         # x & (x+lowest_set)
    jz     contiguous_bitgroup_of_length_ecx

wrong_number_of_bits_set:
0 голосов
/ 24 марта 2019

Пусть ваш номер будет в eax, а желаемая длина префикса будет в edx.

Прежде всего, чтобы получить число конечных единиц, вычислите дополнение и вычислите число конечных нулей (мы переходим к fail, если число не совпадает). cmovz необходим, поскольку bsf не нравится, когда его вызывают в качестве аргумента 0. Если этот случай не появляется, вы можете удалить первые три инструкции.

       mov ebx, 32      ; number of bits in an int

       mov ecx, eax
       not ecx
       bsf ecx, ecx     ; count trailing zeroes in ecx
                        ; i.e. trailing ones in eax
       cmovz ecx, ebx   ; give correct result for eax == -1
       cmp ecx, edx
       jnz fail         ; have as many ones as desired?

Если ваш процессор имеет tzcnt, вы можете избежать этой инструкции:

        mov ecx, eax
        not ecx
        tzcnt ecx, ecx
        cmp ecx, edx
        jnz fail

Обратите внимание, что на процессорах, у которых его нет, tzcnt молча интерпретируется как bsf, нарушая ваш код; Наблюдение за неопределенным исключением инструкции недостаточно, чтобы убедиться, что оно присутствует.

Чтобы убедиться, что никакие другие биты не установлены, очистите суффикс и проверьте, равен ли результат нулю:

        lea ecx, [rax+1]
        and ecx, eax      ; ecx = eax & eax + 1
                          ; i.e. clear all trailing 1 bits
        jnz fail          ; fail if any bits are left

Хотя самая быстрая реализация, вероятно, просто создала бы справочную таблицу со всеми длинами суффиксов, а затем проверила, совпадает ли ваш суффикс:

lut:    dd 00000000h, 00000001h, 00000003h, 00000007h
        dd 0000000fh, 0000001fh, 0000003fh, 0000007fh
        dd 000000ffh, 000001ffh, 000003ffh, 000007ffh
        dd 00000fffh, 00001fffh, 00003fffh, 00007fffh
        dd 0000ffffh, 0001ffffh, 0003ffffh, 0007ffffh
        dd 000fffffh, 001fffffh, 003fffffh, 007fffffh
        dd 00ffffffh, 01ffffffh, 03ffffffh, 07ffffffh
        dd 0fffffffh, 1fffffffh, 3fffffffh, 7fffffffh
        dd ffffffffh

        ...

        cmp lut[edx * 4], eax
        jnz fail
...