Более медленный код с OpenMP, как его можно распараллелить?

Этот код работает медленнее с OpenMP. Без OpenMP я получаю около 10 секунд. С OpenMP я получаю около 40 секунд. Что случилось? Большое спасибо друзья!

for (i=2;i<(nnoib-2);++i){
    #pragma omp parallel for
    for (j=2; j<(nnojb-2); ++j) {
        C[i][j]= absi[i]*absj[j]*
                 (2.0f*B[i][j] + absi[i]*absj[j]*
                 (VEL[i][j]*VEL[i][j]*fat*
                 (16.0f*(B[i][j-1]+B[i][j+1]+B[i-1][j]+B[i+1][j])
                 -1.0f*(B[i][j-2]+B[i][j+2]+B[i-2][j]+B[i+2][j]) 
                 -60.0f*B[i][j]
                 )-A[i][j]));
        c2 = (abs(C[i][j]) > Amax[i][j]);
        if (c2) {
            Amax[i][j] = abs(C[i][j]);
            Ttra[i][j] = t;
        }
    }
}

person Lucas Torquato    schedule 10.03.2013    source источник
comment
Где объявлено c2?   -  person Mysticial    schedule 10.03.2013
comment
c2 — глобальная переменная. с2 внутр. Спасибо!   -  person Lucas Torquato    schedule 10.03.2013
comment
Затем сделайте его локальным, так как будет состояние гонки/зависимость от c2.   -  person Mysticial    schedule 10.03.2013
comment
Ok! Я буду! Может быть, если (abs(C[i][j]) › Amax[i][j]) ?   -  person Lucas Torquato    schedule 10.03.2013


Ответы (2)


Тот факт, что вы используете OpenMP, не означает, что ваша программа будет работать быстрее. Здесь может произойти несколько вещей:

  1. Существует стоимость, связанная с созданием каждого потока, и если вы создаете поток для выполнения небольшого объема вычислений, создание самого потока займет больше времени, чем вычисление.

  2. По умолчанию OpenMP создаст максимальное количество потоков, поддерживаемое вашим процессором. С процессорами, которые поддерживают 2 или более потоков на ядро, потоки будут конкурировать за ресурсы каждого ядра. Используя omp_get_num_threads(), вы можете увидеть, сколько потоков будет создано по умолчанию. Я рекомендую попробовать запустить ваш код с половиной этого значения, используя omp_set_num_threads().

Вы подтвердили, что результаты были одинаковыми с OpenMP и без него? Кажется, есть зависимость с переменными j и c2. Вы должны объявить их приватными для каждого потока:

#pragma omp parallel for private(j,c2)

Я хотел добавить еще одну вещь: прежде чем пытаться распараллелить, вы должны убедиться, что код уже оптимизирован.

В зависимости от вашего компилятора, флагов компилятора и сложности инструкции компилятор может оптимизировать ваш код, а может и нет:

// avoid calculation nnoib-2 every iteration
int t_nnoib = nnoib - 2;
for (i=2; i< t_nnoib; ++i){
    // avoid calculation nnojb-2 every iteration
    int t_nnojb = nnojb - 2;
    // avoid loading absi[i] every iteration
    int t_absi = absi[i];
    for (j=2; j< t_nnojb; ++j) {
        C[i][j]= t_absi * absj[j] *
             (2.0f*B[i][j] + t_absi * absj[j] *
             (VEL[i][j] * VEL[i][j] * fat *
             (16.0f * (B[i][j-1] + B[i][j+1] + B[i-1][j] + B[i+1][j])
              -1.0f * (B[i][j-2] + B[i][j+2] + B[i-2][j] + B[i+2][j]) 
              -60.0f * B[i][j]
             ) - A[i][j]));

        // c2 is a useless variable
        if (abs(C[i][j]) > Amax[i][j]) {
            Amax[i][j] = abs(C[i][j]);
            Ttra[i][j] = t;
         }
    }
}

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

В случае с массивом absi вы избежите того, чтобы система хранила часть этого массива в кеше во время выполнения цикла j. Общая идея этого метода состоит в том, чтобы перемещать во внешний цикл любой доступ к массиву, который не зависит от переменной внутреннего цикла.

person Cristiano Sousa    schedule 10.03.2013
comment
Ok! я буду модификации. i равно 1000, j тоже равно 1000. Большое спасибо друг! - person Lucas Torquato; 10.03.2013

В дополнение к затратам, упомянутым Криштиану, ваш выбор распараллелить по циклу j, а не по циклу i, создает риск ложного разделения в трех назначенных массивах, C, Amax, Ttra. По сути, когда один поток выполняет запись в элемент одного из этих массивов, смежные элементы в одной и той же строке кэша также будут загружены в кэш этого ядра. Когда другое ядро ​​затем начинает записывать свои собственные значения в разные записи, ему придется вытягивать строку из другого кеша, при этом несколько ядер потенциально могут играть в «перетягивание каната».

Решение этой проблемы состоит в том, чтобы распараллелить внешний цикл над i вместо внутреннего цикла над j. Удобно, что это также резко снижает затраты, упомянутые в ответе Криштиану, поскольку порождения и рабочие задания будут происходить только один раз, а не для каждой итерации цикла i. Вам все равно нужно будет приватизировать j и c2 или просто вставить значение c2 в последующее if и удалить переменную (как описано в вашем комментарии). Для большей эффективности использование локально объявленной переменной вместо j означало бы отсутствие доступа к частной переменной потока.

Просто в качестве (довольно важной) проверки, это гнездо цикла на самом деле является частью вашей программы, которая, как вы измерили, занимает большую часть времени? Добавление прагмы OpenMP изменило время с чуть менее 10 до чуть менее 40 с?

person Phil Miller    schedule 10.03.2013
comment
Обратите внимание, что в отношении пункта 2 этого ответа реализации OpenMP обычно не создают и не уничтожают потоки динамически во время выполнения, вместо этого они создают потоки при запуске и уничтожают их при завершении. Это соответствует общему использованию, для которого изначально предназначался OpenMP, это не универсальная система многопоточности. - person High Performance Mark; 10.03.2013
comment
Хорошие реализации делают это, когда они могут заставить семантику соответствовать тому, что запросил программист. Даже в этом случае все равно возникают накладные расходы (возможно, существенные, в зависимости от стратегии планирования) при передаче работы от главного потока рабочим в каждой итерации. - person Phil Miller; 11.03.2013