6

Así que me doy cuenta de que esta pregunta suena estúpida (y sí, estoy usando un doble núcleo), pero he probado dos bibliotecas diferentes (Grand Central Dispatch y OpenMP) y cuando uso clock() para sincronizar el código con y sin las líneas que lo hacen paralelo, la velocidad es la misma. (para el registro ambos usaban su propia forma de paralelo para). Informan que se ejecutan en diferentes hilos, pero tal vez se ejecutan en el mismo núcleo? ¿Hay alguna manera de verificar? (Ambas bibliotecas son para C, me siento incómodo en las capas inferiores.) Esto es súper raro. ¿Algunas ideas?¿Por qué mi computadora no muestra una aceleración cuando uso un código paralelo?

+1

Mucho de esto depende del tipo de código que está ejecutando y de lo que está haciendo exactamente. Además, hay asuntos de escala a tener en cuenta: la mayoría de las bibliotecas paralelas tienen algún tipo de sobrecarga de configuración que, al menos para pequeños programas, puede dominar el tiempo de ejecución real del programa. – Amber

Respuesta

19

EDITAR: Se agregaron detalles para Grand Central Dispatch en respuesta al comentario de OP.

Mientras que las otras respuestas aquí son útiles en general, la respuesta específica a su pregunta es que no debe usar clock() para comparar el tiempo. clock() mide el tiempo de CPU que se suma a través de los hilos. Cuando divide un trabajo entre núcleos, utiliza al menos el mismo tiempo de CPU (generalmente un poco más debido a la sobrecarga de los hilos). Busque clock() en la página this, para encontrar "Si el proceso tiene varios subprocesos, se agrega el tiempo de CPU consumido por todos los subprocesos individuales del proceso".

Es solo que el trabajo se divide entre subprocesos, por lo que el tiempo total que debe esperar es menor. Debería utilizar el tiempo de pared (la hora en un reloj de pared). OpenMP proporciona una rutina omp_get_wtime() para hacerlo. Tome la siguiente rutina como ejemplo:

#include <omp.h> 
#include <time.h> 
#include <math.h> 
#include <stdio.h> 

int main(int argc, char *argv[]) { 
    int i, nthreads; 
    clock_t clock_timer; 
    double wall_timer; 
    for (nthreads = 1; nthreads <=8; nthreads++) { 
     clock_timer = clock(); 
     wall_timer = omp_get_wtime(); 
     #pragma omp parallel for private(i) num_threads(nthreads) 
     for (i = 0; i < 100000000; i++) cos(i); 
     printf("%d threads: time on clock() = %.3f, on wall = %.3f\n", \ 
      nthreads, \ 
      (double) (clock() - clock_timer)/CLOCKS_PER_SEC, \ 
      omp_get_wtime() - wall_timer); 
    } 
} 

Los resultados son los siguientes:

1 threads: time on clock() = 0.258, on wall = 0.258 
2 threads: time on clock() = 0.256, on wall = 0.129 
3 threads: time on clock() = 0.255, on wall = 0.086 
4 threads: time on clock() = 0.257, on wall = 0.065 
5 threads: time on clock() = 0.255, on wall = 0.051 
6 threads: time on clock() = 0.257, on wall = 0.044 
7 threads: time on clock() = 0.255, on wall = 0.037 
8 threads: time on clock() = 0.256, on wall = 0.033 

se puede ver que el tiempo clock() no cambia mucho. Obtengo 0.254 sin pragma, por lo que es un poco más lento usar openMP con un hilo que no usar openMP, pero el tiempo de pared disminuye con cada hilo.

