Является ли функция _mm256_store_ps () атомарной?при использовании вместе с OpenMP - PullRequest
2 голосов
/ 02 мая 2019

Я пытаюсь создать простую программу, которая использует технологию Intel AVX и выполняет умножение и сложение векторов. Здесь я использую Open MP вместе с этим. Но происходит ошибка сегментации из-за вызова функции _mm256_store_ps ().

Я пробовал использовать атомарные функции OpenMP, такие как атомарные, критические и т. Д., Так что если эта функция атомарна по своей природе и несколько ядер пытаются выполняться одновременно, но она не работает.

#include<stdio.h>
#include<time.h>
#include<stdlib.h>
#include<immintrin.h>
#include<omp.h>
#define N 64

__m256 multiply_and_add_intel(__m256 a, __m256 b, __m256 c) {
  return _mm256_add_ps(_mm256_mul_ps(a, b),c);
}

void multiply_and_add_intel_total_omp(const float* a, const float* b, const float* c, float* d)
{
  __m256 a_intel, b_intel, c_intel, d_intel;
  #pragma omp parallel for private(a_intel,b_intel,c_intel,d_intel)
  for(long i=0; i<N; i=i+8) {
    a_intel = _mm256_loadu_ps(&a[i]);
    b_intel = _mm256_loadu_ps(&b[i]);
    c_intel = _mm256_loadu_ps(&c[i]);
    d_intel = multiply_and_add_intel(a_intel, b_intel, c_intel);
    _mm256_store_ps(&d[i],d_intel);
  }
}
int main()
{
    srand(time(NULL));
    float * a = (float *) malloc(sizeof(float) * N);
    float * b = (float *) malloc(sizeof(float) * N);
    float * c = (float *) malloc(sizeof(float) * N);
    float * d_intel_avx_omp = (float *)malloc(sizeof(float) * N);
    int i;
    for(i=0;i<N;i++)
    {
        a[i] = (float)(rand()%10);
        b[i] = (float)(rand()%10);
        c[i] = (float)(rand()%10);
    }
    double time_t = omp_get_wtime();
    multiply_and_add_intel_total_omp(a,b,c,d_intel_avx_omp);
    time_t = omp_get_wtime() - time_t;
    printf("\nTime taken to calculate with AVX2 and OMP : %0.5lf\n",time_t);
  }

  free(a);
  free(b);
  free(c);
  free(d_intel_avx_omp);
    return 0;
}

Я ожидаю, что получу d = a * b + c, но это показывает ошибку сегментации. Я попытался выполнить ту же задачу без OpenMP, и он работает без ошибок. Пожалуйста, дайте мне знать, если есть какие-либо проблемы с совместимостью или мне не хватает какой-либо детали.

  • gcc версия 7.3.0
  • Процессор Intel® Core ™ i3-3110M
  • ОС Ubuntu 18.04
  • Откройте MP 4.5, я выполнил команду $ echo |cpp -fopenmp -dM |grep -i open, и она показала #define _OPENMP 201511
  • Команда для компиляции, gcc first_int.c -mavx -fopenmp

** ОБНОВЛЕНИЕ **

Согласно обсуждениям и предложениям, новый код:

 float * a = (float *) aligned_alloc(N, sizeof(float) * N);
 float * b = (float *) aligned_alloc(N, sizeof(float) * N);
 float * c = (float *) aligned_alloc(N, sizeof(float) * N);
 float * d_intel_avx_omp = (float *)aligned_alloc(N, sizeof(float) * N);

Это работает без прекрасно.

Просто обратите внимание, я пытался сравнить общие расчеты, расчет avx и расчет avx + openmp. Это результат, который я получил,

  • Время, необходимое для расчета без AVX: 0,00037
  • Время, необходимое для расчета с AVX: 0,00024
  • Время, необходимое для расчета с AVX и OMP: 0,00019

N = 50000

Ответы [ 2 ]

3 голосов
/ 02 мая 2019

Документация для _mm256_store_ps гласит:

