inline uint8_t pack8bools(bool* a)
{
uint64_t t = *((uint64_t*)a);
return 0x8040201008040201*t >> 56;
}
void unpack8bools(uint8_t b, bool* a)
{
auto MAGIC = 0x8040201008040201ULL;
auto MASK = 0x8080808080808080ULL;
*((uint64_t*)a) = ((MAGIC*b) & MASK) >> 7;
}
Конечно, вам может потребоваться убедиться, что массив bool правильно выровнен на 8 байтов, чтобы избежать снижения производительности и / или UB
Как они работают?
Предположим, у нас есть 8 bools b[0]
- b[7]
, чьи наименее значимые биты называются a-h соответственно, которые мы хотим упаковать в один байт. Рассматривая эти 8 последовательных bool
как одно 64-битное слово и загружая их, мы получим биты в обратном порядке на машине с прямым порядком байтов. Теперь мы сделаем умножение (здесь точки - нулевые биты)
| b7 || b6 || b4 || b4 || b3 || b2 || b1 || b0 |
.......h.......g.......f.......e.......d.......c.......b.......a
x 1000000001000000001000000001000000001000000001000000001000000001
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
↑......h.↑.....g..↑....f...↑...e....↑..d.....↑.c......↑b.......a
↑.....g..↑....f...↑...e....↑..d.....↑.c......↑b.......a
↑....f...↑...e....↑..d.....↑.c......↑b.......a
+ ↑...e....↑..d.....↑.c......↑b.......a
↑..d.....↑.c......↑b.......a
↑.c......↑b.......a
↑b.......a
a
▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬▬
= abcdefghxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Стрелки добавлены, чтобы легче было видеть положение установленных битов в магическом числе. В этот момент 8 младших битов помещены в верхний байт, нам просто нужно замаскировать оставшиеся биты
Таким образом, магическое число для упаковки будет 0b1000000001000000001000000001000000001000000001000000001000000001
или 0x8040201008040201
. Если вы используете машину с прямым порядком байтов, вам нужно использовать магическое число 0x0102040810204080
, которое рассчитывается аналогичным образом
Для распаковки мы можем сделать аналогичное умножение
| b7 || b6 || b4 || b4 || b3 || b2 || b1 || b0 |
abcdefgh
x 1000000001000000001000000001000000001000000001000000001000000001
__________________________________________________________________
= h0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh0abcdefgh
& 1000000010000000100000001000000010000000100000001000000010000000
__________________________________________________________________
= h0000000g0000000f0000000e0000000d0000000c0000000b0000000a0000000
После умножения у нас есть необходимые биты в наиболее значимых позициях, поэтому нам нужно замаскировать нерелевантные биты и сдвинуть остатки в наименее значимые позиции. Выходными данными будут байты, содержащие от a до h с прямым порядком байтов.
Эффективный способ
На более новых процессорах x86 с BMI2 есть инструкции PEXT и PDEP . Функцию pack8bools
, указанную выше, можно заменить на
_pext_u64(*((uint64_t*)a), 0x0101010101010101ULL);
И функция unpack8bools
может быть реализована как
_pdep_u64(b, 0x0101010101010101ULL);