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


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

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

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

С

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

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

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

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

1 11

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 , но таким образом вы не можете контролировать, выровнена ли нагрузка или нет. В этом случае, так как на современных процессорах нет никакого снижения производительности при использовании несогласованных нагрузок при выравнивании данных, компиляторы, как правило, используют несогласованные нагрузки повсеместно.

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

Почему составители не стремятся обеспечить строгое сглаживание правила о видах ГСП заполнитель?

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

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

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

GCC typedefs для типов заполнителей 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.

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

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