CUDA Тяга медленная при работе больших векторов на моей машине

Я новичок в CUDA и читаю некоторые руководства по тяге. Я пишу простой, но ужасно организованный код и пытаюсь выяснить ускорение тяги (правильна ли эта идея?). Я пытаюсь добавить два вектора (с 10000000 int) к другому вектору, добавив массив в CPU и добавив device_vector в gpu.

Вот эта вещь:

#include <iostream>
#include "cuda.h"#include "cuda_runtime.h"#include "device_launch_parameters.h"#include <thrust/device_vector.h>
#include <thrust/host_vector.h>

#define N 10000000
int main(void)
{
float time_cpu;
float time_gpu;
int *a = new int[N];
int *b = new int[N];
int *c = new int[N];
for(int i=0;i<N;i++)
{
a[i]=i;
b[i]=i*i;
}
clock_t start_cpu,stop_cpu;
start_cpu=clock();
for(int i=0;i<N;i++)
{
c[i]=a[i]+b[i];
}
stop_cpu=clock();
time_cpu=(double)(stop_cpu-start_cpu)/CLOCKS_PER_SEC*1000;
std::cout<<"Time to generate (CPU):"<<time_cpu<<std::endl;
thrust::device_vector<int> X(N);
thrust::device_vector<int> Y(N);
thrust::device_vector<int> Z(N);
for(int i=0;i<N;i++)
{
X[i]=i;
Y[i]=i*i;
}
cudaEvent_t start, stop;
cudaEventCreate(&start);
cudaEventCreate(&stop);
cudaEventRecord(start,0);
thrust::transform(X.begin(), X.end(),
Y.begin(),
Z.begin(),
thrust::plus<int>());
cudaEventRecord(stop,0);
cudaEventSynchronize(stop);
float elapsedTime;
cudaEventElapsedTime(&elapsedTime,start,stop);
std::cout<<"Time to generate (thrust):"<<elapsedTime<<std::endl;
cudaEventDestroy(start);
cudaEventDestroy(stop);
getchar();
return 0;
}

Результаты процессора появляются очень быстро, но на моей машине GPU работает ОЧЕНЬ медленно (i5-2320,4G, GTX 560 Ti), время процессора составляет около 26 time, время работы GPU — около 30! Я только что сделал ошибку с глупыми ошибками в моем коде? или была более глубокая причина?

Будучи новичком в C ++, я проверял свой код снова и снова и все еще медленнее работал на GPU с тягой, поэтому я провел несколько экспериментов, чтобы показать разницу в вычислении vectorAdd с пятью различными подходами.
Я использую Windows API QueryPerformanceFrequency() как единый метод измерения времени.

Каждый из экспериментов выглядит так:

f = large_interger.QuadPart;
QueryPerformanceCounter(&large_interger);
c1 = large_interger.QuadPart;

for(int j=0;j<10;j++)
{
for(int i=0;i<N;i++)//CPU array adding
{
c[i]=a[i]+b[i];
}
}
QueryPerformanceCounter(&large_interger);
c2 = large_interger.QuadPart;
printf("Time to generate (CPU array adding) %lf ms\n", (c2 - c1) * 1000 / f);

а вот мой простой __global__ функция для добавления массива GPU:

__global__ void add(int *a, int *b, int *c)
{
int tid=threadIdx.x+blockIdx.x*blockDim.x;
while(tid<N)
{
c[tid]=a[tid]+b[tid];
tid+=blockDim.x*gridDim.x;
}
}

и функция называется как:

for(int j=0;j<10;j++)
{
add<<<(N+127)/128,128>>>(dev_a,dev_b,dev_c);//GPU array adding
}

Я добавляю вектор a [N] и b [N] к вектору c [N] для цикла 10 раз:

  1. добавить массив на CPU
  2. добавить std :: vector на процессор
  3. добавить thrust :: host_vector на ЦП
  4. добавить тягу :: устройство_вектор на GPU
  5. добавить массив на GPU. и вот результат

с N = 10000000

и я получаю результаты:

  1. Добавление массива процессора 268.992968мс
  2. CPU std :: vector, добавляющий 1908.013595ms
  3. CPU Thrust :: host_vector добавление 10776.456803ms
  4. GPU Thrust :: device_vector добавление 297.156610ms
  5. Добавление массива GPU 5.210573мс

И это меня смутило, я не знаком с реализацией библиотеки шаблонов. Действительно ли так сильно различалась производительность между контейнерами и структурами необработанных данных?

6

Решение

