CUDA,同样工作的更多线程=尽管占用率更高,但运行时间更长,为什么?

时间:2010-03-15 18:11:20

标签: performance cuda

我遇到了一个奇怪的问题,即通过增加线程数来增加我的占用率会降低性能。

我创建了以下程序来说明问题:

#include <stdio.h>
#include <stdlib.h>
#include <cuda_runtime.h>
#include <cutil.h>

__global__ void less_threads(float * d_out) {
    int num_inliers;
    for (int j=0;j<800;++j) {
        //Do 12 computations
        num_inliers += j*(j+1);
        num_inliers += j*(j+2);
        num_inliers += j*(j+3);
        num_inliers += j*(j+4);
        num_inliers += j*(j+5);
        num_inliers += j*(j+6);
        num_inliers += j*(j+7);
        num_inliers += j*(j+8);
        num_inliers += j*(j+9);
        num_inliers += j*(j+10);
        num_inliers += j*(j+11);
        num_inliers += j*(j+12);
    }

    if (threadIdx.x == -1)
        d_out[threadIdx.x] = num_inliers;
}

__global__ void more_threads(float *d_out) {
    int num_inliers;
    for (int j=0;j<800;++j) {
        // Do 4 computations
        num_inliers += j*(j+1);
        num_inliers += j*(j+2);
        num_inliers += j*(j+3);
        num_inliers += j*(j+4);
    }
    if (threadIdx.x == -1)
        d_out[threadIdx.x] = num_inliers;
}


int main(int argc, char* argv[])
{
    float *d_out = NULL;
    cudaMalloc((void**)&d_out,sizeof(float)*25000);

    more_threads<<<780,128>>>(d_out);
    less_threads<<<780,32>>>(d_out);


    return 0;
}

