Почему в SSE есть 128-битные функции загрузки?

Я ковыряюсь в чей-то еще код и в настоящее время пытаюсь понять, почему _mm_load_si128 существует.

По сути, я попытался заменить

_ra = _mm_load_si128(reinterpret_cast<__m128i*>(&cd->data[idx]));

с

_ra = *reinterpret_cast<__m128i*>(&cd->data[idx]);

и он работает и выполняет точно то же самое.

Я понял, что функции загрузки существуют для меньших типов только для удобства, поэтому людям не придется вручную их накапливать в непрерывную память, но для данных, которые уже находятся в правильном порядке, зачем беспокоиться?

Есть ли что-то еще, что _mm_load_si128? Или это по существу просто круговой способ присвоения значения?

Ответ 1

В SSE существуют явные и неявные нагрузки.

  • _mm_load_si128(reinterpret_cast<__m128i*>(&cd->data[idx])); - явная загрузка
  • *reinterpret_cast<__m128i*>(&cd->data[idx]); - неявная загрузка

При явной загрузке вы явно инструктируете компилятор загружать данные в регистр XMM - это "официальный" способ Intel для этого. Вы также можете контролировать, является ли нагрузка выравниваемой или невыровненной нагрузкой, используя _mm_load_si128 или _mm_loadu_si128.

Хотя в качестве расширения большинство компиляторов также могут автоматически генерировать нагрузки XMM, когда вы выполняете type-punning, но таким образом вы не можете контролировать выравнивание или выравнивание нагрузки. В этом случае, поскольку на современных процессорах нет ограничений производительности при использовании невыровненных нагрузок при выравнивании данных, компиляторы обычно используют универсальные нагрузки повсеместно.

Другой, более важный аспект заключается в том, что при неявных нагрузках вы нарушаете правила strict aliasing, что может привести к поведению undefined. Хотя стоит упомянуть, что - как часть расширения - компиляторы, поддерживающие Intel, не склонны применять строгие правила псевдонимов для типов заполнителей XMM, таких как __m128, __m128d, __m128i.

Тем не менее, я считаю, что явные нагрузки более чистые и более пуленепробиваемые.


Почему компиляторы не склонны применять строгие правила псевдонимов для типов заполнителей SSE?

1-я причина заключается в разработке свойств SSE: есть очевидные случаи, когда вам нужно использовать пульт типа, поскольку нет другого способа использовать некоторые из встроенных функций. Мистический ответ прекрасно описывает его.

Как отметил Коди Грей в комментариях, стоит упомянуть, что исторически instrinsics MMX (которые в настоящее время в основном заменяются SSE2) даже не предоставляют явные нагрузки или хранилища - вам приходилось использовать пул типа.

Вторая причина (несколько связанная с первой) заключается в определениях типов этих типов.

GCC typedef для типов заполнителей SSE/SSE2 в <xmmintrin.h > и <emmintrin.h>:

/* The Intel API is flexible enough that we must allow aliasing with other
   vector types, and their scalar components.  */

typedef float __m128 __attribute__ ((__vector_size__ (16), __may_alias__));    
typedef long long __m128i __attribute__ ((__vector_size__ (16), __may_alias__));
typedef double __m128d __attribute__ ((__vector_size__ (16), __may_alias__));

Ключевым здесь является атрибут __may_alias__, который делает работу по типу записи на этих типах, даже если строгий псевдоним включен с флагом -fstrict-aliasing.

Теперь, поскольку clang и ICC совместимы с GCC, они должны следовать тому же соглашению. Так что в настоящее время в этих 3 компиляторах неявные нагрузки/хранилища несколько гарантированно работают даже с флагом -fstrict-aliasing. Наконец, MSVC не поддерживает строгий псевдонимы вообще, поэтому он даже не может быть проблемой.

Тем не менее, это не означает, что вы должны предпочесть неявные нагрузки/хранилища по явным.