Большая часть времени выполнения тратится на ваш цикл, который инициализирует X [i] и Y [i]. Хотя это законно, это очень медленный способ инициализации больших векторов устройств. Было бы лучше создать векторы узлов, инициализировать их, а затем скопировать их на устройство. В качестве теста измените ваш код следующим образом (сразу после цикла, в котором вы инициализируете векторы устройств X [i] и Y [i]):

}  // this is your line of code
std::cout<< "Starting GPU run" <<std::endl;  //add this line
cudaEvent_t start, stop;   //this is your line of code

Затем вы увидите, что результаты синхронизации GPU появляются почти сразу после того, как распечатывается добавленная строка. Таким образом, все время, которое вы ждете, тратится на инициализацию этих векторов устройств непосредственно из кода хоста.

Когда я запускаю это на своем ноутбуке, я получаю время CPU около 40 и время GPU около 5, поэтому GPU работает примерно в 8 раз быстрее, чем CPU для тех фрагментов кода, которые вы фактически синхронизируете.

Если вы создадите X и Y в качестве хост-векторов, а затем создадите аналогичные векторы устройств d_X и d_Y, общее время выполнения будет меньше, например:

thrust::host_vector<int> X(N);
thrust::host_vector<int> Y(N);
thrust::device_vector<int> Z(N);
for(int i=0;i<N;i++)
{
X[i]=i;
Y[i]=i*i;
}
thrust::device_vector<int> d_X = X;
thrust::device_vector<int> d_Y = Y;

и измените ваш вызов преобразования на:

thrust::transform(d_X.begin(), d_X.end(),
d_Y.begin(),
Z.begin(),
thrust::plus<int>());

Итак, теперь вы указали, что измерение запуска CPU быстрее, чем измерение GPU. Извините, я спешил с выводами. Мой ноутбук — это ноутбук HP с 2,6 ГГц ядром i7 и Quadro 1000M GPU. Я использую Centos 6.2 Linux. Несколько комментариев: если вы выполняете какие-либо тяжелые задачи отображения на вашем графическом процессоре, это может снизить производительность. Кроме того, при тестировании этих вещей обычно используется один и тот же механизм для сравнения, вы можете использовать cudaEvents для обоих, если хотите, он может синхронизировать код процессора с кодом GPU. Кроме того, при работе с тягой принято выполнять прогон разогрева без привязки, затем повторять тест для измерения, и аналогично обычной практикой является запуск теста 10 или более раз в цикле, а затем деление для получения среднего значения. В моем случае я могу сказать, что измерение clocks () довольно грубое, потому что последовательные прогоны дадут мне 30, 40 или 50. При измерении на GPU я получаю что-то вроде 5.18256. Некоторые из этих вещей могут помочь, но я не могу точно сказать, почему ваши результаты и мои так сильно отличаются (на стороне GPU).

ОК, я сделал еще один эксперимент. Компилятор будет иметь большое значение на стороне процессора. Я скомпилировал с ключом -O3 и время ЦП сократилось до 0. Затем я преобразовал измерение времени ЦП из метода clocks () в cudaEvents, и я получил измеренное время ЦП 12,4 (с оптимизацией -O3) и все еще 5,1 на GPU боковая сторона.

Ваш пробег будет варьироваться в зависимости от метода синхронизации и того, какой компилятор вы используете на стороне процессора.

9

Другие решения

Первый, Y[i]=i*i; не помещается в целое число для 10M элементов. Целые числа содержат примерно 1e10, а вашему коду нужно 1e14.

Во-вторых, похоже, что время преобразования правильное и должно быть быстрее, чем ЦП, независимо от того, какую библиотеку вы используете. Предложение Роберта инициализировать векторы на CPU, а затем перенести их в GPU, подходит для этого случая.

В-третьих, поскольку мы не можем сделать кратное целое число, ниже приведен более простой код библиотеки CUDA (с использованием ArrayFire над которым я работаю) сделать то же самое с поплавками, для вашего бенчмаркинга:

int n = 10e6;
array x = array(seq(n));
array y = x * x;
timer t = timer::tic();
array z = x + y;
af::eval(z); af::sync();
printf("elapsed seconds: %g\n", timer::toc( t));

Удачи!

1

Недавно я проводил аналогичный тест с использованием CUDA Thrust на своем Quadro 1000m. Я использую thrust :: sort_by_key в качестве эталона для тестирования его производительности, и результат слишком хорош, чтобы убедить мои слова. Для сортировки 512 МБ пар требуется более 100 мс.

Для вашей проблемы, я запутался в двух вещах.

(1) Почему вы умножаете этот time_cpu на 1000? Без 1000 это уже в секундах.

time_cpu=(double)(stop_cpu-start_cpu)/CLOCKS_PER_SEC*1000;

(2) И, упоминая 26, 30, 40, вы имеете в виду секунды или мс? В отчете «cudaEvent» истекло время в «мс», а не «s».

-1
По вопросам рекламы ammmcru@yandex.ru
Adblock
detector