在我的机器上操作大向量时,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*ic[i]=i*i 更改为 Y[i]=ic[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 推力很慢的主要内容,如果未能解决你的问题,请参考以下文章

您如何构建示例 CUDA 推力设备排序?

推力::设备向量使用推力::替换或推力::转换与自定义函子/谓词

在 CUDA 中混合自定义内存管理和推力

git 在我的 vagrant centos 机器上很慢

django 在我的机器上很慢

cuda 推力::for_each 与推力::counting_iterator