Обнаружить 0xff в многобайтовом слове с помощью побитовых операций - PullRequest
0 голосов
/ 25 апреля 2018

У меня есть 32-разрядное целое число без знака, состоящее из 4 байтов, например: 0x12ff3456.

Я пытаюсь найти побитовую операцию для замены байта 0xff на 0x01 и всего остального на 0x00, например:

0x12ff3456 => 0x00010000

0x543245ff => 0x00000001 ... и т. Д.

Только один из байтов, составляющих 32-разрядное целое число без знака, может быть равен 0xff одновременно. Есть ли у кого-нибудь идеи о том, как выполнить это с минимально возможным количеством операций? Складывание (битовые сдвиги + и) может быть выбрано, но требует слишком много операций.

Ответы [ 2 ]

0 голосов
/ 25 апреля 2018

Существует несколько возможностей, и одна из которых приводит к минимальному количеству команд или быстрейшему времени выполнения, зависит от используемой архитектуры машины и используемого компилятора.Некоторые архитектуры предлагают ANDN инструкции, другие поддерживают логические инструкции с тремя входами, третьи объединяют сдвиги с логическими операциями.Ниже я показываю три варианта, которые проходят исчерпывающий тест.

Два подхода состоят в том, чтобы либо основывать вывод на проверке равенства байтов с помощью 0xFF, либо на байтовом тесте «больше чем» для 0xFE.Они выбираются через FUNC_VARIANT.Тест «больше чем» является дополнительным основанием для теста «меньше чем», для которого предусмотрены два варианта реализации, выбранные с помощью LTU_VARIANT.

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

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

Быстрая проверка с Проводник компилятора показывает, что с gcc, FUNC_VARIANT=1, LTU_VARIANT=0 компилируется в кратчайшие последовательности команд для обоих x86-64 и ARM64 .Это, возможно, не обязательно приведет к максимально возможной производительности.

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

#define FUNC_VARIANT  0
#define LTU_VARIANT   0

#define UINT32_H4  0x80808080U   // byte-wise sign bits (MSBs)
#define UINT32_L4  0x01010101U   // byte-wise LSBs 
#define UINT32_M4  0xffffffffU   // byte-wise maximum

uint32_t sign_to_bool4 (uint32_t a)
{
    return (a >> 7) & UINT32_L4;
}

uint32_t vhaddu4 (uint32_t a, uint32_t b)
{
    /* Peter L. Montgomery's observation (newsgroup comp.arch, 2000/02/11,
       https://groups.google.com/d/msg/comp.arch/gXFuGZtZKag/_5yrz2zDbe4J):
       (A+B)/2 = (A AND B) + (A XOR B)/2.
    */
    return (a & b) + (((a ^ b) >> 1) & ~UINT32_H4);
}

uint32_t ltu4_core (uint32_t a, uint32_t b)
{
    /* Sebastiano Vigna, "Broadword implementation of rank/select queries." 
       In: International Workshop on Experimental and Efficient Algorithms, 
       pp. 154-168, Springer Berlin Heidelberg, 2008.
    */
    return (((a | UINT32_H4) - (b & ~UINT32_H4)) | (a ^ b)) ^ (a | ~b);
}

uint32_t vsetltu4 (uint32_t a, uint32_t b)
{
#if LTU_VARIANT==1
    return sign_to_bool4 (ltu4_core (a, b));
#else // LTU_VARIANT
    return sign_to_bool4 (vhaddu4 (~a, b));
#endif // LTU_VARIANT
}

uint32_t vsetgtu4 (uint32_t a, uint32_t b)
{
    return vsetltu4 (b, a);
}

uint32_t vseteq4 (uint32_t a, uint32_t b)
{
    uint32_t r, t;

    /* Alan Mycroft's null-byte detection algorithm (newsgroup comp.lang.c, 1987/04/08,
       https://groups.google.com/forum/#!original/comp.lang.c/2HtQXvg7iKc/xOJeipH6KLMJ):
       null_byte(x) = ((x - 0x01010101) & (~x & 0x80808080))
    */
    r = a ^ b;         // 0x00 if a == b
    t = r | UINT32_H4; // set msbs, to catch carry out
    r = r ^ t;         // extract msbs, msb = 1 if r < 0x80
    t = t - UINT32_L4; // sign bit = 0, if r was 0x00 or 0x80
    t = r & ~t;        // sign_bit = 1, if r was 0x00
    r = t >> 7;        // convert to bool
    return r;
}

uint32_t func (uint32_t a) 
{
#if FUNC_VARIANT == 1
    return vsetgtu4 (a, ~UINT32_L4); // byte-wise a >ᶸ 0xFE
#else // FUNC_VARIANT
    return vseteq4 (a, UINT32_M4);   // byte-wise a == 0xFF
#endif // FUNC_VARIANT
}

uint32_t ref_func (uint32_t a)
{
    uint8_t a0 = (uint8_t)((a >>  0) & 0xff);
    uint8_t a1 = (uint8_t)((a >>  8) & 0xff);
    uint8_t a2 = (uint8_t)((a >> 16) & 0xff);
    uint8_t a3 = (uint8_t)((a >> 24) & 0xff);
    int p0 = (a0 == 0xff);
    int p1 = (a1 == 0xff);
    int p2 = (a2 == 0xff);
    int p3 = (a3 == 0xff);
    return (((uint32_t)p3 << 24) | ((uint32_t)p2 << 16) |
            ((uint32_t)p1 <<  8) | ((uint32_t)p0 <<  0));
}

int main (void)
{
    uint32_t res, ref, x = 0;

    do {
        res = func (x);
        ref = ref_func (x);
        if (res != ref) {
            printf ("error @ %08x: res=%08x  ref=%08x\n", x, res, ref);
            return EXIT_FAILURE;
        }
        x++;
    } while (x);
    printf ("test passed\n");
    return EXIT_SUCCESS;
}
0 голосов
/ 25 апреля 2018

На странице Bit Twiddling Hacks объясняется, как получить маску, в которой каждый нулевой байт помечен с установленным старшим битом.Вы можете применить это здесь:

((~x - 0x01010101) & x & 0x80808080) >> 7
...