В первом примере работа, выполняемая каждым потоком, очень мала, а накладные расходы из среды выполнения OpenMP сводят на нет и ускоряют параллельное выполнение. Вы можете попробовать объединить обе параллельные области вместе, чтобы уменьшить накладные расходы, но это не очень поможет:
#pragma omp parallel for schedule(static)
for(int i=0;i<nx;i++){
curr[i] = (char*)(curr+nx) + i*ny;
next[i] = (char*)(next+nx) + i*ny;
}
Во втором случае узким местом является вызов drand48()
, похороненный где-то в вызове до real_rand()
и суммирование. drand48
использует глобальное состояние, которое используется всеми потоками. В однопоточных приложениях состояние обычно сохраняется в кэше данных L1, и там drand48
действительно быстро. В вашем случае, когда один поток обновляет состояние, это изменение распространяется на другие ядра и делает их кэши недействительными. Следовательно, когда другие потоки вызывают drand48
, состояние должно быть снова извлечено из памяти (или общего кэша L3). Это приводит к огромным задержкам и делает dran48
намного медленнее, чем при использовании в однопоточной программе. То же самое относится к суммированию в sum
, которое также вычисляет неправильное значение из-за гонок данных.
Решение первой проблемы состоит в том, чтобы иметь отдельный PRNG на поток, например, использовать erand48()
и передать локальное значение потока для xsubi
. Вы также должны заполнить каждый PRNG другим значением, чтобы избежать корреляции псевдослучайных потоков. Решением гонки данных является использование сокращений OpenMP:
int sum = 0;
double probability = 0.2;
#pragma omp parallel for collapse(2) reduction(+:sum) schedule(static)
for(int i=1;i<nx-1;i++){
for(int j=1;j<ny-1;j++) {
curr[i][j] = (real_rand() < probability);
sum += curr[i][j];
}
}