CUDA在我的机器上操作大型矢量时推力较慢



我是CUDA的初学者,正在阅读一些推力教程。我写了一个简单但组织严密的代码,并试图计算推力的加速度。(这个想法正确吗?)。我试图通过在cpu上添加array和在gpu上添加device_vector,将两个向量(具有10000000 int)添加到另一个向量。

事情是这样的:

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

CPU结果看起来真的很快,但gpu在我的机器上运行真的很慢(i5-2320,4G,GTX 560 Ti),CPU时间大约是26,gpu时间大约是30!我只是在代码中犯了愚蠢的错误,把推力搞错了吗?还是有更深层次的原因?

作为一个C++新手,我一遍又一遍地检查我的代码,但在GPU上使用推力的时间仍然较慢,所以我做了一些实验来展示用五种不同方法计算vectorAdd的差异。我使用windows APIQueryPerformanceFrequency()作为统一的时间测量方法。

每个实验看起来都是这样的:

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 msn", (c2 - c1) * 1000 / f);

这是我为GPU阵列添加的简单__global__函数:

__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. 在CPU上添加std::vector
  3. 在CPU上添加推力::host_vector
  4. 在GPU上添加推力::device_vector
  5. 在GPU上添加阵列。结果是

N=10000000

我得到结果:

  1. CPU阵列增加268.992968ms
  2. CPU std::矢量加法1908.013595ms
  3. CPU推力::host_vector添加10776.456803ms
  4. GPU推力::device_vector添加297.156610ms
  5. GPU阵列增加5.210573ms

这让我很困惑,我不熟悉模板库的实现。容器和原始数据结构之间的性能真的有这么大的差异吗?

大部分执行时间都花在初始化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的运行速度大约是CPU的8倍。

如果您创建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测量快。对不起,我妄下结论。我的笔记本电脑是一台具有2.6GHz核心i7和Quadro 1000M gpu的HP笔记本电脑。我运行的是centos6.2linux。一些评论:如果你在GPU上运行任何繁重的显示任务,这可能会降低性能。此外,在对这些东西进行基准测试时,通常使用相同的机制进行比较,如果需要,可以对两者使用cudaEvents,它可以将CPU代码与GPU代码的时间相同。此外,推力的常见做法是进行不计时的热身跑,然后重复测试进行测量,同样,在一个循环中进行10次或更多次测试,然后除以得到平均值也是常见的做法。在我的情况下,我可以看出clocks()的测量非常粗略,因为连续运行会给我30、40或50。在GPU测量上,我得到了大约5.18256。其中一些东西可能会有所帮助,但我不能确切地说为什么你的结果和我的结果(在GPU方面)有这么大的差异。

好吧,我又做了一个实验。编译器将在CPU方面产生很大的不同。我用-O3开关编译,CPU时间降到了0。然后,我将CPU计时测量从clocks()方法转换为cudaEvents,得到的CPU测量时间为12.4(经过-O3优化),在GPU方面仍然是5.1。

您的里程数将因计时方法和您在CPU端使用的编译器而异。

首先,Y[i]=i*i;不适合10M元素的整数。Integers大约容纳1e10,而您的代码需要1e14。

其次,看起来转换的时间是正确的,应该比CPU快,无论你使用的是哪个库。Robert建议在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: %gn", timer::toc( t));

祝你好运!

我最近在Quadro 1000米上使用CUDA推力进行了类似的测试。我使用thrust::sort_by_key作为测试其性能的基准,结果太好了,无法说服我的支持者。对512MB对进行排序需要100多毫秒。

对于你的问题,我有两件事感到困惑。

(1) 你为什么把这个time_cpu乘以1000?如果没有1000,它已经在几秒钟内了。

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

(2) 提到26、30、40,你的意思是秒还是毫秒?"cudaEvent"报告的运行时间以"ms"而非"s"为单位。

最新更新