Оптимизация и почему openmp намного медленнее, чем последовательный способ?

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

вот мой код (здесь матрица N * N int, вектор N int, результат N long long):

#pragma omp parallel for private(i,j) shared(matrix,vector,result,m_size)
for(i=0;i<m_size;i++)
{  
  for(j=0;j<m_size;j++)
  {  
    result[i]+=matrix[i][j]*vector[j];
  }
}

и это код для последовательного пути:

for (i=0;i<m_size;i++)
        for(j=0;j<m_size;j++)
            result[i] += matrix[i][j] * vector[j];

когда я попробовал эти две реализации с матрицей 999x999 и вектором 999, время выполнения:

последовательный: госпожа 5439 Параллель: 11120 МС

Я действительно не могу понять, почему OpenMP намного медленнее, чем последовательный algo (более чем в 2 раза медленнее! Кто-нибудь может решить мою проблему?

3 ответов


потому что, когда OpenMP распределяет работу между потоками, происходит много администрирования / синхронизации, чтобы обеспечить значения в вашем shared матрица и вектор не повреждены. Даже если они доступны только для чтения: люди видят это легко, ваш компилятор может и нет.

вещи, чтобы попробовать по педагогическим причинам:

0) Что произойдет, если matrix и vector не shared?

1) распараллелить внутреннюю "J-петлю" во-первых, сохранить внешняя серия" i-loop". Посмотрим, что получится.

2) не собирать сумму в result[i], но в переменной temp и назначьте его содержимое result[i] только после завершения внутреннего цикла, чтобы избежать повторного поиска индекса. Не забудьте init temp до 0 перед началом внутреннего цикла.


код частично страдает от так называемого ложного совместного использования, типичным для всех кэш-когерентных систем. Короче говоря, многие элементы result[] массив вписывается в ту же строку кэша. Когда нить i пишет result[i] в результате += оператор, строка кэша, содержащая эту часть result[] становится грязной. Затем протокол согласованности кэша аннулирует все копии этой строки кэша в других ядрах, и они должны обновить свою копию с верхнего уровня кэш или из основной памяти. As result массив long long, то одна строка кэша (64 байта на x86) содержит 8 элементов и кроме того result[i] в той же строке кэша есть 7 других элементов массива. Поэтому возможно, что два "соседних" потока будут постоянно бороться за владение линией кэша (при условии, что каждый поток работает на отдельном ядре).

чтобы смягчить ложное совместное использование в вашем случае, самое простое-убедиться, что каждый поток получает итерацию блок, размер которого делится на количество элементов в строке кэша. Например, вы можете применить schedule(static,something*8) здесь something должно быть достаточно большим, чтобы пространство итераций не было разбито на слишком много частей, но в то же время оно должно быть достаточно маленьким, чтобы каждый поток получал блок. Е. Г. для m_size равна 999 и 4 нити применяются schedule(static,256) предложение parallel for строительство.

еще одной частичной причиной замедления работы кода может быть то, что OpenMP включен, компилятор может неохотно применять некоторые оптимизации кода при назначении общих переменных. OpenMP предоставляет так называемую модель расслабленной памяти, в которой разрешено, что локальное представление памяти общей переменной в каждом потоке отличается и flush construct предоставляется для синхронизации представлений. Но компиляторы обычно видят общие переменные как неявно volatile если они не могут доказать, что другим потокам не нужен доступ desynchronised общих переменных. Вы случай один из тех, так как result[i] присваивается только значение result[i] никогда не используется другими потоками. В последовательном случае компилятор, скорее всего, создаст временную переменную для хранения результата из внутреннего цикла и назначит только result[i] Как только внутренний цикл закончился. В параллельном случае он может решить, что это создаст временный вид desynchronised из result[i] в других потоках и, следовательно, решить не применять оптимизация. Просто для записи, GCC 4.7.1 с -O3 -ftree-vectorize делает трюк временной переменной с включенным OpenMP и нет.


Я сделал это в связи с комментарием Христо. Я попытался использовать расписание (static, 256). Для меня это не помогает изменить размер chunck по умолчанию. Может, даже хуже. Я распечатал номер потока и его индекс С и без установки расписания, и ясно, что OpenMP уже выбирает индексы потоков, чтобы быть далеко друг от друга, чтобы ложный обмен не казался проблемой. Для меня этот код уже дает хороший импульс с OpenMP.

#include "stdio.h"
#include <omp.h>

void loop_parallel(const int *matrix, const int ld, const int*vector, long long* result, const int m_size) {
    #pragma omp parallel for schedule(static, 250)
    //#pragma omp parallel for
    for (int i=0;i<m_size;i++) {
        //printf("%d %d\n", omp_get_thread_num(), i);
        long long sum = 0;
        for(int j=0;j<m_size;j++) {
            sum += matrix[i*ld +j] * vector[j];
        }
        result[i] = sum;
    }
}

void loop(const int *matrix, const int ld, const int*vector, long long* result, const int m_size) {
    for (int i=0;i<m_size;i++) {
        long long sum = 0;
        for(int j=0;j<m_size;j++) {
            sum += matrix[i*ld +j] * vector[j];
        }
        result[i] = sum;
    }
}

int main() {
    const int m_size = 1000;
    int *matrix = new int[m_size*m_size];
    int *vector = new int[m_size];
    long long*result = new long long[m_size];
    double dtime;

    dtime = omp_get_wtime();
    loop(matrix, m_size, vector, result, m_size);
    dtime = omp_get_wtime() - dtime;
    printf("time %f\n", dtime);

    dtime = omp_get_wtime();
    loop_parallel(matrix, m_size, vector, result, m_size);
    dtime = omp_get_wtime() - dtime;
    printf("time %f\n", dtime);

}