Получить длину строки C 16 или 32-байтового буфера фиксированного размера?(Ширина регистра XMM или YMM) - PullRequest
4 голосов
/ 05 июня 2019

Есть ли способ получить длину строки ASCII, которая хранится в 16- или 32-байтовом буфере, загрузив ее в регистр XMM или YMM? По сути, я ищу индекс (в битах или байтах) первого нулевого байта.

Моя цель - избежать зацикливания и разветвления. Я надеюсь, что что-то существует в AVX или SSE по образцу BSF (сканирование битов вперед), но работает с байтами, а не с битами.

Может быть, что-то вроде следующего?

_my_constant_time_strlen:
 vpxor ymm0, ymm0
 VPCMPEQB ymm0, ymm0, [rdi]
 vpmovmskb eax, ymm0
 bsf eax, eax
 ; string length is in eax?

   ; and rax, 31              ; editor's note: useless AND
 ret

1 Ответ

7 голосов
/ 05 июня 2019

Это - это в точности как вы реализуете strlen или memchr с AVX2. (Для буфера фиксированного размера, где вы знаете, что где-то в буфере будет совпадение .)

(за исключением того, что теперь у вас есть избыточный and).

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

Вы можете вытащить vpxor -изменение из цикла, если не будете его разрушать.

Реальная работа - это всего 3 моп на процессорах Intel (Haswell / Skylake) : vpcmpeqb - это 1 микроплавкий моп, поскольку вы избегаете режима индексированной адресации. vpmovmskb - 1 моп с задержкой от 2 до 3 циклов. tzcnt равен 1 моп на процессорах Intel или AMD. (bsf также составляет 1 моп на Intel). На Intel tzcnt или bsf имеет 3 цикла задержки.

Таким образом, в основном Intel общая задержка от данных векторной загрузки, готовых к длине в RAX, составляет 1 (vpcmpeqb) + 2 или 3 (movmsk) + 3 (tzcnt) = 6 или 7 циклов. Это не ветвление, просто зависимость от данных, так что это довольно разумно. Это не учитывает задержку использования нагрузки или задержку пересылки хранилища, независимо от того, был ли адрес или данные на критическом пути. А пропускная способность отлично , при 1 стрлн за такт (узкое место на порте 0 и / или порте 1) на Intel.

На AMD Ryzen, vpcmpeqb ymm - это 2 мопа с задержкой 2c. vpmovmskb ymm - это 2 мопа (для порта 2) с задержкой 3c. tzcnt - 2 мопа с задержкой 2с. Таким образом, общая задержка = 7 циклов, а пропускная способность - 1 на 2 цикла, узкое место при пропускной способности movmsk. (Ryzen lzcnt имеет задержку 1 моп / 1с; предположительно tzcnt - это бит + + 1041 * или что-то в этом роде.)

Номера от https://agner.org/optimize/.


Нет инструкции, которая сканирует регистр XMM или YMM на наличие первого ненулевого байта, единственный эффективный способ - pcmpeqb / pmovmskb. Вы можете vmovd xmm0, eax в конце, если вы хотите, чтобы позиция байта в векторном регистре вместо целого числа.

Но обычно вы хотите результат в целочисленном регистре.


Единственное, что приходит на ум при горизонтальном векторном поиске / сканировании без movmsk to integer сначала - это phminposuw, что не то, что вам нужно.

Или, может быть, vpand с вектором 1,2,4,8,16, ... степеней 2, затем vpsadbw для горизонтального добавления байтов. Самый младший установленный бит в результате указывает позицию первого 0 в этом 8-байтовом фрагменте.

Или вы можете сделать log2 (vector_length) шагами тасования и маскирования следующего элемента, чтобы получить вектор, в котором только первый 0 на входе имеет элемент 0xff. Затем VPAND с вектором от 0,1,2,3,4,... и vpsadbw до hsum, и единственным ненулевым элементом будет позиция байта. Но это намного дороже, чем vpcmpeqb / vpmovmskb / bsf / vmovd вернуться в регистр XMM, если вы действительно хотите получить результат в регистре XMM по какой-то причине.


Если вы не уверены, что есть нулевые байты, используйте tzcnt вместо bsf, потому что он выдает 32 (размер операнда) для ввода со всеми нулями. bsf также медленнее на AMD, так что на самом деле вы всегда должны использовать tzcnt здесь .

bsf оставляет назначение без изменений, если вход был нулевым. (Документально подтверждено AMD; Intel говорит «undefined», но в любом случае реализует его.)

И / или проверка ZF после битового сканирования для обнаружения случая ввода со всеми нулями. (tzcnt устанавливает CF для нулевого входа и устанавливает ZF в соответствии с нулевым или нет выходным сигналом.)


Кроме того, обнуляйте регистр с vpxor XMM, а не YMM. Сохраняет моп на AMD.

...