2015-11-24 2 views
9

У меня есть код, который работает с значениями __m128. Я использую x86-64 SSE intrinsics для этих значений, и я нахожу, что если значения не выравниваются в памяти, я получаю сбой. Это связано с моим компилятором (clang в этом случае), генерирующим только согласованные инструкции загрузки.Как дать команду компилятору генерировать неуравновешенные нагрузки для __m128

Могу ли я дать команду моему компилятору генерировать неравномерные нагрузки вместо этого, глобально или для определенных значений (возможно, с какой-либо аннотацией)?


Причина, по которой у меня есть несогласованные значения, заключается в том, что я пытаюсь сохранить память. У меня есть struct примерно следующим образом:

#pragma pack(push, 4) 
struct Foobar { 
    __m128 a; 
    __m128 b; 
    int c; 
}; 
#pragma pack(pop) 

Я затем создать массив этих структур. 2-й элемент в массиве начинается с 36 байт, который не кратен 16.

Я знаю, что я могу переключиться на структуру представления массивов или удалить прагму упаковки (за счет увеличения размера struct от 36 до 48 байт); но я также знаю, что неуправляемые нагрузки в наши дни не так дороги и хотели бы попробовать это в первую очередь.


Update, чтобы ответить на некоторые из приведенных ниже комментариев:

Мой реальный код был ближе к этому:

struct Vector4 { 
    __m128 data; 
    Vector4(__m128 v) : data(v) {} 
}; 
struct Foobar { 
    Vector4 a; 
    Vector4 b; 
    int c; 
} 

Я тогда некоторые вспомогательные функции, такие как:

inline Vector4 add(const Vector4& a, const Vector4 &b) { 
    return Vector4(_mm_add_ps(a.data, b.data)); 
} 

inline Vector4 subtract(const Vector4& a, const Vector4& b) { 
    return Vector4(_mm_sub_ps(a.data, b.data)); 
} 

// etc.. 

Я использую эти утилиты часто в сочетании. Поддельный пример:

Foobar myArray[1000]; 
myArray[i+1].b = sub(add(myArray[i].a, myArray[i].b), myArray[i+1].a); 

При взгляде на «Z Бозон» 'ы ответ на мой код эффективно изменен на:

struct Vector4 { 
    float data[4]; 
}; 

inline Vector4 add(const Vector4& a, const Vector4 &b) { 
    Vector4 result; 
    _mm_storeu_ps(result.data, _mm_add_ps(_mm_loadu_ps(a.data), _mm_loadu_ps(b.data))); 
    return result; 
} 

Моя проблема заключалась в том, когда функции полезности используются в комбинации, как указано выше, что сгенерированный код может иметь избыточные инструкции по загрузке/хранению. Оказывается, это не проблема. Я тестировал свой компилятор (clang), и он удалил их все. Я согласен с ответом Z Bozon.

+4

Не используйте '__m128' в своей структуре. Используйте, например, 'float a [4]' и явно загружайте и сохраняйте файлы с '_mm_loadu_ps' и' _mm_storeu_ps'. –

+0

Похоже, что OP не только использует явные интрижки, но и получает код SIMD, генерируемый clang из-за автоматической векторизации в некоторых случаях? –

+0

@PaulR, если это так, то OP должен добавить эту информацию к своему вопросу. –

ответ

3

На мой взгляд, вы должны написать свои структуры данных, используя стандартные конструкции C++ (из которых __m128i нет). Если вы хотите использовать встроенные функции, которые не являются стандартными C++, вы «вводите мир SSE» через встроенные функции, такие как _mm_loadu_ps, и вы «оставите мир SSE» обратно на стандартный C++ с внутренним значением, например _mm_storeu_ps. Не полагайтесь на неявные нагрузки и хранилища SSE. Я видел слишком много ошибок на SO, делая это.

В этом случае вы должны использовать

struct Foobar { 
    float a[4]; 
    float b[4]; 
    int c; 
}; 

то вы можете сделать

Foobar foo[16]; 

В этом случае foo[1] не будет выровненной 16 байт, но если вы хотите использовать SSE и оставить стандартный C++ do

__m128 a4 = _mm_loadu_ps(foo[1].a); 
__m128 b4 = _mm_loadu_ps(foo[1].b); 
__m128 max = _mm_max_ps(a4,b4); 
_mm_storeu_ps(array, max); 

затем вернитесь к стандартному C++.

Еще одна вещь, вы можете рассмотреть этот

struct Foobar { 
    float a[16]; 
    float b[16]; 
    int c[4]; 
}; 

затем, чтобы получить массив из 16 исходной структуры делают

Foobar foo[4]; 

В этом случае, если первый элемент выравнивается так все другие элементы.


Если вы хотите вспомогательные функции, которые действуют на SSE регистров не используйте явную или неявную нагрузку/магазины в функции полезности. Перечислите ссылки const на __m128 и верните __m128, если вам нужно.

//SSE utility function 
static inline __m128 mulk_SSE(__m128 const &a, float k) 
{ 
    return _mm_mul_ps(_mm_set1_ps(k),a); 
} 