La mejora no siempre será tan buena debido, por ejemplo, a partes de su cálculo que no son paralelas (vea Amdahl's_law) o diferentes hilos que luchan en la misma memoria.

EDITAR: Para Grand Central Dispatch, el GCD reference indica que GCD usa gettimeofday para el tiempo de pared. Así, se crea una nueva aplicación de cacao, y en applicationDidFinishLaunching pongo:

struct timeval t1,t2; 
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
for (int iterations = 1; iterations <= 8; iterations++) { 
    int stride = 1e8/iterations; 
    gettimeofday(&t1,0); 
    dispatch_apply(iterations, queue, ^(size_t i) { 
     for (int j = 0; j < stride; j++) cos(j); 
    }); 
    gettimeofday(&t2,0); 
    NSLog(@"%d iterations: on wall = %.3f\n",iterations, \ 
       t2.tv_sec+t2.tv_usec/1e6-(t1.tv_sec+t1.tv_usec/1e6)); 
} 

y me da los siguientes resultados en la consola:

2010-03-10 17:33:43.022 GCDClock[39741:a0f] 1 iterations: on wall = 0.254 
2010-03-10 17:33:43.151 GCDClock[39741:a0f] 2 iterations: on wall = 0.127 
2010-03-10 17:33:43.236 GCDClock[39741:a0f] 3 iterations: on wall = 0.085 
2010-03-10 17:33:43.301 GCDClock[39741:a0f] 4 iterations: on wall = 0.064 
2010-03-10 17:33:43.352 GCDClock[39741:a0f] 5 iterations: on wall = 0.051 
2010-03-10 17:33:43.395 GCDClock[39741:a0f] 6 iterations: on wall = 0.043 
2010-03-10 17:33:43.433 GCDClock[39741:a0f] 7 iterations: on wall = 0.038 
2010-03-10 17:33:43.468 GCDClock[39741:a0f] 8 iterations: on wall = 0.034 

que es casi lo mismo que me estaba anteriormente.

Este es un ejemplo muy artificial. De hecho, debe asegurarse de mantener la optimización en -O0, de lo contrario, el compilador se dará cuenta de que no mantenemos ninguno de los cálculos y no hacemos el ciclo en absoluto. Además, el número entero del que estoy tomando cos es diferente en los dos ejemplos, pero eso no afecta demasiado los resultados. Consulte el STRIDE en la página de manual para dispatch_apply para saber cómo hacerlo correctamente y por qué iterations es ampliamente comparable con num_threads en este caso.

EDIT: Tomo nota de que la respuesta de Jacob incluye

uso el omp_get_thread_num() función dentro de mi bucle parallelized para imprimir cuyo núcleo está trabajando en ... De esta manera usted puede estar seguro de que se ejecuta en ambos núcleos.

que no es correcto (se ha corregido parcialmente mediante una edición). Usar omp_get_thread_num() es de hecho una buena manera de asegurarse de que su código sea multiproceso, pero no muestra "en qué núcleo está trabajando", simplemente qué hilo. Por ejemplo, el siguiente código:

#include <omp.h> 
#include <stdio.h> 

int main() { 
    int i; 
    #pragma omp parallel for private(i) num_threads(50) 
    for (i = 0; i < 50; i++) printf("%d\n", omp_get_thread_num()); 
} 

imprime que es el uso de hilos de 0 a 49, pero esto no muestra cuyo núcleo está trabajando, ya que sólo tienen ocho núcleos. Al observar el Monitor de actividad (el OP mencionó el GCD, por lo que debe estar en una Mac, vaya al Window/CPU Usage), puede ver los trabajos cambiando entre núcleos, entonces core! = Thread.

+0

Maldito buen punto. Normalmente uso RDTSC para crear perfiles, no clock(). –

+0

Gracias esto suena como lo que estaba buscando pero ¿podría explicar un poco más? Como si estuviera usando el reloj fuera del bucle for (después de que todos los hilos habían terminado excepto el hilo de llamada), eso significaría que clock() no es confiable para obtener el tiempo. No estoy seguro de si esto suena bien. Me importa la hora en sí misma (en milisegundos o lo que sea) que requiere la ejecución del código, sin tener en cuenta ninguna otra estadística. Además, ¿cuál sería la versión de GCD de omp_get_wtime? –

+0

'clock()' es confiable para obtener el tiempo de * CPU * que suma el tiempo utilizado por todos los hilos. GCD usa 'gettimeofday' para el tiempo de pared, vea los detalles y las referencias anteriores. – Ramashalanka

8

Lo más probable es que su tiempo de ejecución no esté vinculado por esos bucles en paralelo.

Mi sugerencia es que el perfil de su código para ver lo que está tomando la mayor parte del tiempo. La mayoría de los ingenieros le dirán que debe hacer esto antes de haciendo algo drástico para optimizar las cosas.

2

Es difícil de adivinar sin ningún detalle. Tal vez su aplicación ni siquiera está vinculada a la CPU. ¿Has visto cargar la CPU mientras tu código estaba en funcionamiento? ¿Golpeó al 100% en al menos un núcleo?

0

Si está utilizando mucha memoria dentro del lazo, eso podría evitar que sea más rápido. También podría examinar la biblioteca pthread para manejar manualmente el subproceso.

0

utilizo la función omp_get_thread_num() dentro de mi bucle parallelized imprimir cuyo núcleo que está trabajando en si no se especifica num_threads. Por ejemplo,

printf("Computing bla %d on core %d/%d ...\n",i+1,omp_get_thread_num()+1,omp_get_max_threads()); 

Lo anterior va a funcionar para este pragma #pragma omp paralelo para defecto (ninguno) compartido (a, b, c)

De esta manera usted puede estar seguro de que se está ejecutando en tanto núcleos ya que solo se crearán 2 subprocesos.

Por cierto, ¿está habilitado OpenMP cuando está compilando? En Visual Studio tiene que habilitarla en la Páginas de propiedades, C++ -> Language y establecer OpenMP Support a Yes

+0

Esto muestra qué hilo está haciendo el trabajo, no cuál núcleo: mi respuesta anterior proporciona detalles. – Ramashalanka

+0

Estás en lo correcto. – Jacob

1

Su pregunta le faltan algunos detalles muy importantes como lo que la naturaleza de su aplicación es, ¿qué parte de ella está usted tratando para mejorar, perfiles de resultados (si los hay), etc ...

dicho esto usted debe recordar varios puntos críticos cuando se acerca a un esfuerzo de mejora del rendimiento:

  • esfuerzos siempre deben concentrarse en las áreas de código que hav e ha demostrado, mediante el perfilado , siendo el
  • paralelización CPU código atado ineficientes casi nunca mejorar el rendimiento (en una sola máquina de la base). Perderá un tiempo precioso en interruptores de contexto innecesarios y obtendrá nada. Puede muy fácilmente empeorar el rendimiento haciendo esto.
  • Incluso si está paralelizando el código de CPU en una máquina multinúcleo, debe recordar que nunca tiene garantía de ejecución en paralelo.

Asegúrese de que no va en contra de estos puntos, porque una conjetura (salvo detalles adicionales) dirá que eso es exactamente lo que está haciendo.

+0

Su punto 2 no es del todo correcto. A menudo, cuando los programadores construyen algoritmos paralelos, naturalmente mejoran la localidad del código que se paraleliza. Esencialmente, bloquean sus bucles en aras del paralelismo, y luego su ejecución secuencial también se vuelve más rápida. – Novelocrat

Cuestiones relacionadas