Как использовать инструкцию POPCNT для сборки при работе на 32-битной Ubuntu
Для конкретного проекта я использую gcc и 32-битную 12.04 LTS Ubuntu, работающую на i7 Core с поддержкой инструкций AVX SIMD.
Из-за 32-битной ОС я, очевидно, не могу использовать инструкции AVX, работающие на 256-битной версии. У меня есть доступ к инструкциям SSE4.2 с использованием 128 битов, и POPCNT может работать с 16, 32 и 64 битными данными, поэтому выглядело многообещающе. Но я попробовал несколько способов предоставить 64-битные данные в POPCNT без успеха. GCC 4.6.3 возвращает
- "Неизвестное имя регистра" для r8 до r15,
- "Неправильное имя реестра" для rax-rdx,
- при попытке предоставить регистры mm или дать моей встроенной функции сборки некоторую длину uint64 или long, которые влияют на регистры в такой
путь:
uint64 a, b;
__asm__ volatile (“POPCNT %1, %0;”
:”=r”(b)
:”r”(a)
:
)
gcc сообщает "несоответствие типов операндов для popcnt",
- и запись POPCNTQ приводит к "неверному суффиксу инструкции для popcnt".
Было бы так здорово, если бы POPCNT поддерживал 128-битные регистры xmm...
Любой обходной путь для применения POPCNT на 64-битных данных в сборке?
PS: обсуждение использования попсового счетчика SSSE3 с использованием shuffle по сравнению с производительностью SSE4 POPCNT пришло к заключению здесь http://danluu.com/assembly-intrinsics/ и было связано только с тем фактом, что использование встроенных функций не всегда обеспечивает эффективный код сборки. Хорошо использовать встроенные функции для быстрой оптимизации кода C/C++, и если этого достаточно для удовлетворения потребностей, хорошо. Но в остальном я получил почти 30% -ное улучшение производительности при программировании с использованием shuffle в сборке по сравнению с внутренним.
4 ответа
64-битный POPCOUNT не поддерживается в 32-битных системах, потому что
Префикс REX доступен только в длинном режиме. (не в 32-битной ОС)
следовательно
и запись POPCNTQ приводит к "неверному суффиксу инструкции для popcnt".
см. здесь: http://www.felixcloutier.com/x86/POPCNT.html (цитата ниже)
Opcode Instruction Op/En 64-Bit Mode Compat/Leg Mode Description
F3 0F B8 /r POPCNT r16, r/m16 RM Valid Valid POPCNT on r/m16
F3 0F B8 /r POPCNT r32, r/m32 RM Valid Valid POPCNT on r/m32
F3 REX.W 0F B8 /r POPCNT r64,r/m64 RM Valid N.E. POPCNT on r/m64
Обходным путем будет разделить 64/128 бит на две / четыре 32-битные инструкции:
; a=uint_64, 64 bit operand, little endian
popcount eax, dword ptr [a]
popcount edx, dword ptr [a+4]
add eax, edx
xor edx, edx ; for first mov below
mov dword ptr [b], edx ; not neccessary, only due to 64 target op (will there ever be 2^64 bits set???)
mov dword ptr [b+4], eax
РЕДАКТИРОВАТЬ: 64-битная версия размера операнда (двоичного) HammingDistance в коде MASM32:
Hamming_64 PROC word1:QWORD , word2: QWORD
mov ecx, dword ptr [word1]
mov edx, dword ptr [word1+4]
xor ecx, dword ptr [word2]
xor edx, dword ptr [word2+4]
popcnt eax, ecx
popcnt ebx, edx
add eax, ebx ; returns distance in EAX
ret
Hamming_64 ENDP
popcnt
целочисленная инструкция Таким образом, в 32-битном режиме вы не можете использовать его с 64-битными операндами. Вам нужно будет вычислить popcnt
для двух половинок и сложите их вместе. Это то, что все версии Clang, которые я тестировал, делают для встроенного. Тем не менее, я не мог заставить какую-либо версию gcc использовать инструкцию popcnt. Таким образом, хотя обычно рекомендуется встроенная функция, в этом случае встроенный asm может быть лучше.
Я не знаю, есть ли 32-битная команда popcnt, но держу пари, что вы не можете использовать 64-битную popcnt в 32-битном коде. Попробуйте объявить a и b как uint32_t. Кстати, uint64_t - это стандартная версия C, а uint64 - нет.
После реализации 32-битного POPCNT с использованием сборки, похоже, что по сравнению с методом сборки в случайном порядке SSSE3 реального улучшения нет. Как я и подозревал, только 64-битная версия POPCNT может почти удвоить скорость.