PTX输出为:

    .entry _Z12less_threadsPf (
        .param .u32 __cudaparm__Z12less_threadsPf_d_out)
    {
    .reg .u32 %r<35>;
    .reg .f32 %f<3>;
    .reg .pred %p<4>;
    .loc    17  6   0
 //   2  #include <stdlib.h>
 //   3  #include <cuda_runtime.h>
 //   4  #include <cutil.h>
 //   5  
 //   6  __global__ void less_threads(float * d_out) {
$LBB1__Z12less_threadsPf:
    mov.s32     %r1, 0;
    mov.s32     %r2, 0;
    mov.s32     %r3, 0;
    mov.s32     %r4, 0;
    mov.s32     %r5, 0;
    mov.s32     %r6, 0;
    mov.s32     %r7, 0;
    mov.s32     %r8, 0;
    mov.s32     %r9, 0;
    mov.s32     %r10, 0;
    mov.s32     %r11, 0;
    mov.s32     %r12, %r13;
    mov.s32     %r14, 0;
$Lt_0_2562:
 //<loop> Loop body line 6, nesting depth: 1, iterations: 800
    .loc    17  10  0
 //   7     int num_inliers;
 //   8     for (int j=0;j<800;++j) {
 //   9         //Do 12 computations
 //  10         num_inliers += j*(j+1);
    mul.lo.s32  %r15, %r14, %r14;
    add.s32     %r16, %r12, %r14;
    add.s32     %r12, %r15, %r16;
    .loc    17  11  0
 //  11         num_inliers += j*(j+2);
    add.s32     %r17, %r15, %r12;
    add.s32     %r12, %r1, %r17;
    .loc    17  12  0
 //  12         num_inliers += j*(j+3);
    add.s32     %r18, %r15, %r12;
    add.s32     %r12, %r2, %r18;
    .loc    17  13  0
 //  13         num_inliers += j*(j+4);
    add.s32     %r19, %r15, %r12;
    add.s32     %r12, %r3, %r19;
    .loc    17  14  0
 //  14         num_inliers += j*(j+5);
    add.s32     %r20, %r15, %r12;
    add.s32     %r12, %r4, %r20;
    .loc    17  15  0
 //  15         num_inliers += j*(j+6);
    add.s32     %r21, %r15, %r12;
    add.s32     %r12, %r5, %r21;
    .loc    17  16  0
 //  16         num_inliers += j*(j+7);
    add.s32     %r22, %r15, %r12;
    add.s32     %r12, %r6, %r22;
    .loc    17  17  0
 //  17         num_inliers += j*(j+8);
    add.s32     %r23, %r15, %r12;
    add.s32     %r12, %r7, %r23;
    .loc    17  18  0
 //  18         num_inliers += j*(j+9);
    add.s32     %r24, %r15, %r12;
    add.s32     %r12, %r8, %r24;
    .loc    17  19  0
 //  19         num_inliers += j*(j+10);
    add.s32     %r25, %r15, %r12;
    add.s32     %r12, %r9, %r25;
    .loc    17  20  0
 //  20         num_inliers += j*(j+11);
    add.s32     %r26, %r15, %r12;
    add.s32     %r12, %r10, %r26;
    .loc    17  21  0
 //  21         num_inliers += j*(j+12);
    add.s32     %r27, %r15, %r12;
    add.s32     %r12, %r11, %r27;
    add.s32     %r14, %r14, 1;
    add.s32     %r11, %r11, 12;
    add.s32     %r10, %r10, 11;
    add.s32     %r9, %r9, 10;
    add.s32     %r8, %r8, 9;
    add.s32     %r7, %r7, 8;
    add.s32     %r6, %r6, 7;
    add.s32     %r5, %r5, 6;
    add.s32     %r4, %r4, 5;
    add.s32     %r3, %r3, 4;
    add.s32     %r2, %r2, 3;
    add.s32     %r1, %r1, 2;
    mov.u32     %r28, 1600;
    setp.ne.s32     %p1, %r1, %r28;
    @%p1 bra    $Lt_0_2562;
    cvt.u32.u16     %r29, %tid.x;
    mov.u32     %r30, -1;
    setp.ne.u32     %p2, %r29, %r30;
    @%p2 bra    $Lt_0_3074;
    .loc    17  25  0
 //  22     }
 //  23  
 //  24     if (threadIdx.x == -1)
 //  25         d_out[threadIdx.x] = num_inliers;
    cvt.rn.f32.s32  %f1, %r12;
    ld.param.u32    %r31, [__cudaparm__Z12less_threadsPf_d_out];
    mul24.lo.u32    %r32, %r29, 4;
    add.u32     %r33, %r31, %r32;
    st.global.f32   [%r33+0], %f1;
$Lt_0_3074:
    .loc    17  26  0
 //  26  }
    exit;
$LDWend__Z12less_threadsPf:
    } // _Z12less_threadsPf

    .entry _Z12more_threadsPf (
        .param .u32 __cudaparm__Z12more_threadsPf_d_out)
    {
    .reg .u32 %r<19>;
    .reg .f32 %f<3>;
    .reg .pred %p<4>;
    .loc    17  28  0
 //  27  
 //  28  __global__ void more_threads(float *d_out) {
$LBB1__Z12more_threadsPf:
    mov.s32     %r1, 0;
    mov.s32     %r2, 0;
    mov.s32     %r3, 0;
    mov.s32     %r4, %r5;
    mov.s32     %r6, 0;
$Lt_1_2562:
 //<loop> Loop body line 28, nesting depth: 1, iterations: 800
    .loc    17  32  0
 //  29     int num_inliers;
 //  30     for (int j=0;j<800;++j) {
 //  31         // Do 4 computations
 //  32         num_inliers += j*(j+1);
    mul.lo.s32  %r7, %r6, %r6;
    add.s32     %r8, %r4, %r6;
    add.s32     %r4, %r7, %r8;
    .loc    17  33  0
 //  33         num_inliers += j*(j+2);
    add.s32     %r9, %r7, %r4;
    add.s32     %r4, %r1, %r9;
    .loc    17  34  0
 //  34         num_inliers += j*(j+3);
    add.s32     %r10, %r7, %r4;
    add.s32     %r4, %r2, %r10;
    .loc    17  35  0
 //  35         num_inliers += j*(j+4);
    add.s32     %r11, %r7, %r4;
    add.s32     %r4, %r3, %r11;
    add.s32     %r6, %r6, 1;
    add.s32     %r3, %r3, 4;
    add.s32     %r2, %r2, 3;
    add.s32     %r1, %r1, 2;
    mov.u32     %r12, 1600;
    setp.ne.s32     %p1, %r1, %r12;
    @%p1 bra    $Lt_1_2562;
    cvt.u32.u16     %r13, %tid.x;
    mov.u32     %r14, -1;
    setp.ne.u32     %p2, %r13, %r14;
    @%p2 bra    $Lt_1_3074;
    .loc    17  38  0
 //  36     }
 //  37     if (threadIdx.x == -1)
 //  38         d_out[threadIdx.x] = num_inliers;
    cvt.rn.f32.s32  %f1, %r4;
    ld.param.u32    %r15, [__cudaparm__Z12more_threadsPf_d_out];
    mul24.lo.u32    %r16, %r13, 4;
    add.u32     %r17, %r15, %r16;
    st.global.f32   [%r17+0], %f1;
$Lt_1_3074:
    .loc    17  39  0
 //  39  }
    exit;
$LDWend__Z12more_threadsPf:
    } // _Z12more_threadsPf

