Подтвердить что ты не робот

Простейший возможный пример для показа GPU превосходит процессор с использованием CUDA

Я ищу наиболее краткое количество кода, которое может быть закодировано как для CPU (с использованием g++), так и для графического процессора (с использованием nvcc), для которого графический процессор последовательно превосходит CPU. Любой тип алгоритма является приемлемым.

Чтобы уточнить: я буквально ищу два коротких блока кода, один для CPU (с использованием С++ в g++) и один для графического процессора (с использованием С++ в nvcc), для которого GPU превосходит. Предпочтительно в масштабе секунд или миллисекунд. Возможна кратчайшая пара кодов.

4b9b3361

Ответ 1

Во-первых, я повторю свой комментарий: графические процессоры имеют высокую пропускную способность и высокую задержку. Пытаясь заставить GPU бить процессор за наносекунду (или даже миллисекунду или вторую работу), совершенно не хватает места для работы с GPU. Ниже приведен какой-то простой код, но для того, чтобы действительно оценить преимущества GPU, вам понадобится большой размер проблемы, чтобы амортизировать затраты на запуск... в противном случае это бессмысленно. Я могу победить Ferrari в гонке на два фута, просто потому, что требуется время, чтобы повернуть ключ, запустить двигатель и нажать на педаль. Это не значит, что я быстрее Ferrari каким-либо значимым образом.

Используйте что-то вроде этого в С++:

  #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]);
  }

Используйте что-то подобное в 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]);
  }

Если это не сработает, попробуйте сделать N и M больше или измените 256 на 128 или 512.

Ответ 2

Очень простой метод заключается в вычислении квадратов, например, для первых 100 000 целых чисел или операции с большой матрицей. Ita легко внедряется и поддается силам на графическом процессоре, избегая ветвления, не требуя стека и т.д. Я делал это с OpenCL vs С++ некоторое время назад и получал некоторые довольно удивительные результаты. (2 ГБ GTX460 достиг 40-кратной производительности двухъядерного процессора.)

Вы ищете пример кода или просто идеи?

Edit

40x - это двухъядерный процессор, а не четырехъядерный процессор.

Некоторые указатели:

  • Убедитесь, что вы не работаете, скажем, Crysis во время выполнения тестов.
  • Снимите все ненужные приложения и службы, которые могут украсть процессорное время.
  • Убедитесь, что ваш ребенок не запускает просмотр фильма на вашем ПК во время работы тестов. Аппаратное декодирование MPEG имеет тенденцию влиять на результат. (Автовоспроизведение пусть мой двухлетний старт Презренный меня, вставив диск. Yay.)

Как я уже сказал в ответе на комментарий к @Paul R, рассмотрим возможность использования OpenCL, так как он легко позволит вам запустить тот же код на графическом процессоре и процессоре без необходимости его переопределения.

(Вероятно, это довольно очевидно в ретроспективе.)

Ответ 3

Я согласен с комментариями Дэвида о том, что OpenCL - отличный способ проверить это, из-за того, насколько легко переключаться между запущенным кодом на CPU и GPU. Если вы можете работать на Mac, у Apple есть хороший образец кода, который выполняет N-body моделирования с использованием OpenCL, с ядрами работающих на CPU, GPU или обоих. Вы можете переключаться между ними в режиме реального времени, и подсчет FPS отображается на экране.

В гораздо более простом случае они имеют "привет мир" приложение командной строки OpenCL, которое вычисляет квадраты способом, аналогичным тому, что описывает Дэвид, Вероятно, это можно было бы портировать на платформы без Mac без особых усилий. Чтобы переключиться между использованием графического процессора и процессора, я считаю, вам просто нужно изменить

int gpu = 1;

в исходном файле hello.c до 0 для CPU, 1 для графического процессора.

В Apple есть еще один пример кода OpenCL в своем основном исходном коде Mac.

р. У Дэвида Гохара был пример ускорения OpenCL GPU при выполнении расчетов молекулярной динамики в самом конце этот вводный видеосеанс по теме (около минуты 34). В его расчетах он видит примерно 27-кратное ускорение, перейдя от параллельной реализации, выполняемой на 8 ядрах процессора, к одному графическому процессору. Опять же, это не самый простой пример, но он показывает реальное приложение и преимущество выполнения определенных вычислений на графическом процессоре.

Я также сделал некоторые манипуляции в мобильном пространстве с использованием шейдеров OpenGL ES для выполнения рудиментарных вычислений. Я обнаружил, что простой шейдер порога цвета, проходящий через изображение, был примерно на 14-28X быстрее при запуске в качестве шейдера на графическом процессоре, чем тот же расчет, выполненный на CPU для этого конкретного устройства.

Ответ 4

Для справки я сделал аналогичный пример с измерениями времени. С GTX 660 ускорение GPU было 24X, где его работа включала передачу данных в дополнение к фактическим вычислениям.

#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;
}