Ошибка сегментации (дамп ядра) при использовании avx в массиве, выделенном с помощью new[]

Когда я запускаю этот код в 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;
}

Можете ли вы показать результат: std::cout << sizeof(INFO) << "\n" << info << "\n" << (((char*) info[0].temp) - ((char*) info)) << "\n"; (для успешной и неудачной версии)?

chtz 08.04.2019 09:38

Кодовые блоки: 288, 0x10261b0, 32 VS2015: 288, 0000014B0EDB4080, 32

Mohsen Ghahremani Manesh 08.04.2019 10:02

Адрес в CodeBlocks не выровнен по 32 байтам. Я не думаю, что С++ 11 гарантирует, что operator new соблюдает выравнивание. Вы можете перегрузить этот оператор или скомпилировать с более новым стандартом (не уверен, какой из них требуется для обеспечения выравнивания).

chtz 08.04.2019 10:41

Из любопытства (поскольку я совершенно не разбираюсь в выравнивании памяти), есть ли причина использовать массив, выделенный с помощью new вместо vector?

Michiel uit het Broek 08.04.2019 11:59

@Michiel Я согласен, в 99,9% случаев вы должны предпочесть std::vector ручному new/delete (т.е. если вы не знаете, почему в вашем случае лучше последнее). Тем не менее, до C++17 у вас были бы аналогичные проблемы с выравниванием.

chtz 08.04.2019 14:30

Кстати, я не уверен, что в вашем примере MSVC работает правильно по дизайну или случайно (например, он может просто консервативно выровнять все до 32 байтов вместо 16).

chtz 08.04.2019 14:32

@chtz: это, вероятно, работает с MSVC, потому что MSVC никогда не использует загрузку / сохранение, требующие выравнивания. Он всегда использует [v]movdqu, даже если есть гарантия выравнивания во время компиляции. Только для не-AVX при сворачивании загрузки в операнд источника памяти (например, paddb xmm0, [rdi]) вы получите требование даже 16-байтных загрузок. Это пропущенная оптимизация для очень старых процессоров (старше, чем Nehalem, например, Core 2), где movdqu дороже, чем movdqa, даже если адрес выравнивается во время выполнения. Но gcc/clang создают asm, требующий максимально возможного выравнивания.

Peter Cordes 08.04.2019 20:44
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
7
571
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Проблема в том, что до 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/…

Mohsen Ghahremani Manesh 09.04.2019 12:22

Я нашел два способа решить эту проблему

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.
Peter Cordes 09.04.2019 05:07

Во второй версии это, возможно, сработало, но это небезопасно! Если new INFO[_size] создает невыровненный указатель, назначение структуры может привести к ошибке из-за использования 32-byte-alignment-required vmovdqa для копирования членов __m256i. (Если он достаточно велик, чтобы компилятор решил просто вызвать для него memcpy, то это позволит избежать ошибок, но последующее использование info[i].temp[k] все равно может привести к ошибке, если у вас недовыровненный __m256i. Если только вы никогда не используете его только с _mm256_loadu_si256 / storeu)

Peter Cordes 09.04.2019 05:10

Другие вопросы по теме