//main function 
void foo(float *x, float *y n) 
{ 
    for(int i=0; i<n; i+=4) 
     __m128 t1 = _mm_loadu_ps(x[i]); 
     __m128 t2 = mulk_SSE(x4,3.14159f); 
     _mm_store_ps(&y[i], t2); 
    } 
} 

Причина использовать константную ссылку, что MSVC не может пройти __m128 по значению. Без ссылки на const вы получите сообщение об ошибке

ошибка C2719: формальный параметр с __declspec (выровнять ('16 ')) не будет выровнен.

__m128 для MSVC действительно является союзником в любом случае.

typedef union __declspec(intrin_type) _CRT_ALIGN(16) __m128 { 
    float    m128_f32[4]; 
    unsigned __int64 m128_u64[2]; 
    __int8    m128_i8[16]; 
    __int16    m128_i16[8]; 
    __int32    m128_i32[4]; 
    __int64    m128_i64[2]; 
    unsigned __int8  m128_u8[16]; 
    unsigned __int16 m128_u16[8]; 
    unsigned __int32 m128_u32[4]; 
} __m128; 

Предполагается, что MSVC не должен загружать соединение, когда функции утилиты SSE встроены.


На основе OPS последнего обновления кода здесь является то, что я хотел бы предложить

#include <x86intrin.h> 
struct Vector4 { 
    __m128 data; 
    Vector4() { 
    } 
    Vector4(__m128 const &v) { 
     data = v; 
    } 
    Vector4 & load(float const *x) { 
     data = _mm_loadu_ps(x); 
     return *this; 
    } 
    void store(float *x) const { 
     _mm_storeu_ps(x, data); 
    } 
    operator __m128() const { 
     return data; 
    } 
}; 

static inline Vector4 operator + (Vector4 const & a, Vector4 const & b) { 
    return _mm_add_ps(a, b); 
} 

static inline Vector4 operator - (Vector4 const & a, Vector4 const & b) { 
    return _mm_sub_ps(a, b); 
} 

struct Foobar { 
    float a[4]; 
    float b[4]; 
    int c; 
}; 

int main(void) 
{ 
    Foobar myArray[10]; 
    // note that myArray[0].a, myArray[0].b, and myArray[1].b should be  // initialized before doing the following 
    Vector4 a0 = Vector4().load(myArray[0].a); 
    Vector4 b0 = Vector4().load(myArray[0].b); 
    Vector4 a1 = Vector4().load(myArray[1].a);   
    (a0 + b0 - a1).store(myArray[1].b); 
} 

Этот код был основан на идеях Agner Туман-х Vector Class Library.

+0

Спасибо за хорошо написанный ответ. Это может быть повторением вопроса, который я поставил в комментарии выше, но я хочу уточнить один момент. Если я использую предложенный вами подход и имею несколько функций полезности, каждый из которых «вводит» и «уходит» в мир SSE таким образом.Когда я свяжу эти функции (и давайте предположим, что они встанут в очередь), будет ли компилятор вообще достаточно умен, чтобы удалить избыточные операции storeu/loadu? Или, может быть, мне придется писать «предустановленные» функции полезности, которые выполняют несколько действий подряд, одновременно входя в мир SSE только один раз? – pauldoo

+0

@pauldoo, можете ли вы добавить пример того, что вы имеете в виду на свой вопрос? –

+0

@pauldoo, как только вы заполнили регистр SSE данными, вы можете передать его функции, например. '__m128 foo (__ m128 const & a)'. Используйте ссылку const, чтобы поддерживать MSVC. –

0

Вы можете попробовать изменить свой-структуру, чтобы:

#pragma pack(push, 4) 
struct Foobar { 
    int c; 
    __m128 a; 
    __m128 b; 
}; 
#pragma pack(pop) 

Это будет иметь тот же размер, конечно, и должен в теории силы звоном для создания нелинейных нагрузках/магазинов.


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

v = _mm_max_ps(myArray[300].a, myArray[301].a) 

к:

__m128i v1 = _mm_loadu_ps((float *)&myArray[300].a); 
__m128i v2 = _mm_loadu_ps((float *)&myArray[301].a); 
v = _mm_max_ps(v1, v2); 
+0

Для вашего альтернативного предложения OP также может использовать 'float a [4]'. –

+0

True - я просто добавлял иллюстрацию для текущего определения структуры, но было бы разумнее переключиться на массив поплавков, и это тоже избаловало бы от уродливых бросков. –

0

При использовании автоматической векторизации или явного OpenMP4/Cilk/псевдокомментарии приводом векторизации, то можно принудительно компилятору использовать невыровненные нагрузки для векторизованного контура с помощью:

#pragma vector unaligned //for C/C++ 

CDEC$ vector unaligned ; for Fortran 

Это в первую очередь предназначен для управления компромиссами между «выровнено, но очищенным» против «не очищенные, но выровненных». Подробнее читайте в https://software.intel.com/en-us/articles/utilizing-full-vectors

Это работает только для компиляторов Intel, насколько мне известно. У компиляторов Intel также есть внутренний компилятор -mP2OPT_vec_alignment = 6, чтобы сделать то же самое для всей единицы компиляции.

Я не проверял, может ли это быть эффективно применено к реализациям, где встроенные/сборные используются вместе с OpenMP/Cilk.

 Смежные вопросы

  • Нет связанных вопросов^_^