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.