multithreading - dollars - OpenMP: compartir matrices entre hilos
openmp python (1)
¡Buen día a todos!
Estoy realizando una simulación de dinámica molecular, y recientemente comencé a tratar de implementarla en paralelo. A primera vista, todo parecía lo suficientemente simple: escribe #pragma omp parallel for directive delante de los bucles que consumen más tiempo. Pero sucede que las funciones en esos bucles operan en arreglos o, para ser precisos, en arreglos que pertenecen a un objeto de mi clase que contiene toda la información sobre el sistema de partículas y las funciones que operan en este sistema, de modo que cuando agregué ese número pragma directive antes de uno de los bucles que más tiempo consumía, el tiempo de cálculo en realidad aumentó varias veces a pesar de que mi procesador de 2 hilos de 4 núcleos estaba completamente cargado.
Para resolver esto escribí otro programa más simple. Este programa de prueba realiza dos bucles idénticos, uno en paralelo, y el segundo, en serie. Se mide el tiempo que lleva ejecutar estos dos bucles. Los resultados me sorprendieron: siempre que el primer bucle se calculaba en paralelo, su tiempo de cálculo disminuía en comparación con el modo serie (1500 y 6000 ms respectivamente), pero el tiempo de cálculo del segundo bucle aumentaba drásticamente (15 000 frente a 6000 en serie).
Traté de usar las cláusulas private () y firstprivate (), pero los resultados fueron los mismos. ¿No deberían todas las variables definidas e inicializadas antes de la región paralela ser compartidas automáticamente de todos modos? El tiempo de cálculo del segundo bucle vuelve a la normalidad si se realiza en otro vector: vec2, pero crear un nuevo vector para cada iteración es, claramente, no una opción. También traté de poner la actualización actual de vec1 en el área crítica de #pragma omp, pero eso tampoco fue bueno. Ninguno de los dos ayudó a agregar la cláusula Shared (vec1).
Agradecería que pudieras señalar mis errores y mostrar la forma correcta.
¿Es necesario poner eso privado (i) en el código?
Aquí está este programa de prueba:
#include "stdafx.h"
#include <omp.h>
#include <array>
#include <time.h>
#include <vector>
#include <iostream>
#include <Windows.h>
using namespace std;
#define N1 1000
#define N2 4000
#define dim 1000
int main(){
vector<int>res1,res2;
vector<double>vec1(dim),vec2(N1);
clock_t t, tt;
int k=0;
for( k = 0; k<dim; k++){
vec1[k]=1;
}
t = clock();
#pragma omp parallel
{
double temp;
int i,j,k;
#pragma omp for private(i)
for( i = 0; i<N1; i++){
for(j = 0; j<N2; j++){
for( k = 0; k<dim; k++){
temp+= j;
}
}
vec1[i]+=temp;
temp = 0;
}
}
tt = clock();
cout<<tt-t<<endl;
for(int k = 0; k<dim; k++){
vec1[k]=1;
}
t = clock();
for(int g = 0; g<N1; g++){
for(int h = 0; h<N2; h++){
for(int y = 0; y<dim; y++){
vec1[g]+=h;
}
}
}
tt = clock();
cout<<tt-t<<endl;
getchar();
}
¡Gracias por tu tiempo!
PD: uso visual studio 2012, mi procesador es Intel Core i3-2370M. Mi archivo de ensamblaje en dos partes:
¡Felicitaciones! Ha expuesto otra mala implementación de OpenMP, cortesía de Microsoft. Mi teoría inicial fue que el problema proviene de la caché L3 particionada en Sandy Bridge y más tarde en las CPU de Intel. Pero el resultado de ejecutar el segundo ciclo solo en la primera mitad del vector no confirmó esa teoría. Entonces tiene que ser algo en el generador de código que se activa cuando OpenMP está habilitado. La salida del ensamblaje lo confirma.
Básicamente, el compilador no optimiza el bucle en serie al compilar con OpenMP habilitado. De ahí viene la desaceleración. Parte del problema también fue introducido por usted haciendo que el segundo bucle no sea idéntico al primero. En el primer bucle acumula valores intermedios en una variable temporal, que el compilador optimiza para registrar variables, mientras que en el segundo caso invoca el operator[]
en cada iteración. Cuando compila sin OpenMP habilitado, el optimizador de código transforma el segundo bucle en algo que es bastante similar al primer bucle, por lo que obtiene casi el mismo tiempo de ejecución para ambos bucles.
Cuando habilita OpenMP, el optimizador de código no optimiza el segundo ciclo y se ejecuta mucho más lento. El hecho de que su código ejecute un bloque paralelo antes de eso no tiene nada que ver con la ralentización. Supongo que el optimizador de código no puede comprender el hecho de que vec1
está fuera del alcance de la región parallel
OpenMP y, por lo tanto, ya no debe tratarse como una variable compartida y el bucle puede optimizarse. Obviamente, esta es una "característica", que se introdujo en Visual Studio 2012, ya que el generador de código en Visual Studio 2010 puede optimizar el segundo ciclo incluso con OpenMP habilitado.
Una posible solución sería migrar a Visual Studio 2010. Otra solución (hipotética, ya que no tengo VS2012) sería extraer el segundo ciclo en una función y pasar el vector por referencia a él. Esperemos que el compilador sea lo suficientemente inteligente como para optimizar el código en la función separada.
Esta es una tendencia muy mala. Microsoft prácticamente ha dejado de admitir OpenMP en Visual C ++. Su implementación todavía (casi) se ajusta a OpenMP 2.0 solamente (por lo tanto, no hay tareas explícitas y otros extras de OpenMP 3.0+) y errores como este no hacen las cosas mejor. Le recomendaría que cambie a otro compilador habilitado para OpenMP (Compilador Intel C / C ++, GCC, cualquier cosa que no sea Microsoft) o cambie a otro paradigma de enhebrado independiente del compilador, por ejemplo, Intel Threading Building Blocks. Microsoft claramente está impulsando su biblioteca paralela para .NET y ahí es donde va todo el desarrollo.
Gran advertencia de grasa
¡No use el clock()
para medir el tiempo transcurrido del reloj de pared! Esto solo funciona como se espera en Windows. En la mayoría de los sistemas Unix (incluido Linux), clock()
realmente devuelve el tiempo total de CPU consumido por todos los hilos en el proceso desde que se creó . Esto significa que clock()
puede devolver valores que son varias veces mayores que el tiempo del reloj de pared transcurrido (si el programa se ejecuta con muchos hilos ocupados) o varias veces más cortos que el reloj de pared (si el programa duerme o espera en Eventos IO entre las mediciones). En cambio, en los programas OpenMP, debe usarse la función de temporizador portátil omp_get_wtime()
.