【发布时间】:2021-07-18 20:33:27
【问题描述】:
我的问题是关于获得一些相同长度数组的“总和”。例如,我总共有一个 M*N(100 * 2000) 长度的浮点数组。我想获得每个 N(2000) 浮点数的 M(100) 和值。我找到了两种方法来完成这项工作。一种是在 M 的 for 循环中使用 Cublas 函数,例如 cublasSasum。另一种是自写的核函数,循环加数。我的问题是这两种方式的速度以及如何在它们之间进行选择。
对于Cublas方法,无论N(4000~2E6)有多大,耗时主要取决于循环数M。
对于自写的犬舍功能,速度随N变化很大。具体来说,如果N很小,在5000以下,它比Cublas方式运行得快得多。则时间消耗随着N的增加而增加。
N = 4000 |10000 | 40000 | 80000 | 1E6 | 2E6
t = 254ms| 422ms | 1365毫秒| 4361ms| 5399 毫秒 | 10635毫秒
如果 N 足够大,它的运行速度会比 Cublas 方式慢得多。我的问题是我怎么能用 M 或 N 来决定我应该使用哪种方式?我的代码可能用于不同的 GPU 设备。我必须在扫描的参数中比较速度,然后“猜测”以在每个 GPU 设备中做出选择,还是可以从 GPU 设备信息中推断?
另外,对于核函数方式,我在决定blockSize 和gridSize 时也有问题。我必须在这里指出,我更关心的是速度而不是效率。因为内存有限。例如,如果我有 8G 内存。我的数据格式是 4 个字节的浮点数。 N是1E5。那么M最多为2E4,小于MaxGridSize。所以我有两种方法如下。我发现有一个更大的 gridSize 总是更好,我不知道原因。是关于每个线程的寄存器号的使用吗?但我认为在这个内核函数中每个线程不需要很多寄存器。
任何建议或信息将不胜感激。谢谢。
库布拉斯方式
for (int j = 0;j<M;j++)
cublasStatus = cublasSasum(cublasHandle,N,d_in+N*j,1,d_out+j);
自写内核方式
__global__ void getSum(int M, int N, float* in, float * out)
{
int i = threadIdx.x + blockIdx.x * blockDim.x;
if(i<M){
float tmp = 0;
for(int ii = 0; ii<N; ii++){
tmp += *(in+N*i+ii);
}
out[i] = tmp;
}
}
更大的 gridSize 更快。不知道是什么原因。
getSum<<<M,1>>>(M, N, d_in, d_out); //faster
getSum<<<1,M>>>(M, N, d_in, d_out);
这是一个blockSize-time参数扫描结果。 M = 1E4.N = 1E5。
cudaEventRecord(start, 0);
//blockSize = 1:1024;
int gridSize = (M + blockSize - 1) / blockSize;
getSum<<<gridSize1,blockSize1>>>...
cudaEventRecord(stop, 0);
cudaEventSynchronize(stop);
cudaEventElapsedTime(&time, start, stop);
看来我应该选择一个相对较小的blockSize,比如10~200。我只是想知道为什么完全占用(blockSize 1024)比较慢。我只是出于一些可能的原因在这里发帖,注册号码?延迟?
【问题讨论】:
-
如果要求和的值在内存中是连续的,您可以尝试 Thrusts
reduce_by_key(请参阅 here)。它将在一次内核调用中完成操作,而不是M,并且还会进行一些优化。对于理想的 Thrust 性能,您可能不会为键创建自己的数组,而是使用一些 Thrusts “花哨的迭代器”来创建您的键序列,这只是每个值的行号或列号。我想象transform_iterator采用counting_iterator并将其除以列/行大小。 -
此选项仅在您被允许使用 C++ 时可用。
-
实际上有一个官方的 Thrust 示例完全按照我的描述 here
-
而this 的答案展示了如何将 Thrust 与原始 CUDA 指针一起使用
-
@PaulG 很抱歉我无法使用
thrust,因为我使用的是 Matlab mex。它现在不支持thrust。感谢您的意见。我可以用thrust以exe 格式编写代码。
标签: arrays cuda sum kernel cublas