(Примечание. Хотя этот вопрос касается "store", случай "load" имеет те же проблемы и является абсолютно симметричным.)
Внутренние функции SSE предоставляют функцию _mm_storeu_pd
со следующей подписью:
void _mm_storeu_pd (double *p, __m128d a);
Итак, если у меня есть вектор двух двухлокальных, и я хочу сохранить его в массив из двух двухлокальных, я могу просто использовать это внутреннее.
Однако мой вектор не является двумя двойниками; это два 64-битных целых числа, и я хочу сохранить их в массив из двух 64-битных целых чисел. То есть, я хочу функцию со следующей сигнатурой:
void _mm_storeu_epi64 (int64_t *p, __m128i a);
Но внутренности не обеспечивают такую функцию. Ближайшие они _mm_storeu_si128
:
void _mm_storeu_si128 (__m128i *p, __m128i a);
Проблема в том, что эта функция принимает указатель на __m128i
, а мой массив - массив int64_t
. Запись на объект с помощью указателя неправильного типа является нарушением строгой псевдонимы и, безусловно, является undefined. Я обеспокоен тем, что мой компилятор, теперь или в будущем, изменит порядок или иным образом оптимизирует работу магазина, тем самым нарушая мою программу странными способами.
Чтобы быть ясным, я хочу, чтобы это функция, которую я могу вызвать следующим образом:
__m128i v = _mm_set_epi64x(2,1);
int64_t ra[2];
_mm_storeu_epi64(&ra[0], v); // does not exist, so I want to implement it
Вот шесть попыток создания такой функции.
Попытка # 1
void _mm_storeu_epi64(int64_t *p, __m128i a) {
_mm_storeu_si128(reinterpret_cast<__m128i *>(p), a);
}
У меня, похоже, проблема с строгим псевдонимом, о которой я беспокоюсь.
Попытка # 2
void _mm_storeu_epi64(int64_t *p, __m128i a) {
_mm_storeu_si128(static_cast<__m128i *>(static_cast<void *>(p)), a);
}
Возможно, лучше вообще, но я не думаю, что это имеет значение в этом случае.
Попытка # 3
void _mm_storeu_epi64(int64_t *p, __m128i a) {
union TypePun {
int64_t a[2];
__m128i v;
};
TypePun *p_u = reinterpret_cast<TypePun *>(p);
p_u->v = a;
}
Это генерирует неправильный код моего компилятора (GCC 4.9.0), который испускает выровненную команду movaps
вместо нестандартного movups
. (Союз выровнен, поэтому трюки reinterpret_cast
GCC в предположении p_u
тоже выровнены.)
Попытка # 4
void _mm_storeu_epi64(int64_t *p, __m128i a) {
union TypePun {
int64_t a[2];
__m128i v;
};
TypePun *p_u = reinterpret_cast<TypePun *>(p);
_mm_storeu_si128(&p_u->v, a);
}
Кажется, что я испускаю код, который я хочу. технически undefined в С++, широко поддерживается, Но есть ли этот пример - где я передаю указатель на элемент союза, а не через сам союз, действительно ли можно использовать союз для типа-punning?
Попытка # 5
void _mm_storeu_epi64(int64_t *p, __m128i a) {
p[0] = _mm_extract_epi64(a, 0);
p[1] = _mm_extract_epi64(a, 1);
}
Это работает и отлично действует, но вместо двух выдает две инструкции.
Попытка # 6
void _mm_storeu_epi64(int64_t *p, __m128i a) {
std::memcpy(p, &a, sizeof(a));
}
Это работает и отлично действует... я думаю. Но он испускает откровенно ужасный код в моей системе. GCC разливает a
в выровненный стековый слот через выровненное хранилище, а затем вручную перемещает компонентные слова в пункт назначения. (На самом деле он разливает его дважды, один раз для каждого компонента. Очень странно.)
...
Есть ли способ написать эту функцию, которая будет (а) генерировать оптимальный код в типичном современном компиляторе и (б) иметь минимальный риск столкнуться с строгим псевдонимом?