Intel SSE: почему "mm extract ps "возвращает" int "вместо " float"?

почему _mm_extract_ps возвратить int вместо float?

Как правильно читать один float из регистра XMM в C?

или, скорее, другой способ спросить это:, что напротив _mm_set_ps инструкция?

4 ответов


с документы MSDN, Я считаю, что вы можете бросить результат на поплавок.

Примечание из их примера значение 0xc0a40000 эквивалентно -5.125 (a.m128_f32[1]).

Update: я настоятельно рекомендую ответы от @doug65536 и @PeterCordes (ниже) вместо моего, который, по-видимому, генерирует плохо выполняющийся код на многих компиляторах.


ни один из ответов, похоже, на самом деле не отвечает на вопрос,почему это возвращение int.

причина в том, что extractps инструкция фактически копирует компонент вектора в общий реестр. Это кажется довольно глупым для него, чтобы вернуть int, но это то, что на самом деле происходит - сырое значение с плавающей запятой заканчивается в общем регистре (который содержит целые числа).

если ваш компилятор настроен на создание SSE для всех плавающих точечные операции, то ближе всего к "извлечению" значения в регистр было бы перетасовать значение в низкую компоненту вектора, а затем привести его к скалярному поплавку. Это должно привести к тому, что компонент вектора останется в регистре SSE:

/* returns the second component of the vector */
float foo(__m128 b)
{
    return _mm_cvtss_f32(_mm_shuffle_ps(b, b, _MM_SHUFFLE(0, 0, 0, 2)));
}

на _mm_cvtss_f32 intrinsic свободен, он не генерирует инструкции, он только заставляет компилятор переинтерпретировать регистр xmm как float поэтому он может быть возвращен как таковой.

на _mm_shuffle_ps получает желаемое значение в самый низкий компонент. The _MM_SHUFFLE макрос генерирует непосредственный операнд для результирующего shufps инструкция.

на 2 в Примере получает float из бита 95:64 регистра 127:0 (3-й 32-битный компонент с начала, в порядке памяти) и помещает его в компонент 31: 0 регистра (начало, в порядке памяти).

полученный сгенерированный код, скорее всего, вернет значение естественно в регистре, как и любой другое значение с плавающей запятой возвращается, без неэффективной записи в память и ее чтения.

если вы создаете код, который использует FPU x87 для плавающей точки (для обычного кода C, который не оптимизирован SSE), это, вероятно, приведет к созданию неэффективного кода - компилятор, вероятно, сохранит компонент вектора SSE, а затем использует fld чтобы прочитать его обратно в стек регистров x87. В целом 64-разрядные платформы не используют x87 (они используют SSE для всех с плавающей запятой, в основном скалярные инструкции, если компилятор не векторизует).

Я должен добавить, что я всегда использую C++, поэтому я не уверен, что более эффективно передавать __m128 по значению или по указателю в C. В C++ я бы использовал const __m128 & и такой код будет в заголовке, поэтому компилятор может быть встроенным.


