У меня есть четыре двухбитовых битовых поля, хранящихся в одном байте. Каждое битовое поле может, таким образом, представлять 0, 1, 2 или 3. Например, здесь 4 возможных значения, где первые 3 битовых поля равны нулю:
00 00 00 00 = 0 0 0 0
00 00 00 01 = 0 0 0 1
00 00 00 10 = 0 0 0 2
00 00 00 11 = 0 0 0 3
Я хотел бы эффективный способ суммировать четыре битовых поля. Например:
11 10 01 00 = 3 + 2 + 1 + 0 = 6
8-битная таблица поиска на современном процессоре Intel x64 занимает 4 цикла, чтобы вернуть ответ от L1. Кажется, должен быть какой-то способ быстрее вычислить ответ. 3 цикла дают место для 6-12 простых бит операций. В качестве стартера прямая маска и сдвиг выглядят так, как будто это займет 5 циклов на Sandy Bridge:
Предполагая, что битовые поля: d c b a
, и эта маска: 00 00 00 11
Разъяснение с помощью Иры: это предполагает, что a
, b
, c
и d
идентичны и все были установлены на начальную byte
. Как ни странно, я могу сделать это бесплатно. Поскольку я могу сделать 2 загрузки за цикл, вместо загрузки byte
один раз, я могу просто загрузить его четыре раза: a
и d
в первом цикле, b
и c
на втором. Вторые две нагрузки будут отложены на один цикл, но я не нуждаюсь в них до второго цикла. Разделение ниже показывает, как вещи должны разрываться на отдельные циклы.
a = *byte
d = *byte
b = *byte
c = *byte
latency
latency
a &= mask
d >>= 6
b >>= 2
c >>= 4
a += d
b &= mask
c &= mask
b += c
a += b
Другая кодировка для битподов, чтобы облегчить логику, на самом деле была бы прекрасной, если она вписывается в один байт и каким-то образом сопоставляет ее с этой схемой. Отбрасывание на сборку тоже прекрасное. Текущая цель - Sandy Bridge, но цель Haswell или за ее пределами тоже прекрасна.
Применение и мотивация: я пытаюсь сделать процедуру распаковки битов с открытым исходным кодом быстрее. Каждое битовое поле представляет собой сжатую длину каждого из следующих четырех целых чисел. Мне нужна сумма, чтобы узнать, сколько байтов мне нужно перепрыгнуть, чтобы перейти к следующей группе из четырех. Текущая петля занимает 10 циклов, причем 5 из них подходят для поиска, который я пытаюсь избежать. Бритье цикла будет ~ 10% улучшения.
Изменить: Первоначально я сказал "8 циклов", но, как указывает Евгений, я ошибался. Как указывает Евгений, единственный раз, когда есть косвенная 4-х циклическая нагрузка, - это загрузка из первых 2K системной памяти без использования индексного регистра. Правильный список задержек можно найти в Руководство по оптимизации архитектуры Intel Раздел 2.12
> Data Type (Base + Offset) > 2048 (Base + Offset) < 2048
> Base + Index [+ Offset]
> Integer 5 cycles 4 cycles
> MMX, SSE, 128-bit AVX 6 cycles 5 cycles
> X87 7 cycles 6 cycles
> 256-bit AVX 7 cycles 7 cycles
Редактирование: Я думаю, именно так решение Айры ниже будет разбиваться на циклы. Я думаю, что он также занимает 5 циклов рабочей нагрузки.
a = *byte
b = *byte
latency
latency
latency
a &= 0x33
b >>= 2
b &= 0x33
c = a
a += b
c += b
a &= 7
c >>= 4
a += c