Это - это в точности как вы реализуете 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.