к сожалению, int _mm_extract_ps() не для получения скаляра float элемент из вектора. внутренний не раскрывает форму назначения памяти инструкции (которая может быть полезна для этой цели). Это не единственный случай, когда внутренние компоненты не могут напрямую выразить все, для чего полезна инструкция. :(

gcc и clang знают, как работает инструкция asm, и будут использовать ее таким образом для вас при компиляции других перетасовок; тип-каламбур _mm_extract_ps результат float обычно приводит к ужасному asm от gcc (extractps eax, xmm0, 2 / mov [mem], eax).

имя имеет смысл, если вы думаете о _mm_extract_ps как извлечение IEEE 754 binary32 float bit pattern из домена FP процессора в целочисленный домен (как скаляр C int), а не управляющими ФП битовые шаблоны с целыми вектор ОПС. согласно моему тестированию с gcc, clang и icc (см. ниже), это единственный "портативный" случай использования, где _mm_extract_ps компилируется в good asm во всех компиляторах. Все остальное - это просто специфичный для компилятора хак, чтобы получить asm, который вы хотите.


соответствующая инструкция asm EXTRACTPS r/m32, xmm, imm8. Обратите внимание, что местом назначения может быть память или целое регистрация, но не другой регистр XMM. Это эквивалент FP PEXTRD r/m32, xmm, imm8 (также в SSE4.1), где форма integer-register-destination более очевидно полезна. EXTRACTPS нет наоборот INSERTPS xmm1, xmm2/m32, imm8.

возможно, это сходство с PEXTRD упрощает внутреннюю реализацию, не повреждая use-case извлечения в память (для asm, а не внутренних компонентов) или, возможно, SSE4.1 дизайнеры Intel считали, что на самом деле это более полезно, чем в качестве неразрушающего копирования и перетасовки FP-домена (чего x86 серьезно не хватает без AVX). Существуют инструкции FP-vector, которые имеют источник XMM и назначение memory-or-xmm, например MOVSS xmm2/m32, xmm, поэтому такая инструкция не будет новой. Интересный факт: опкоды для PEXTRD и EXTRACTPS отличаются только в последнем бит.


в сборке скаляр float - это просто низкий элемент регистра XMM (или 4 байта в памяти). Верхние элементы XMM даже не должны быть обнулены для таких инструкций, как ADDSS для работы без каких-либо дополнительных исключений FP. В вызывающих соглашениях, которые передают / возвращают FP args в регистрах XMM (например, все обычные x86-64 ABIs),float foo(float a) необходимо предположить, что верхние элементы XMM0 содержат мусор при входе, но могут оставлять мусор в высоких элементах XMM0 при возврате. (Подробнее).

как указывает @doug, другие инструкции shuffle могут использоваться для получения плавающего элемента вектора в нижней части регистра xmm. это уже была в основном решенная проблема в SSE1 / SSE2, и кажется, EXTRACTPS и INSERTPS не пытались решить его для регистровых операндов.


SSE4.1 INSERTPS xmm1, xmm2/m32, imm8 является одним из лучших способов для компиляторов реализовать _mm_set_ss(function_arg) когда скалярный поплавок уже находится в регистре, и они не могут/не оптимизируют обнуление верхних элементов. (который большую часть времени для компиляторов, кроме clang). Этот связанный вопрос также дополнительно обсуждает неспособность встроенных устройств предоставлять версии инструкций загрузки или хранения как EXTRACTPS, INSERTPS и PMOVZX, которые имеют операнд памяти более узкий, чем 128b (таким образом, не требующий выравнивания даже без AVX). Невозможно написать безопасный код, который компилируется так же эффективно, как то, что вы можете сделать в asm.

без AVX 3-операнда SHUFPS x86 не обеспечивает полностью эффективный и универсальный способ копирования и перетасовки вектора FP способом integer PSHUFD может. SHUFPS другой зверь, если не используется на месте с src=dst. Сохранение оригинала требует MOVAPS, который стоит uop и задержки на процессорах перед IvyBridge, и всегда стоит размер кода. Использование PSHUFD между инструкциями FP стоит задержки (задержки обхода). (См.этот ответ горизонтальной суммы для некоторых трюков, таких как использование SSE3 MOVSHDUP).

SSE4.1 INSERTPS может извлечь один элемент в отдельный регистр, но AFAIK по-прежнему зависит от предыдущего значения назначения, даже если все исходные значения будут заменены. Ложные зависимости, подобные этой, плохи для выполнения вне порядка. xor-обнуление регистр в качестве назначения для INSERTPS по-прежнему будет 2 uops и будет иметь меньшую задержку, чем MOVAPS+SHUFPS на SSE4.1 процессоры без mov-устранение для MOVAPS с нулевой задержкой (только Penryn, Nehalem, Sandybridge. Также Silvermont, если вы включаете маломощные процессоры). Однако размер кода немного хуже.


используя _mm_extract_ps а затем введите-punning результат обратно в float (как и предполагалось в принятом в настоящее время ответе и его комментарии) - плохая идея. Для вашего кода легко скомпилировать что-то ужасное (например, EXTRACTPS в память, а затем загрузить обратно в регистр XMM) на gcc или icc. Clang, похоже, невосприимчив к поведению braindead и делает свою обычную тасовку-компиляцию с собственным выбором инструкций тасовки (включая соответствующее использование EXTRACTPS).

я пробовал эти примеры с gcc5.4 -O3 -msse4.1 -mtune=haswell, clang3.8.1, и icc17, в проводнике компилятора Godbolt. Я использовал режим C, а не C++, но каламбур типа на основе союза разрешен в GNU C++ как расширение ISO C++. Приведение указателя для каламбура типов нарушает строгое сглаживание в C99 и c++, даже с расширениями GNU.

#include <immintrin.h>

// gcc:bad  clang:good  icc:good
void extr_unsafe_ptrcast(__m128 v, float *p) {
  // violates strict aliasing
  *(int*)p = _mm_extract_ps(v, 2);
}

  gcc:   # others extractps with a memory dest
    extractps       eax, xmm0, 2
    mov     DWORD PTR [rdi], eax
    ret


// gcc:good  clang:good  icc:bad
void extr_pun(__m128 v, float *p) {
  // union type punning is safe in C99 (and GNU C and GNU C++)
  union floatpun { int i; float f; } fp;
  fp.i = _mm_extract_ps(v, 2);
  *p = fp.f;     // compiles to an extractps straight to memory
}

   icc:
    vextractps eax, xmm0, 2
    mov       DWORD PTR [rdi], eax
    ret       


// gcc:good  clang:good  icc:horrible
void extr_gnu(__m128 v, float *p) {
  // gcc uses extractps with a memory dest, icc does extr_store
  *p = v[2];
}

 gcc/clang:
    extractps       DWORD PTR [rdi], xmm0, 2
 icc:
    vmovups   XMMWORD PTR [-24+rsp], xmm0
    mov       eax, DWORD PTR [-16+rsp]      # reload from red-zone tmp buffer
    mov       DWORD PTR [rdi], eax

// gcc:good  clang:good  icc:poor
void extr_shuf(__m128 v, float *p) {
  __m128 e2 = _mm_shuffle_ps(v,v, 2);
  *p = _mm_cvtss_f32(e2);  // gcc uses extractps
}

 icc:   (others: extractps right to memory)
    vshufps   xmm1, xmm0, xmm0, 2
    vmovss    DWORD PTR [rdi], xmm1

когда вы хотите получить конечный результат в регистре xmm, компилятор должен оптимизировать ваши экстракты и сделать что-то совершенно другое. Gcc и clang оба преуспевают, но ICC нет.

// gcc:good  clang:good  icc:bad
float ret_pun(__m128 v) {
  union floatpun { int i; float f; } fp;
  fp.i = _mm_extract_ps(v, 2);
  return fp.f;
}

  gcc:
    unpckhps        xmm0, xmm0
  clang:
    shufpd  xmm0, xmm0, 1
  icc17:
    vextractps DWORD PTR [-8+rsp], xmm0, 2
    vmovss    xmm0, DWORD PTR [-8+rsp]

обратите внимание, что icc сделал плохо для extr_pun, тоже, поэтому ему не нравится тип-каламбур на основе союза для этого.

явный победитель здесь делает перетасовку "вручную" с _mm_shuffle_ps(v,v, 2), и через _mm_cvtss_f32. мы получили оптимальный код от каждого компилятора как для регистра, так и для назначения памяти, за исключением ICC, который не смог использовать EXTRACTPS для случая memory-dest. С AVX, shufps + отдельный магазин по-прежнему только 2 uops на Intel Процессоры, просто больший размер кода и нужен регистр tmp. Без AVX, однако, это будет стоить MOVAPS, чтобы не уничтожить исходный вектор :/


по данным таблицы инструкций Agner Fog, все процессоры Intel, кроме Nehalem, реализуют версии регистра назначения как PEXTRD, так и EXTRACTPS с несколькими uops: обычно просто перетасовывают uop + A MOVD uop для перемещения данных из векторного домена в GP-целое число. Nehalem register-destination EXTRACTPS is 1 uop for порт 5, с задержкой цикла 1+2 (задержка обхода 1+).

я понятия не имею, почему им удалось реализовать EXTRACTPS как один uop, но не PEXTRD (который составляет 2 uops и работает с задержкой цикла 2+1). Nehalem MOVD - 1 uop (и работает на любом порту ALU) с задержкой цикла 1+1. (Я думаю, что +1 для задержки обхода между Vec-int и целочисленными правилами общего назначения).

Nehalem много заботится о векторных FP против целых доменов; процессоры SnB-семейства имеют меньшие (иногда нулевые) обход задержки задержки между доменами.

версии памяти-dest PEXTRD и EXTRACTPS-это 2 uops на Nehalem.

на Broadwell и более поздних версиях EXTRACTPS назначения памяти и PEXTRD - 2 uops, но на Sandybridge через Haswell EXTRACTPS назначения памяти-3 uops. Память-назначение PEXTRD-2 uops на всем, кроме Sandybridge, где это 3. Это кажется странным, и таблицы Агнера Фога иногда имеют ошибки, но это возможно. Микро-фьюжн не работе с некоторыми инструкциями по некоторым микроархитектур.

если бы какая-либо инструкция оказалась чрезвычайно полезной для чего-либо важного (например, внутри внутренних циклов), разработчики ЦП построили бы исполнительные блоки, которые могли бы сделать все это как один uop (или, возможно, 2 для memory-dest). Но это потенциально требует больше битов во внутреннем формате uop (который sandybridge упростил).

интересный факт: _mm_extract_epi32(vec, 0) compiles (на большинстве компиляторов) в movd eax, xmm0 что короче и быстрее, чем pextrd eax, xmm0, 0.

интересно, что они выполняют по-разному на Nehalem (который много заботится о векторных FP против целых доменов и вышел вскоре после SSE4.1 был введен в Penryn (45nm Core2)). EXTRACTPS с назначением регистра-1 uop, с задержкой цикла 1+2 (+2 от задержки обхода между FP и целочисленным доменом). PEXTRD 2 uops, и бежит в латентности цикла 2+1.


попробовать _mm_storeu_ps, или любой из вариаций операции магазина SSE.