Разве __m128d изначально не выровнен? - PullRequest
0 голосов
/ 14 декабря 2018

У меня есть этот код:

double a[bufferSize];
double b[voiceSize][bufferSize];
double c[voiceSize][bufferSize];

...

inline void AddIntrinsics(int voiceIndex, int blockSize) {
    // assuming blockSize / 2 == 0 and voiceIndex is within the range
    int iters = blockSize / 2;
    __m128d *pA = (__m128d*)a;
    __m128d *pB = (__m128d*)b[voiceIndex];
    double *pC = c[voiceIndex];

    for (int i = 0; i < iters; i++, pA++, pB++, pC += 2) {
        _mm_store_pd(pC, _mm_add_pd(*pA, *pB));
    }   
}

Но "иногда" это вызывает Нарушение доступа к памяти , что, я думаю, связано с отсутствием выравнивания памяти в моих 3 массивах a, b и c.

Но так как я оперирую с __m128d (в котором используется __declspec(align(16))), не гарантируется ли выравнивание при приведении к этим указателям?

Или так как в нем будет использоваться __m128d"регистр", он может mov напрямую регистрироваться из невыровненной памяти (следовательно, исключение)?

Если так, как бы вы выровняли массивы в C ++ для такого рода вещей? std :: align ?

Я нахожусь на Win x64, MSVC, Компиляция в режиме Release 32 и 64 бит.

1 Ответ

0 голосов
/ 14 декабря 2018

__m128d - это тип, который предполагает / требует / гарантирует (компилятору) 16-байтовое выравнивание 1 .

Приведение неверно выровненного указателя к __m128d* и разыменование егонеопределенное поведение, и это ожидаемый результат. Используйте _mm_loadu_pd, если ваши данные могут быть не выровнены. (Или, предпочтительно, выровняйте ваши данные с alignas(16) double a[bufferSize]; 2 ).ISO C ++ 11 и более поздние версии имеют переносимый синтаксис для выравнивания статического и автоматического хранения (но не так просто для динамического хранения).

Приведение указателя к __m128d* и разыменование его похоже на обещание компилятору, что он выровнен. C ++ позволяет вам лгать компилятору с потенциально катастрофическими результатами. Выполнение операции, требующей выравнивания, не приводит к обратному выравниванию ваших данных;это не имеет смысла или даже невозможно, когда вы компилируете несколько файлов по отдельности или когда вы работаете с помощью указателей.


Сноска 1. Интересный факт: реализация встроенного в Intel API встроенных функций Intel добавляет тип __m128d_u: невыровненные векторы, которые подразумевают 1-байтовое выравнивание, если вы разыменовываете указатель.

typedef double __m128d_u 
       __attribute__ ((__vector_size__ (16), __may_alias__, __aligned__ (1)));

Не использовать в переносимом коде;Я не думаю, что MSVC поддерживает это, а Intel не определяет это.

Сноска 2: В вашем случае вам также необходимо выровнять каждую строку ваших 2D-массивов на 16Поэтому вам нужно, чтобы размерность массива была [voiceSize][round_up_to_next_power_of_2(bufferSize)], если bufferSize может быть нечетным.Оставление неиспользованных дополнительных элементов в конце каждой строки является обычной техникой, например, в графическом программировании для двумерных изображений с потенциально нечетной шириной.


Кстати, это не "особый""или специфично для встроенных функций: приведение void* или char* к int* (и разыменование его) безопасно только в том случае, если оно достаточно выровнено. В x86-64 System V и Windows x64, alignof(int) = 4.

(Интересный факт: даже создание неправильно выровненного указателя - это неопределенное поведение в ISO C ++. Но компиляторы, которые поддерживают встроенный API-интерфейс Intel, должны поддерживать такие вещи, как _mm_loadu_si128( (__m128i*)char_ptr ), поэтому мы можем рассмотреть создание без разыменования невыровненных указателей как частьрасширение.)

Обычно это работает на x86, потому что только 16-байтовые загрузки имеют версию, требующую выравнивания.Но на SPARC, например, у вас потенциально может быть та же проблема.Тем не менее, можно столкнуться с проблемами со смещенными указателями на int или short даже на x86. Почему при выравнивании доступа к памяти mmap иногда происходит ошибка segfault на AMD64? является хорошим примером: автоматическая векторизация с помощью gcc предполагает, что некоторое целое число элементов uint16_t достигнет 16-байтовой границы выравнивания.

Также проще столкнуться с проблемами встроенных функций, поскольку alignof(__m128d) больше, чем выравнивание большинства примитивных типов.В 32-битных реализациях x86 C ++ alignof(maxalign_t) - только 8, поэтому malloc и new обычно возвращают только 8-байтовую выровненную память.

...