Я пытаюсь найти первый экземпляр символа, в этом случае '' ', используя simd (AVX2 или ранее). Я хотел бы использовать _mm256_cmpeq_epi8, но тогда мне нужен быстрый способ найти, если какой-либо из результирующие байты в __m256i были установлены в 0xFF. Затем план должен был использовать _mm256_movemask_epi8 для преобразования результата из байтов в биты и использовать ffs для получения соответствующего индекса. Лучше ли вы перемещать часть за раз, используя _mm_movemask_epi8? Любые другие предложения?
Найти первый экземпляр персонажа с помощью simd
Ответ 1
У вас есть правильная идея с _mm256_cmpeq_epi8
→ _mm256_movemask_epi8
. AFAIK, что оптимальный способ реализовать это для процессоров Intel по крайней мере. PMOVMSKB r32, ymm
- это такая же скорость, что и 16-байтная версия XMM, поэтому было бы огромной потерей для распаковки двух полос вектора 256b и их одновременного перемещения, а затем рекомбинации целочисленных результатов. (Источник: Таблица инструкций Agner Fog. См. Другие перфомансы в x86 тег wiki.)
Сделайте код внутри цикла максимально эффективным, оставив ffs
до тех пор, пока не определите ненулевой результат из _mm256_movemask_epi8
.
TEST/JCC может вставлять макросов в один uop, но BSF/JCC не работает, поэтому требуется дополнительная инструкция. (И вам было бы сложно получить компилятор C для выпуска BSF/JCC в любом случае. Вероятнее всего, ответвление на результат ffs
даст вам какой-то тест для ввода, отличный от нуля, затем BSF, затем добавьте 1, затем сравните-и-ветвь. Это явно ужасно по сравнению с просто проверкой результата movemask.)
Также обратите внимание, что для подобных задач сравнение movemask (например, чтобы проверить, что оно равно 0xFFFFFFFF) так же эффективно, как разветвление на нем, отличное от нуля.
Как предложил Павел Р., рассмотрение некоторых реализаций strlen, strchr и memchr может быть информативным. Существует несколько рукописных реализаций asm в реализациях libc с открытым исходным кодом и в других местах. (например, glibc и Agner Fog asmlib.)
Многие версии glibc просматривают до границы выравнивания, затем используют развернутый цикл, который читает 64B за раз (в 4 векторах SSE, поскольку я не думаю, что glibc имеет версию AVX2).
Чтобы оптимизировать длинные строки, уменьшите накладные расходы при проверке результатов сравнения путем сравнения результатов сравнения ORing и проверьте это. Если вы найдете хит, вернитесь и повторите проверку ваших векторов, чтобы увидеть, у какого вектора был хит.
Может быть несколько эффективнее делать ffs
на одном 64-битном целое, которое вы создали из нескольких результатов movemask (со сдвигом и |
). Я не уверен в том, чтобы делать это внутри цикла перед тестированием на ноль; Я не помню, сделала ли это одна из стратегий glibc strlen или нет.
Все, что я предложил здесь, это материал, который можно увидеть в asm в различных стратегиях glibc для strlen, memchr и связанных функций. Здесь sysdeps/x86_64/strlen.S, но я могу найти другой исходный файл где-нибудь, используя больше, чем базовый SSE2. (Или нет, я мог бы думать о другой функции, возможно, ничего не получилось за SSE2, до тех пор, пока AVX (3-операндовые insns) и AVX2 (целые векторы 256b).
См. также:
- glibc
strchr-avx2.S
(у Woboq.org есть хороший исходный браузер с полезным поиском имен файлов/символов). - glibc
memchr-avx2.S
glibc memchr использует PMAXUB вместо POR. Я не уверен, что это полезно для какой-то тайной микроархитектурной причины, но оно работает на меньшем количестве портов на большинстве процессоров. Возможно, это желательно, чтобы избежать конфликтов ресурсов с чем-то еще? IDK, кажется странным, поскольку он конкурирует с PCMPEQB.