сокращение с OpenMP с SSE/AVX

Я хочу сделать сокращение массива с помощью OpenMP и SIMD. Я читал, что сокращение в OpenMP эквивалентно:

inline float sum_scalar_openmp2(const float a[], const size_t N) {
    float sum = 0.0f;
    #pragma omp parallel
    {
        float sum_private = 0.0f;
        #pragma omp parallel for nowait
        for(int i=0; i<N; i++) {
            sum_private += a[i];
        }
        #pragma omp atomic
        sum += sum_private;
    }
    return sum;
}

Я получил эту идею по следующей ссылке: http://bisqwit.iki.fi/story/howto/openmp/#ReductionClause Но atomic также не поддерживает сложные операторы. Что я сделал, так это заменил атомарный на критический и реализовал сокращение с OpenMP и SSE следующим образом:

#define ROUND_DOWN(x, s) ((x) & ~((s)-1))
inline float sum_vector4_openmp(const float a[], const size_t N) {
    __m128 sum4 = _mm_set1_ps(0.0f);
    #pragma omp parallel 
    {
        __m128 sum4_private = _mm_set1_ps(0.0f);
        #pragma omp for nowait
        for(int i=0; i < ROUND_DOWN(N, 4); i+=4) {
            __m128 a4 = _mm_load_ps(a + i);
            sum4_private = _mm_add_ps(a4, sum4_private);
        }
        #pragma omp critical
        sum4 = _mm_add_ps(sum4_private, sum4);
    }
    __m128 t1 = _mm_hadd_ps(sum4,sum4);
    __m128 t2 = _mm_hadd_ps(t1,t1);
    float sum = _mm_cvtss_f32(t2);  
    for(int i = ROUND_DOWN(N, 4); i < N; i++) {
        sum += a[i];
    }
    return sum;
} 

Однако эта функция работает не так хорошо, как я надеюсь. Я использую Visual Studio 2012 Express. Я знаю, что могу немного улучшить производительность, развернув загрузку/добавление SSE несколько раз, но это все же меньше, чем я ожидаю.

Я получаю гораздо лучшую производительность, запуская срезы массивов, равные количеству потоков:

inline float sum_slice(const float a[], const size_t N) {
    int nthreads = 4;
    const int offset = ROUND_DOWN(N/nthreads, nthreads);
    float suma[8] = {0};
    #pragma omp parallel for num_threads(nthreads) 
    for(int i=0; i<nthreads; i++) {
        suma[i] = sum_vector4(&a[i*offset], offset);
    }
    float sum = 0.0f;
    for(int i=0; i<nthreads; i++) {
        sum += suma[i]; 
    }
    for(int i=nthreads*offset; i < N; i++) {
        sum += a[i];
    }
    return sum;    
}

inline float sum_vector4(const float a[], const size_t N) {
    __m128 sum4 = _mm_set1_ps(0.0f);
    int i = 0;
    for(; i < ROUND_DOWN(N, 4); i+=4) {
        __m128 a4 = _mm_load_ps(a + i);
        sum4 = _mm_add_ps(sum4, a4);
    }
    __m128 t1 = _mm_hadd_ps(sum4,sum4);
    __m128 t2 = _mm_hadd_ps(t1,t1);
    float sum = _mm_cvtss_f32(t2);
    for(; i < N; i++) {
        sum += a[i];
    }
    return sum;

}

Кто-нибудь знает, есть ли лучший способ делать сокращения с более сложными операторами в OpenMP?


person Community    schedule 15.03.2013    source источник
comment
Каков размер N, который вы используете для проведения теста?   -  person veda    schedule 16.03.2013
comment
Я делаю тесты на размерах кеша: N(L1) = 32k, N(L2) = 256K, N(L3/4) = 2M и N››N(L3).   -  person    schedule 16.03.2013


Ответы (1)


Я предполагаю, что ответ на ваш вопрос - нет. Я не думаю, что есть лучший способ сделать сокращение с более сложными операторами в OpenMP.

Предполагая, что массив выровнен по 16 битам, количество потоков openmp равно 4, можно ожидать, что прирост производительности будет в 12-16 раз за счет OpenMP + SIMD. В реальности это может не дать достаточного прироста производительности, потому что

  1. При создании потоков openmp возникают накладные расходы.
  2. Код выполняет 1 операцию сложения для 1 операции загрузки. Следовательно, ЦП не выполняет достаточно вычислений. Таким образом, похоже, что ЦП тратит большую часть времени на загрузку данных, что связано с пропускной способностью памяти.
person veda    schedule 16.03.2013
comment
В коде SSE существует переносимая циклическая зависимость. Его можно развернуть 2 или более раз, чтобы ускорить процесс. На самом деле, я уже это делаю, но не добавляю код, потому что он делает текст длиннее. - person ; 16.03.2013
comment
Массивы выровнены по 32 битам, потому что я также тестирую AVX. - person ; 16.03.2013