关于cuda:为什么小输入的cpu比gpu快?

Why is the cpu faster than the gpu for small inputs?

我已经体验到在输入量较小的情况下,CPU的执行速度要比GPU快。为什么是这样?准备,数据传输还是什么?

例如内核和CPU功能(CUDA代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
__global__ void squareGPU(float* d_in, float* d_out, unsigned int N) {
    unsigned int lid = threadIdx.x;
    unsigned int gid = blockIdx.x*blockDim.x+lid;
    if(gid < N) {
        d_out[gid] = d_in[gid]*d_in[gid];
    }
}

void squareCPU(float* d_in, float* d_out, unsigned int N) {
    for(unsigned int i = 0; i < N; i++) {
        d_out[i] = d_in[i]*d_in[i];
    }
}

在5000个32位浮点数组上运行这些功能100次,我使用一个小的测试程序得到了以下内容

1
2
3
4
5
6
7
8
9
Size of array:
5000
Block size:
256

You chose N=5000 and block size: 256

Total time for GPU: 403 microseconds (0.40ms)
Total time for CPU: 137 microseconds (0.14ms)

将数组的大小增加到1000000,我得到:

1
2
3
4
5
6
7
8
9
Size of array:
1000000
Block size:
256

You chose N=1000000 and block size: 256

Total time for GPU: 1777 microseconds (1.78ms)
Total time for CPU: 48339 microseconds (48.34ms)

我不包括用于在主机和设备之间传输数据的时间(反之亦然),实际上,这是我的测试过程的相关部分:

1
2
3
4
5
6
7
gettimeofday(&t_start, NULL);

for(int i = 0; i < 100; i++) {
    squareGPU<<< num_blocks, block_size>>>(d_in, d_out, N);
} cudaDeviceSynchronize();

gettimeofday(&t_end, NULL);

选择一个块大小后,我计算相对于数组大小的块数:unsigned int num_blocks = ((array_size + (block_size-1)) / block_size);


回答CPU与GPU性能比较的一般问题相当复杂,通常需要考虑至少3或4个我可以想到的不同因素。但是,通过将测量结果与实际计算隔离开来,已经稍微简化了问题,而不是进行数据传输或"完整操作"。

在这种情况下,可能至少要考虑两件事:

  • 内核启动开销-在每次GPU启动时,在GPU上启动内核会带来"大约"固定成本开销,通常为5到50微秒。这意味着,如果您调整工作量以使CPU可以在少于该时间的时间内完成工作,则GPU不可能更快。即使在该级别之上,也存在一个描述该开销模型的线性函数,如果有固定开销,我相信您可以确定是否可以比较CPU和GPU的性能。在比较小型测试用例时,这是要考虑的重要因素,但是我的猜测是,由于大多数测试用例的计时都远高于50微秒,因此我们可以安全地"忽略"该因素,作为一个近似值。

  • 实际CPU与实际GPU的实际性能/能力。通常很难建模,具体取决于您使用的特定硬件,而您尚未提供该信息。但是,无论如何,我们可以根据您提供的数据在下一节中对此进行一些观察和一些推测。

  • 考虑到N=5000N=1000000,您的两个案例涉及N描述的全部工作。建立一个小图表:

    1
    2
    3
          N  |  CPU time    |  GPU time
       5000  |    137       |  403
    1000000  |  48339       | 1777

    因此我们看到,在CPU的情况下,当工作增加200倍时,执行时间增加了?352倍,而在GPU的情况下,执行时间增加了?倍。 4.5。我们需要解释这两种"非线性",以便对正在发生的事情有一个合理的猜测。

  • 缓存的效果-因为您要运行100次测试用例,所以缓存可能会产生影响。在CPU情况下,这是我为什么不看到线性关系的唯一猜测。我猜想在很小的情况下,您位于某些CPU"内部"缓存中,"查看"中具有40KB的数据。转到更大的大小,您可以看到8MB的数据,尽管这可能适合CPU上的"外部"缓存,但可能不会,即使这样做,外部缓存的整体性能也可能比内部缓存。我猜这是CPU随着数据变大而变得越来越差的原因。较大的数据集对您的CPU产生了非线性的负面影响。在GPU的情况下,外部缓存最多为6MB(除非您在Ampere GPU上运行),因此较大的数据集无法完全容纳在外部缓存中。

  • 计算机饱和的影响-根据工作负载,CPU和GPU都可以完全"加载"或部分加载。在CPU的情况下,我猜您没有使用任何多线程,因此您的CPU代码仅限于单个内核。 (并且,您的CPU几乎肯定有多个可用的内核。)您的单线程代码将大致"饱和",即保持该单内核"繁忙"。但是GPU有许多内核,我想您较小的测试用例(最多可以处理5000个线程)只会部分饱和GPU。我的意思是,在较小的情况下,某些GPU线程处理资源将处于空闲状态(除非您碰巧在最小的GPU上运行)。 5000个线程仅足以使2个GPU SM保持繁忙,因此,如果您的GPU具有2个以上的SM,则在较小的测试用例中其某些资源处于空闲状态,而在百万线程的较大测试用例中足以饱和,即保持所有当前任何CUDA GPU上的线程处理资源繁忙。这样的效果是,虽然CPU根本无法从较大的测试案例中受益(您应该考虑使用多线程),但您的GPU可能会受益。较大的测试用例可使您的GPU在与较小的测试用例相同的时间内完成更多工作。因此,GPU从较大的工作量中以积极的方式非线性地受益。

  • GPU被赋予足够大的工作量时,也可以更好地减轻外部缓存丢失的影响。在存在"大量"并行工作负载的情况下,这称为GPU的延迟隐藏效果,而CPU没有(或没有那么多)相应的机制。因此,这可能取决于其他因素,具体取决于您的确切CPU和GPU。我不打算在此处提供有关延迟隐藏的完整教程,但是该概念部分基于上面的第2项,因此您可以从中收集总体思想/好处。