在我的机器上操作大向量时,CUDA 推力很慢
Posted
技术标签:
【中文标题】在我的机器上操作大向量时,CUDA 推力很慢【英文标题】:CUDA Thrust slow when operating large vectors on my machine 【发布时间】:2012-09-19 08:50:35 【问题描述】:我是一个 CUDA 初学者,正在阅读一些推力教程。我编写了一个简单但组织得非常糟糕的代码,并试图找出推力的加速度。(这个想法正确吗?)。我尝试通过在 cpu 上添加数组并在 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 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);
这是我用于 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 次:
-
在 CPU 上添加数组
在 CPU 上添加 std::vector
在 CPU 上添加推力::host_vector
在 GPU 上添加推力::device_vector
在 GPU 上添加数组。这是结果
N=10000000
我得到了结果:
-
CPU 阵列增加 268.992968ms
CPU std::vector 添加 1908.013595ms
CPU Thrust::host_vector 添加 10776.456803ms
GPU Thrust::device_vector 添加 297.156610ms
GPU 阵列增加 5.210573ms
这让我很困惑,我不熟悉模板库的实现。容器和原始数据结构之间的性能真的差别很大吗?
【问题讨论】:
【参考方案1】:大部分执行时间都花在了初始化 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 笔记本电脑。我正在运行centos 6.2 linux。一些 cmets:如果您在 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 端使用的编译器而异。
【讨论】:
我没有看到他对初始化部分进行计时。所以我认为这不是问题。 当您实际运行代码时,时间会以合理的数字出现,即报告的 gpu 时间比报告的 cpu 时间快,正如我在回答中提到的那样。我也不认为这是问题所在。我相信 OP 会感到困惑,因为整体执行时间很长。 我知道初始化部分可能真的很慢,感谢您首先创建 host_vector 的建议。但问题是在我的电脑上CPU时间大约是26,GPU时间大约是30! (抱歉,我的问题没有说清楚,我已经编辑过了)我还将Y[i]=i*i
和c[i]=i*i
更改为 Y[i]=i
和 c[i]=i
。奇怪的是,我想知道 GPU 时间是否以某种方式乘以 10……您是如何在笔记本电脑上运行代码的? @罗伯特@gpu
在我的“回答”帖子中添加了一些回复
@Robert 我也参与了 0 毫秒问题。我也做了一些实验并编辑了我的问题。你可以看看。【参考方案2】:
首先,Y[i]=i*i;
不适合 10M 个元素的整数。整数大约包含 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: %g\n", timer::toc( t));
祝你好运!
【讨论】:
【参考方案3】:我最近在我的 Quadro 1000m 上使用 CUDA Thrust 进行了类似的测试。我使用thrust::sort_by_key 作为基准来测试它的性能,结果太好了,无法说服我的嘘声。排序512MB 对需要100+ms。
对于您的问题,我对两件事感到困惑。
(1) 为什么将 time_cpu 乘以 1000?没有1000,它已经在几秒钟内。
time_cpu=(double)(stop_cpu-start_cpu)/CLOCKS_PER_SEC*1000;
(2) 而且,提到 26、30、40,您是指秒还是毫秒? 'cudaEvent' 报告以 'ms' 而不是 's' 为单位的经过时间。
【讨论】:
以上是关于在我的机器上操作大向量时,CUDA 推力很慢的主要内容,如果未能解决你的问题,请参考以下文章