activar - El ejemplo más simple posible para mostrar que la GPU supera a la CPU usando CUDA
cuda ubuntu (4)
En primer lugar, reiteraré mi comentario: las GPU son de gran ancho de banda y alta latencia. Intentar que la GPU supere a una CPU para un trabajo de nanosegundos (o incluso un milisegundo o un segundo trabajo) no tiene el objetivo de hacer cosas de GPU. A continuación se incluye un código simple, pero para apreciar realmente los beneficios de rendimiento de la GPU, necesitará un gran problema para amortizar los costos de inicio en ... de lo contrario, no tiene sentido. Puedo vencer a un Ferrari en una carrera de dos pies, simplemente porque me toma un tiempo girar la llave, arrancar el motor y presionar el pedal. Eso no significa que sea más rápido que el Ferrari de ninguna manera significativa.
Use algo como esto en C ++:
#define N (1024*1024)
#define M (1000000)
int main()
{
float data[N]; int count = 0;
for(int i = 0; i < N; i++)
{
data[i] = 1.0f * i / N;
for(int j = 0; j < M; j++)
{
data[i] = data[i] * data[i] - 0.25f;
}
}
int sel;
printf("Enter an index: ");
scanf("%d", &sel);
printf("data[%d] = %f/n", sel, data[sel]);
}
Use algo como esto en CUDA / C:
#define N (1024*1024)
#define M (1000000)
__global__ void cudakernel(float *buf)
{
int i = threadIdx.x + blockIdx.x * blockDim.x;
buf[i] = 1.0f * i / N;
for(int j = 0; j < M; j++)
buf[i] = buf[i] * buf[i] - 0.25f;
}
int main()
{
float data[N]; int count = 0;
float *d_data;
cudaMalloc(&d_data, N * sizeof(float));
cudakernel<<<N/256, 256>>>(d_data);
cudaMemcpy(data, d_data, N * sizeof(float), cudaMemcpyDeviceToHost);
cudaFree(d_data);
int sel;
printf("Enter an index: ");
scanf("%d", &sel);
printf("data[%d] = %f/n", sel, data[sel]);
}
Si eso no funciona, intente agrandar N y M, o cambiar 256 a 128 o 512.
Estoy buscando la cantidad de código más concisa posible que pueda codificarse tanto para una CPU (con g ++) como para una GPU (con nvcc) para las cuales la GPU supera constantemente a la CPU. Cualquier tipo de algoritmo es aceptable.
Para aclarar: estoy buscando literalmente dos bloques de código cortos, uno para la CPU (que utiliza C ++ en g ++) y otro para la GPU (que utiliza C ++ en nvcc) para los cuales la GPU supera. Preferiblemente en la escala de segundos o milisegundos. El par de códigos más corto posible.
Estoy de acuerdo con los comentarios de David acerca de que OpenCL es una excelente manera de probar esto, debido a lo fácil que es cambiar entre ejecutar código en la CPU frente a GPU. Si puedes trabajar en una Mac, Apple tiene una buena cantidad de código de muestra que hace una simulación de N-cuerpo usando OpenCL , con núcleos ejecutándose en la CPU, GPU o ambos. Puede alternar entre ellos en tiempo real, y el recuento de FPS se muestra en pantalla.
Para un caso mucho más simple, tienen una aplicación de línea de comandos OpenCL "hola mundo" que calcula los cuadrados de una manera similar a la que describe David. Eso probablemente podría ser portado a plataformas que no sean Mac sin mucho esfuerzo. Para cambiar entre el uso de GPU y CPU, creo que solo necesita cambiar el
int gpu = 1;
línea en el archivo fuente hello.c a 0 para CPU, 1 para GPU.
Apple tiene algo más de código de ejemplo de OpenCL en su listado principal de código fuente de Mac .
El Dr. David Gohara tuvo un ejemplo de aceleración de la GPU de OpenCL al realizar cálculos de dinámica molecular al final de esta sesión de video introductorio sobre el tema (alrededor del minuto 34). En su cálculo, ve una aceleración de aproximadamente 27X al pasar de una implementación paralela que se ejecuta en 8 núcleos de CPU a una sola GPU. Una vez más, no es el ejemplo más simple, pero muestra una aplicación del mundo real y la ventaja de ejecutar ciertos cálculos en la GPU.
También he realizado algunos retoques en el espacio móvil utilizando los sombreadores de OpenGL ES para realizar cálculos rudimentarios . Descubrí que un simple sombreado de umbral de color en una imagen era aproximadamente 14-28X más rápido cuando se ejecuta como un sombreador en la GPU que el mismo cálculo realizado en la CPU para este dispositivo en particular.
Para referencia, hice un ejemplo similar con mediciones de tiempo. Con la GTX 660, la aceleración de la GPU fue 24X, donde su operación incluye transferencias de datos además del cálculo real.
#include "cuda_runtime.h"
#include "device_launch_parameters.h"
#include <stdio.h>
#include <time.h>
#define N (1024*1024)
#define M (10000)
#define THREADS_PER_BLOCK 1024
void serial_add(double *a, double *b, double *c, int n, int m)
{
for(int index=0;index<n;index++)
{
for(int j=0;j<m;j++)
{
c[index] = a[index]*a[index] + b[index]*b[index];
}
}
}
__global__ void vector_add(double *a, double *b, double *c)
{
int index = blockIdx.x * blockDim.x + threadIdx.x;
for(int j=0;j<M;j++)
{
c[index] = a[index]*a[index] + b[index]*b[index];
}
}
int main()
{
clock_t start,end;
double *a, *b, *c;
int size = N * sizeof( double );
a = (double *)malloc( size );
b = (double *)malloc( size );
c = (double *)malloc( size );
for( int i = 0; i < N; i++ )
{
a[i] = b[i] = i;
c[i] = 0;
}
start = clock();
serial_add(a, b, c, N, M);
printf( "c[0] = %d/n",0,c[0] );
printf( "c[%d] = %d/n",N-1, c[N-1] );
end = clock();
float time1 = ((float)(end-start))/CLOCKS_PER_SEC;
printf("Serial: %f seconds/n",time1);
start = clock();
double *d_a, *d_b, *d_c;
cudaMalloc( (void **) &d_a, size );
cudaMalloc( (void **) &d_b, size );
cudaMalloc( (void **) &d_c, size );
cudaMemcpy( d_a, a, size, cudaMemcpyHostToDevice );
cudaMemcpy( d_b, b, size, cudaMemcpyHostToDevice );
vector_add<<< (N + (THREADS_PER_BLOCK-1)) / THREADS_PER_BLOCK, THREADS_PER_BLOCK >>>( d_a, d_b, d_c );
cudaMemcpy( c, d_c, size, cudaMemcpyDeviceToHost );
printf( "c[0] = %d/n",0,c[0] );
printf( "c[%d] = %d/n",N-1, c[N-1] );
free(a);
free(b);
free(c);
cudaFree( d_a );
cudaFree( d_b );
cudaFree( d_c );
end = clock();
float time2 = ((float)(end-start))/CLOCKS_PER_SEC;
printf("CUDA: %f seconds, Speedup: %f/n",time2, time1/time2);
return 0;
}
Un método muy, muy simple sería calcular los cuadrados para, por ejemplo, los primeros 100,000 enteros, o una operación de matriz grande. Es fácil de implementar y se presta a las fortalezas de las GPU al evitar las ramificaciones, no requiere una pila, etc. Hice esto con OpenCL vs C ++ hace un tiempo y obtuve resultados bastante sorprendentes. (Un GTX460 de 2 GB logró aproximadamente 40 veces el rendimiento de una CPU de doble núcleo).
¿Estás buscando código de ejemplo, o simplemente ideas?
Editar
El 40x era vs una CPU de doble núcleo, no un núcleo cuádruple.
Algunos punteros:
- Asegúrese de que no está ejecutando, por ejemplo, Crysis mientras ejecuta sus puntos de referencia.
- Derriba todas las aplicaciones y servicios innecesarios que podrían estar robando el tiempo de CPU.
- Asegúrese de que su hijo no comience a ver una película en su PC mientras se ejecutan los puntos de referencia. La decodificación de hardware MPEG tiende a influir en el resultado. (La reproducción automática permite que mi hijo de dos años comience con Despicable Me insertando el disco. Sí).
Como dije en mi respuesta de comentario a @Paul R, considere usar OpenCL ya que le permitirá ejecutar fácilmente el mismo código en la GPU y la CPU sin tener que volver a implementarlos.
(Estos son probablemente bastante obvios en retrospectiva.)