Как использовать инструкцию сборки 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 long, которые влияют на регистры в таком

способ:

uint64 a, b;
__asm__ volatile (“POPCNT %1, %0;”
            :”=r”(b)
            :”r”(a)
            :
        )

gcc сообщает "несоответствие типа операнда для popcnt",

  • а запись POPCNTQ приводит к «недопустимому суффиксу инструкции для popcnt».

Было бы так здорово, если бы POPCNT поддерживал 128-битные регистры xmm...

Есть ли обходной путь для применения POPCNT к 64-битным данным в сборке?

PS: обсуждение подсчета всплывающих окон SSSE3 с использованием перемешивания по сравнению с производительностью SSE4 POPCNT завершилось здесь http://danluu.com/assembly-intrinsics/ и было связано только с тем, что использование встроенных функций не всегда обеспечивает эффективный ассемблерный код. Приятно использовать встроенные функции для быстрой оптимизации кода C/C++, и если этого достаточно для удовлетворения потребностей, прекрасно. Но в остальном я получил почти 30-процентное улучшение производительности при кодировании всплывающего подсчета с использованием перетасовки в ассемблере по сравнению со встроенным.


person user3581220    schedule 23.01.2015    source источник
comment
Я могу ошибаться, но что именно означают RAX и R15 в 32-битном режиме? Это имена 64-битных регистров GP, которых по определению нет в 32-битном режиме. Широкие регистры — это векторные регистры XMM. И хотя "r"(a) кажется невинным синтаксисом, он требует, чтобы a соответствовал регистру GP.   -  person MSalters    schedule 26.01.2015
comment
и обратите внимание, что вы используете умные кавычки, которые являются недопустимыми символами в C и C++, поэтому они даже не скомпилируются   -  person phuclv    schedule 27.01.2015


Ответы (4)


popcnt — целочисленная инструкция. Таким образом, в 32-битном режиме вы не можете использовать его с 64-битными операндами. Вам нужно будет вычислить popcnt для двух половин и сложить их вместе. Это то, что все протестированные мной версии clang делают для встроенной функции. Однако я не смог получить ни одну версию gcc для использования инструкции popcnt. Поэтому, хотя обычно рекомендуется встроенный, в этом случае встроенный ассемблер может быть лучше.

person Jester    schedule 23.01.2015
comment
Ну, это неудобно, что я могу обрабатывать до 128 бит упакованных данных в 32-битной ОС с использованием SSEx, и эта сборка POPCNT не может работать с 64-битными данными, даже упакованными :-s - person user3581220; 23.01.2015
comment
@ user3581220, почему он должен работать с 64-битными значениями в 32-битном режиме, если там нет даже 64-битного регистра? - person phuclv; 26.01.2015
comment
Потому что есть 128-битные регистры xmm, которые доступны в 32-битной ОС (учитывая, что значения упакованы, это не было бы большой проблемой). - person user3581220; 26.01.2015
comment
@user3581220 user3581220 Intel решила сделать регистры XMM доступными во всех режимах работы процессора, но AMD решила разрешить доступ только к полным 64 битам целочисленных регистров в 64-битном длинном режиме. - person Ross Ridge; 27.01.2015
comment
@ user3581220 Регистры XMM предназначены не для большого 128-битного числа, а для одновременного хранения нескольких значений. Они используются по-разному, поскольку XMM не является регистром общего назначения. Также для использования 64-битных регистров требуется префикс REX, который принимает код операции для inc и dec. Вот почему вы не можете использовать однобайтовые inc/dec в 64-битном режиме. Причина в том, что для префикса и опкода в x86 почти не осталось кода. - person phuclv; 27.01.2015
comment
@RossRidge, возможно, у AMD не было выбора. Они должны выбирать между отбрасыванием некоторых используемых инструкций в 32-битном режиме (что ломает многое) или запретом на доступ к 64-битным регистрам в этом режиме. - person phuclv; 27.01.2015

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
person zx485    schedule 23.01.2015
comment
Я уже видел этот документ на зеркальном сайте, но хотел быть уверенным, что никто не найдет способ использовать 64-битную версию. - person user3581220; 25.01.2015
comment
Что ж, мой текущий метод сборки на самом деле выполняет XOR для двух векторов по 64 байта, а затем обрабатывает расстояние Хэмминга, поэтому я попытался заменить перемешивание на 32-битный POPCNT двумя разными способами: а) запись результата XOR из xmm в выровненный массив перед обработкой POPCNT, b) сдвиг регистра xmm результата XOR на 4 байта и помещение его в 32-битный регистр exx перед обработкой POPCNT в этом регистре. Упорядочив использование регистров в коде с учетом задержки инструкций и пропускной способности, мне не удалось улучшить метод тасования (на самом деле я потерял от 5 до 15%). - person user3581220; 26.01.2015
comment
Я все еще не уверен, чего вы пытаетесь достичь или какой алгоритм вы пытаетесь реализовать. Во всяком случае, я добавил в пост 32-битную версию, которая вычисляет расстояние Хэмминга двух 64-битных значений (QWord) без использования регистров xmm. Довольно прямолинейно и должно быть очень быстро. - person zx485; 26.01.2015
comment
Я тоже пробовал. Разница с popcount при перемешивании SSSE3 составляет всего 1-2%. - person user3581220; 29.01.2015
comment
Однако на этот раз я протестировал выполнение на каждом ядре с OpenMP одного потока для 32-битного XOR и POPCOUNT с использованием 32-битных регистров GP и другого потока для 128-битного XOR и SSSE3 Shuffle popcount с использованием регистров xmm. Один базовый процесс 200 000 векторов в цикле for разделен для потоков сначала как [0, 100 000[ и [100 000, 200 000[ , затем как for(i=0; i‹200.000; i+=2) и for(i=1; i‹200.000; i+=2). Не было никакого выигрыша по сравнению с тем, чтобы позволить OpenMP запускать SSE4 POPCNT в два потока для ядра. - person user3581220; 29.01.2015

Я не знаю, существует ли 32-битная инструкция popcnt, но могу поспорить, что вы не можете использовать 64-битную команду popcnt в 32-битном коде. Попробуйте объявить a и b как uint32_t. Кстати, uint64_t — это стандарт C, а uint64 — нет.

person gnasher729    schedule 23.01.2015
comment
Да, есть. Но я пропущу большую часть (все?) улучшения по сравнению с моей текущей реализацией сборки SSSE3 с использованием перемешивания. - person user3581220; 23.01.2015
comment
Моя ошибка насчет uint64_t. Написал это только для того, чтобы показать, как я передал аргументы в регистры ассемблерной функции. - person user3581220; 23.01.2015

После реализации 32-битного POPCNT с использованием ассемблера кажется, что нет никаких реальных улучшений по сравнению с методом случайной сборки SSSE3. Как я и подозревал, только 64-битная версия POPCNT может почти удвоить скорость.

person user3581220    schedule 25.01.2015