Вы правы в том, что у вас есть два основных варианта: защитить обновление данных или использовать специфичные для потока копии. Тем не менее, вы можете сделать намного лучше для каждого варианта:
При работе с защищенными обновлениями вы должны защищать только то, что и когда это абсолютно необходимо. Вы можете использовать начальную атомную проверку для предотвращения критических областей в большинстве случаев, аналогично двойной проверке шаблона блокировки.
double *matrixVectorHadamard(CSR *A, double *T, double *tB, double *tReq) {
initialize_T(tReq);
#pragma omp parallel for schedule(static, BLOCK_SIZE)
for(int i=0;i<N;i++) {
int num_edges = A->row_ptr[i+1] - A->row_ptr[i];
if (num_edges) {
if(T[i] != INFINITY && tB[i] != INFINITY) {
for(int j=0;j<num_edges;j++) {
// !WARNING! You MUST declare index within the parallel region
// or explicitly declare it private to avoid data races!
int index = A->col_ind[A->row_ptr[i] + j];
double tmp = T[i] + A->val[A->row_ptr[i]+j];
double old;
#pragma omp atomic
old = tReq[index];
if (tmp < old) {
#pragma omp critical
{
tmp = min(tReq[index], tmp);
// Another atomic ensures that the earlier read
// outside of critical works correctly
#pragma omp atomic
tReq[index] = tmp;
}
}
}
}
}
}
return tReq;
}
Примечание. К сожалению, ни один OpenMP / C не поддерживает прямой атомарный минимум.
Альтернативой является сокращение, которое поддерживается даже самим стандартом. Таким образом, нет необходимости заново изобретать разделение работы и т. Д. Вы можете просто сделать следующее:
double *matrixVectorHadamard(CSR *A, double *T, double *tB, double *tReq) {
initialize_T(tReq);
#pragma omp parallel for schedule(static, BLOCK_SIZE) reduction(min:tReq)
for(int i=0;i<N;i++) {
int num_edges = A->row_ptr[i+1] - A->row_ptr[i];
if (num_edges) {
if(T[i] != INFINITY && tB[i] != INFINITY) {
for(int j=0;j<num_edges;j++) {
// !WARNING! You MUST declare index within the parallel region
// or explicitly declare it private to avoid data races!
int index = A->col_ind[A->row_ptr[i] + j];
tReq[index] = min(tReq[index], T[i]+A->val[A->row_ptr[i]+j]);
}
}
}
}
return tReq;
}
OpenMP волшебным образом создаст локальные для потока копии tReq
и объединит (уменьшит) их в конце.
Какая версия лучше для вас, зависит от размера целевого массива и скорости записи. Если вы часто пишете, сокращение будет полезным, потому что оно не замедляется из-за плохого кэширования critical
/ atomic
. Если у вас огромный целевой массив или не так много итераций обновления, первое решение становится более интересным из-за относительных накладных расходов на создание и уменьшение массива.