CUDA中的复数/ cuComplex算法

时间:2013-07-08 17:04:45

标签: c++ cuda

我是CUDA的新手,想了解更多有关复数运算及其速度影响的信息。

我需要为'j []'数组中的所有元素求解以下复数等式,并将答案存储在'Ans []'中:

Ans [0] = (2.0/((20.5*(j[0]*j[0]))+(5.55*j[0])+20));
Ans [1] = (2.0/((20.5*(j[1]*j[1]))+(5.55*j[1])+20));
...
...
...
Ans [n] = (2.0/((20.5*(j[n]*j[n]))+(5.55*j[n])+20));

由于我需要对'j'的所有元素执行相同的计算,我可以并行化此代码并让每个线程/块处理每个计算(blockIdx.x = 0 - > Ans [0]等) 根据我的理解,如果我对很多元素并行执行此操作,我应该能够看到速度的提高。但是,可以在一行c ++代码中编写的内容在GPU中需要几行。

我的问题是,所有额外的代码行是否意味着更长的处理时间,因为它涉及在多个临时值中保存中间值。如果是这样,当元素数量小于1000时,在GPU中进行这种计算是否仍然有意义? (任意数字)

等式:

C++ -> Ans [0] = (2.0/((20.5*(j[0]*j[0]))+(5.55*j[0])+20));

我的GPU版本:

int tid = blockIdx.x;

    temp1[tid] = cuCmul(j[tid], j[tid]);
    temp2[tid] = cuCmul(temp1[tid], make_cuDoubleComplex(20.5, 0));
    temp3[tid] = cuCmul(j[tid], make_cuDoubleComplex(5.55, 0));
    temp4[tid] = cuCadd(temp2[tid], temp3[tid]);
    temp5[tid] = cuCadd(temp4[tid], make_cuDoubleComplex(20, 0));
    Ans[tid] = cuCdiv(make_cuDoubleComplex(2.0, 0), temp5[tid]);

另外,如果有更有效的方法为GPU写这个

,请告诉我

2 个答案:

答案 0 :(得分:2)

  

可以在一行c ++代码中编写的内容在GPU中需要几行。

这可能不是真的,至少对于你所展示的例子。您似乎担心临时存储,但编译器(主机和GPU)非常适合确定临时存储是否有意义,并优化它的内部或外部。在存储使用和操作顺序方面,许多程序员陷入了认为他们编写的C代码很好地表示机器将会做什么的陷阱,但是对于现代编译器,通常情况并非如此。

举个例子,你说这是你的CPU代码:

Ans [0] = (2.0/((20.5*(j[0]*j[0]))+(5.55*j[0])+20));

GPU版本可以写成:

Ans [0] = cuCdiv(make_cuDoubleComplex(2.0, 0), cuCadd(cuCadd(cuCmul(cuCmul(j[tid], j[tid]), make_cuDoubleComplex(20.5, 0)), cuCmul(j[tid], make_cuDoubleComplex(5.55, 0))), make_cuDoubleComplex(20, 0)));

不使用显式临时存储。 (然而,代码肯定难以阅读。)但是在主机(C)或设备(GPU)情况下“幕后”的内容可能看起来不同。在计算如何优化这样的一行或几行代码时,编译器通常比程序员更好。

让代码先运行。然后基准(时间)它。然后决定是否要仔细研究优化。像visual profiler这样的工具可以帮助您发现优化机会。

即使您的主机C代码看起来很简单,请记住复数仍然有2个与之关联的数量。即使看到(抽象的)C代码并不明显,“引擎盖下”编译器仍在进行必要的操作,分别处理数字,以适应+, - ,*,/的各种操作。 p>

  

我的问题是,所有额外的代码行是否意味着更长的处理时间,因为它涉及在多个临时值中保存中间值。

不一定,由于我上面描述的原因。你在任何一个实现中都做了大部分相同的工作,编译器会观察到这个并且可能生成类似的机器代码。

  

如果是这样,当元素数量少于1000时,在GPU中进行这种计算是否仍然有意义? (任意数字)

如果您计算的答案总数大约是1000,那么现代GPU的问题“非常小”。现代GPU可能有8个(或更多)SM,每个SM能够同时运行1到3个warp(32个线程),并且机器还需要相当稳定的warp“准备运行”以便保留所有管道(内存) ,计算等)忙。 1000个线程可能是实现GPU良好利用率的最低要求。显然,这很大程度上取决于您将运行哪些GPU或GPU。例如,笔记本电脑中的小型低端GPU可能能够以更小的问题实现高利用率。但是如果计算的范围是你在这里显示的类型的1000,我无法想象在CPU(主机代码)上花费很多时间。

答案 1 :(得分:2)

CUDA使用C ++的子集。其中一个受支持的功能是重载运算符。

__device__ __host__ cuDoubleComplex  operator*(cuDoubleComplex a, cuDoubleComplex b) { return cuCmul(a,b); }
__device__ __host__ cuDoubleComplex  operator+(cuDoubleComplex a, cuDoubleComplex b) { return cuCadd(a,b); }
__device__ __host__ cuDoubleComplex  operator/(cuDoubleComplex a, cuDoubleComplex b) { return cuCdiv(a,b); }

当其中一个输入是double而不是cuDoubleComplex时,您可以类似地重载运算符。

如果您没有在其他内核中使用相同的操作,最好继续执行您正在执行的操作。但是,如果您正在处理需要在其他内核中继续使用类似操作的大型项目,则最好有一个包含所有这些重载操作符的头文件。

  

我的问题是,所有额外的代码行是否意味着更长时间   处理时间,因为它涉及保存众多中间值   临时工。如果是这样,那么进行这种计算是否仍然有意义   在GPU中,当元素数量少于1000时?   (任意数字)

编译器通常应为相同的操作生成相同数量的临时变量,而不管代码行。加速来自GPU完成的并行操作的数量。在大约1000个元素上,单线程主机端实现应该能够击败执行这些操作的CUDA内核。将数据从主机复制到设备,启动内核,读取和写入全局内存等都会产生开销。

支持CUDA的GPU通常能够一次运行数千个线程。并且每个线程应具有相对较高的计算带宽比,以便最佳地使用GPU。