注意两个内核总共应该完成相同的工作量(如果threadIdx.x == -1是一个停止编译器优化所有内容并留下空内核的技巧)。工作应该是相同的,因为more_threads使用的线程数是4倍,但每个线程的工作量减少了4倍。

剖析器结果的重要结果如下:

more_threads:GPU运行时= 1474 us,每个线程注册= 6,占用率= 1,分支= 83746,divergent_branch = 26,指令= 584065,gst请求= 1084552

less_threads:GPU运行时= 921 us,每个线程的注册数= 14,占用率= 0.25,分支= 20956,divergent_branch = 26,指令= 312663,gst请求= 677381

正如我之前所说,使用更多线程的内核运行时间更长,这可能是由于指令数量的增加。

为什么还有更多说明?

考虑到没有条件代码,为什么有分支,更不用说分歧了?

当没有全局内存访问时,为什么有任何gst请求

这里发生了什么!

由于

更新

添加了PTX代码并修复了CUDA C,因此应该编译

3 个答案:

答案 0 :(得分:4)

由于您的代码只有算术指令,因此您不需要非常高的占用率来隐藏算术单元的延迟。实际上,即使您有记忆指令,只要您的读/写有效,您可以在占用率达到50%时实现最佳性能。有关占用率和性能的更多信息,请参阅录制的Advanced CUDA C演示文稿。

在您的情况下,假设您的内核不需要高占用率来使算术单元饱和,那么使用更少的块而不是更小的块将获得更好的性能,因为启动块有成本。但一般情况下,与实际运行代码的时间相比,启动块的成本可以忽略不计。

为什么还有更多说明?

请记住,计数器不计算每个块(也称为CTA),而是根据SM(流处理多处理器)或每个TPC(纹理处理群集)计算,TPC是一组两个或三个SM,具体取决于您的设备。指令计数是按SM。

期望less_threads内核具有更少的指令是公平的,但是每个块启动的warp是所需数量的四倍,这意味着每个SM将执行大约四倍的代码。考虑到较短的内核代码,您的测量似乎并不合理。

为什么有任何分支?

实际上你确实有条件代码:

for (int j=0;j<800;++j)

这有条件,但是warp中的所有线程确实执行相同的路径,因此它不会发散。我的猜测是在某个地方的管理代码中出现分歧,如果你担心的话,你可以看一下PTX代码来分析它。与执行的指令数量相比,26是非常低的,因此这不会影响您的表现。

为什么有gst请求?

在您的代码中,您有:

if (threadIdx.x == -1)
  d_out[blockIdx.x*blockDim.x+threadIdx.x] = num_inliers;

这将由加载/存储单元处理,因此即使它不会导致实际交易,也会计算。 gst_32 / gst_64 / gst_128计数器指示实际内存传输(您的设备具有计算能力1.2或1.3,旧设备具有不同的计数器集)。

答案 1 :(得分:4)

这两项功能的工作量不同。

more_threads<<<780, 128>>>():

  • 780块
  • 每个块128个线程
  • 每循环4 mul
  • 每个循环添加8个
  • 780 * 128 * 800 *(4 + 8)= 958,464,000 flops

less_threads<<<780, 32>>>():

  • 780块
  • 每块32个线程
  • 每循环12 mul
  • 每循环添加24个
  • 780 * 32 * 800 *(12 + 24)= 718,848,000人民币

因此,more_threads比更少的线程做更多的工作,这就是为什么指令数量增加以及为什么more_threads更慢的原因。要修复more_threads,在循环内只进行3次计算:780 * 128 * 800 *(3 + 6)= 718,848,000。

答案 2 :(得分:0)

  1. 两个函数具有不同数量的代码行,因此指令数不同

  2. for循环使用分支实现。最后一行代码总是不同的

  3. 全局商店请求与全局分数不同。操作已设置,但从未提交。