Когда я запускаю этот код в Visual Studio 2015, код работает правильно. Но код генерирует следующую ошибку в кодовых блоках: Ошибка сегментации (сброс ядра). Я также запускал код в Ubuntu с той же ошибкой.
#include <iostream>
#include <immintrin.h>
struct INFO
{
unsigned int id = 0;
__m256i temp[8];
};
int main()
{
std::cout<<"Start AVX..."<<std::endl;
int _size = 100;
INFO *info = new INFO[_size];
for (int i = 0; i<_size; i++)
{
for (int k = 0; k < 8; k++)
{
info[i].temp[k] = _mm256_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
}
}
std::cout<<"End AVX."<<std::endl;
return 0;
}
Кодовые блоки: 288, 0x10261b0, 32 VS2015: 288, 0000014B0EDB4080, 32
Адрес в CodeBlocks не выровнен по 32 байтам. Я не думаю, что С++ 11 гарантирует, что operator new
соблюдает выравнивание. Вы можете перегрузить этот оператор или скомпилировать с более новым стандартом (не уверен, какой из них требуется для обеспечения выравнивания).
Из любопытства (поскольку я совершенно не разбираюсь в выравнивании памяти), есть ли причина использовать массив, выделенный с помощью new
вместо vector
?
@Michiel Я согласен, в 99,9% случаев вы должны предпочесть std::vector
ручному new
/delete
(т.е. если вы не знаете, почему в вашем случае лучше последнее). Тем не менее, до C++17 у вас были бы аналогичные проблемы с выравниванием.
Кстати, я не уверен, что в вашем примере MSVC работает правильно по дизайну или случайно (например, он может просто консервативно выровнять все до 32 байтов вместо 16).
@chtz: это, вероятно, работает с MSVC, потому что MSVC никогда не использует загрузку / сохранение, требующие выравнивания. Он всегда использует [v]movdqu
, даже если есть гарантия выравнивания во время компиляции. Только для не-AVX при сворачивании загрузки в операнд источника памяти (например, paddb xmm0, [rdi]
) вы получите требование даже 16-байтных загрузок. Это пропущенная оптимизация для очень старых процессоров (старше, чем Nehalem, например, Core 2), где movdqu
дороже, чем movdqa
, даже если адрес выравнивается во время выполнения. Но gcc/clang создают asm, требующий максимально возможного выравнивания.
Проблема в том, что до C++17 new
и delete
не учитывали выравнивание типа, подлежащего выделению. Если вы посмотрите на сгенерированную сборку из этой простой функции:
INFO* new_test() {
int _size = 100;
INFO *info = new INFO[_size];
return info;
}
Вы увидите, что при компиляции с чем-либо до C++17 вызывается operator new[](unsigned long)
, тогда как для C++17 вызывается operator new[](unsigned long, std::align_val_t)
(и 32
передается для второго параметра).
Поиграйте с ним в Godbolt.
Если вы не можете использовать С++ 17, вы можете перезаписать operator new[]
(и operator delete[]
-- и вы также должны перезаписать operator new
и operator delete
...):
struct INFO {
unsigned int id = 0;
__m256i temp[8];
void* operator new[](size_t size) {
// part of C11:
return aligned_alloc(alignof(INFO), size);
}
void operator delete[](void* addr) {
free(addr); // aligned_alloc is compatible with free
}
};
Это часть предыдущего примера Godbolt, если вы скомпилируете его с помощью -DOVERWRITE_OPERATOR_NEW
.
Обратите внимание, что это не решает проблему выравнивания при использовании std::vector
(или любого другого std
-контейнера), для этого вам нужно передать выровненный распределитель в контейнер (не является частью предыдущего примера).
В Windowsalign_alloc и free были заменены на _aligned_malloc и _aligned_free, которые можно найти в malloc.h. для получения дополнительной информации взгляните на stackoverflow.com/questions/32133203/…
Я нашел два способа решить эту проблему
The first solution How to solve the 32-byte-alignment issue for AVX load/store operations?
struct INFO
{
__m256i temp[8];
unsigned int id = 0;
};
INFO *info = static_cast<INFO*>(_mm_malloc(sizeof(INFO)*_size, 32));
_mm_free(info);
The second solution
INFO *info = new INFO[_size];
for (int i = 0; i < _size; i++)
{
INFO new_info;
for (int k = 0; k < 8; k++)
{
new_info.temp[k] = _mm256_setr_epi8(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
}
info[i] = new_info;
}
_mm_malloc
– это нет, заменяющий new
. Он не совместим с delete
(или free
), только с _mm_free
.
Во второй версии это, возможно, сработало, но это небезопасно! Если new INFO[_size]
создает невыровненный указатель, назначение структуры может привести к ошибке из-за использования 32-byte-alignment-required vmovdqa
для копирования членов __m256i
. (Если он достаточно велик, чтобы компилятор решил просто вызвать для него memcpy, то это позволит избежать ошибок, но последующее использование info[i].temp[k]
все равно может привести к ошибке, если у вас недовыровненный __m256i
. Если только вы никогда не используете его только с _mm256_loadu_si256
/ storeu)
Можете ли вы показать результат:
std::cout << sizeof(INFO) << "\n" << info << "\n" << (((char*) info[0].temp) - ((char*) info)) << "\n";
(для успешной и неудачной версии)?