Хранить 256 бит (состоящих из 8 упакованных элементов с плавающей запятой одинарной (32-битной) точности изв память. mem_addr должен быть выровнен по 32-байтовой границе, иначе может быть сгенерировано исключение общей защиты .

Вместо него можно использовать _mm256_storeu_si256 вместо невыровненногохранилища.


Лучший вариант - выровнять все массивы по 32-байтовой границе (для 256-битных регистров avx) и использовать выровненную загрузку и запоминающие устройства для максимальной производительности, поскольку не выровненные нагрузки / запоминающие устройства пересекаютГраница строки кэша влечет за собой снижение производительности.

Используйте std::aligned_alloc (или C11 aligned_alloc, memalign, posix_memalign, независимо от того, что у вас есть) вместо malloc(size), например:

float* allocate_aligned(size_t n) {
    constexpr size_t alignment = alignof(__m256);
    return static_cast<float*>(aligned_alloc(alignment, sizeof(float) * n));
}
// ...
float* a = allocate_aligned(N);
float* b = allocate_aligned(N);
float* c = allocate_aligned(N);
float* d_intel_avx_omp = allocate_aligned(N);

В C ++ - 17 new можно выделить с выравниванием:

float* allocate_aligned(size_t n) {
    constexpr auto alignment = std::align_val_t{alignof(__m256)};
    return new(alignment) float[n];
}

В качестве альтернативы используйте Vc: переносимые типы C ++ с нулевыми издержками для явного параллельного программирования данных который выравнивает выделенные кучей векторы SIMD для вас:

#include <cstdio>
#include <memory>
#include <chrono>
#include <Vc/Vc>

Vc::float_v random_float_v() {
    alignas(Vc::VectorAlignment) float t[Vc::float_v::Size];
    for(unsigned i = 0; i < Vc::float_v::Size; ++i)
        t[i] = std::rand() % 10;
    return Vc::float_v(t, Vc::Aligned);
}

unsigned reverse_crc32(void const* vbegin, void const* vend) {
    unsigned const* begin = reinterpret_cast<unsigned const*>(vbegin);
    unsigned const* end = reinterpret_cast<unsigned const*>(vend);
    unsigned r = 0;
    while(begin != end)
        r = __builtin_ia32_crc32si(r, *--end);
    return r;
}

int main() {
    constexpr size_t N = 65536;
    constexpr size_t M = N / Vc::float_v::Size;

    std::unique_ptr<Vc::float_v[]> a(new Vc::float_v[M]);
    std::unique_ptr<Vc::float_v[]> b(new Vc::float_v[M]);
    std::unique_ptr<Vc::float_v[]> c(new Vc::float_v[M]);
    std::unique_ptr<Vc::float_v[]> d_intel_avx_omp(new Vc::float_v[M]);

    for(unsigned i = 0; i < M; ++i) {
        a[i] = random_float_v();
        b[i] = random_float_v();
        c[i] = random_float_v();
    }

    auto t0 = std::chrono::high_resolution_clock::now();
    for(unsigned i = 0; i < M; ++i)
        d_intel_avx_omp[i] = a[i] * b[i] + c[i];
    auto t1 = std::chrono::high_resolution_clock::now();

    double seconds = std::chrono::duration_cast<std::chrono::duration<double>>(t1 - t0).count();
    unsigned crc = reverse_crc32(d_intel_avx_omp.get(), d_intel_avx_omp.get() + M); // Make sure d_intel_avx_omp isn't optimized out.
    std::printf("crc: %u, time: %.09f seconds\n", crc, seconds);
}

Параллельная версия:

#include <tbb/parallel_for.h>
// ...
    auto t0 = std::chrono::high_resolution_clock::now();
    tbb::parallel_for(size_t{0}, M, [&](unsigned i) {
        d_intel_avx_omp[i] = a[i] * b[i] + c[i];
    });
    auto t1 = std::chrono::high_resolution_clock::now();
2 голосов
/ 02 мая 2019

Вы должны использовать выровненную память для этих встроенных функций. Измените malloc(...) на aligned_alloc(sizeof(float) * 8, ...) (C11).

Это совершенно не связано с атомистикой. Вы работаете с совершенно отдельными частями данных (даже с разными строками кэша), поэтому никакой защиты не требуется.

...