Напишите несколько юнит-тестов, которые проходят по оригиналу!
Прежде всего, SSE2 является базовой для x86-64, поэтому вам определенно следует использовать это вместо 64-битных целых чисел.
GCC (в отличие от MSVC) не предполагает нарушений строгого псевдонима, поэтому может потребоваться установить функции диапазона битов (которые приводят входящий указатель к знаку int*
(!!) или uint64_t*
в зависимости от WIN64 или нет) скомпилировано с -fno-strict-aliasing для точного определения приведения указателя.
Вы можете заменить часть цикла функций set / clear для диапазона битов на memset (который может встроить gcc) или рукописный встроенный цикл SSE, если вы ожидаете, что размер обычно будет маленьким (например, менее 200 байт или поэтому не стоит тратить время на вызов libc memset)
Я думаю, что эти dntil0
функции в первом блоке являются просто циклами поиска битов для первого 0 или первого 1 бита, вперед или назад.
Перепишите их с нуля с помощью встроенных функций SSE2 : _mm_cmpeq_epi8
/ _mm_movemask_epi8
, чтобы найти первый байт, который не равен 0 или 0, а затем используйте bsf
или bsr
об этом.
См. Исходный код glibc для SSE2 memchr или любую более простую реализацию, оптимизированную для SSE2, чтобы узнать, как выполнить поиск по байту. Или glibc memmem
для примера сравнения для , равного , но это легко: вместо поиска ненулевого _mm_movemask_epi8()
(указывающего на совпадение), ищите результат, который != 0xffff
(все), чтобы указать, что было несоответствие. Используйте bsf
или bsr
для этой битовой маски, чтобы найти индекс байта в векторе SIMD.
Таким образом, в общей сложности вы будете использовать BSR или BSF дважды в каждой функции: по одному, чтобы найти индекс байта в векторе SIMD, и снова, чтобы найти битовый индекс в целевом байте.
Для функции битового сканирования используйте GCC __builtin_clz
или __builtin_ctz
, чтобы найти первый 1
бит. Бит тиддлинг: какой бит установлен?
Для поиска первого нуля вместо первого, побитового инвертирования, например __builtin_ctz( ~p[idx] )
, где p
- это unsigned char*
в вашем буфере поиска (который вы использовали _mm_loadu_si128()
on), и idx
это смещение в этом 16-байтовом окне. (То, что вы вычислили с __builtin_ctz()
для результата movemask
, который вышел из векторного цикла.)
Как работал оригинал:
z -= 32
зацикливается на 32 бита (размер int
, потому что это было написано при условии, что оно будет скомпилировано для Windows x86 или x86-64 Windows).
lptr[z>>5];
преобразует битовый индекс в индекс int
. Так что это просто цикл по буферу 1 int
за один раз.
Когда он находит 4-байтовый элемент != 0xFFFFFFFF
, он находит int
, содержащий бит, который не равен 1; то есть он содержит бит, который мы ищем. Таким образом, он использует bsf
или bsr
для битового сканирования и поиска положения этого бита в this int
.
Это добавляет это к z
(битовая позиция начала этого int
).
Это точно такой же алгоритм, который я описал выше, но реализовал одно целое число за раз вместо 16 байтов за раз.
Он действительно должен использовать uint32_t
или unsigned int
для битовых манипуляций, без подписи int
, но он, очевидно, работает правильно на MSVC.
if (z >= zsiz) return(zsiz);
Это проверка размера, чтобы выйти из цикла, если бит не найден.