OpenMP / C ++: paralelo para bucle con reducción posterior: ¿mejores prácticas?

Dado el siguiente código …

for (size_t i = 0; i < clusters.size(); ++i) { const std::set& cluster = clusters[i]; // ... expensive calculations ... for (int j : cluster) velocity[j] += f(j); } 

… que me gustaría ejecutar en múltiples CPU / núcleos. La función f no usa la velocity .

Un simple #pragma omp parallel for antes del primer bucle for producirá resultados impredecibles / erróneos, porque la std::vector velocity se modifica en el bucle interno. Varios hilos pueden acceder y (intentar) modificar el mismo elemento de velocity al mismo tiempo.

Creo que la primera solución sería escribir #pragma omp atomic antes de la velocity[j] += f(j); operación. Esto me da un error de comstackción (podría tener algo que ver con que los elementos sean de tipo Eigen::Vector3d o que la velocity sea ​​un miembro de la clase). Además, leo que las operaciones atómicas son muy lentas en comparación con tener una variable privada para cada hilo y hacer una reducción al final. Así que eso es lo que me gustaría hacer, creo.

Se me ha ocurrido esto:

 #pragma omp parallel { // these variables are local to each thread std::vector velocity_local(velocity.size()); std::fill(velocity_local.begin(), velocity_local.end(), Eigen::Vector3d(0,0,0)); #pragma omp for for (size_t i = 0; i < clusters.size(); ++i) { const std::set& cluster = clusters[i]; // ... expensive calculations ... for (int j : cluster) velocity_local[j] += f(j); // save results from the previous calculations } // now each thread can save its results to the global variable #pragma omp critical { for (size_t i = 0; i < velocity_local.size(); ++i) velocity[i] += velocity_local[i]; } } 

¿Es esta una buena solución? ¿Es la mejor solución? (¿Es incluso correcto ?)

Otros pensamientos: el uso de la cláusula de reduce (en lugar de la sección critical ) produce un error de comstackción. Creo que esto es porque la velocity es un miembro de la clase.

He intentado encontrar una pregunta con un problema similar, y esta pregunta parece que es casi la misma. Pero creo que mi caso puede diferir porque el último paso incluye un bucle for . También la pregunta de si este es el mejor enfoque aún se mantiene.

Editar: Como solicitud por comentario: La cláusula de reduction

  #pragma omp parallel reduction(+:velocity) for (omp_int i = 0; i < velocity_local.size(); ++i) velocity[i] += velocity_local[i]; 

… arroja el siguiente error:

error C3028: ‘ShapeMatching :: velocity’: solo se puede usar un miembro de datos estáticos o variables en una cláusula de intercambio de datos

(error similar con g++ )

Estás haciendo una reducción de matriz. He descrito esto varias veces (por ejemplo, reducir una matriz en openmp y completar la reducción de matrices de histogtwigs en paralelo con openmp sin usar una sección crítica ). Puedes hacer esto con y sin una sección crítica.

Ya lo ha hecho correctamente con una sección crítica (en su edición reciente), así que permítame describir cómo hacerlo sin una sección crítica.


 std::vector velocitya; #pragma omp parallel { const int nthreads = omp_get_num_threads(); const int ithread = omp_get_thread_num(); const int vsize = velocity.size(); #pragma omp single velocitya.resize(vsize*nthreads); std::fill(velocitya.begin()+vsize*ithread, velocitya.begin()+vsize*(ithread+1), Eigen::Vector3d(0,0,0)); #pragma omp for schedule(static) for (size_t i = 0; i < clusters.size(); i++) { const std::set& cluster = clusters[i]; // ... expensive calculations ... for (int j : cluster) velocitya[ithread*vsize+j] += f(j); } #pragma omp for schedule(static) for(int i=0; i 

Este método requiere cuidado / ajuste extra debido a la falsa compartición que no he hecho.

En cuanto a qué método es mejor tendrás que probar.