根据分析器,关键部分的原子计数器未使用原子带宽

时间:2018-11-15 18:54:03

标签: cuda atomic

即使对共享变量有第一线程的每次线程原子访问,探查器也显示原子的零带宽:

enter image description here

我可以在这里做最小复制的例子:

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

#define criticalSection(T, ...) {\
    __shared__ int ctrBlock; \
    if(threadIdx.x==0) \
       ctrBlock=0; \
    __syncthreads(); \
    while(atomicAdd(&ctrBlock,0)<(blockDim.x/32)) \
    { \
       if( atomicAdd(&ctrBlock,0) == (threadIdx.x/32) ) \
       { \
            int ctr=0; \
            while(ctr<32) \
            { \
                   if( ctr == (threadIdx.x&31) ) \
                   { \
                    { \
                          T,##__VA_ARGS__; \
                    } \
                   } \
                   ctr++; \
                   __syncwarp(); \
            } \
            if((threadIdx.x&31) == 0)atomicAdd(&ctrBlock,1); \
        } \
        __syncthreads(); \
     } \
}

__global__ void
vectorAdd(const float *A, const float *B, float *C, int numElements)
{
    int i = blockDim.x * blockIdx.x + threadIdx.x;

     // instead of if(i==0) C[0]=0.0f; initialization
    if(i==blockDim.x*blockIdx.x)
       C[blockDim.x*blockIdx.x]=0.0f;

    __syncthreads();
    criticalSection({
        if (i < numElements)
        {
           C[blockDim.x*blockIdx.x] += A[i] + B[i];
        }
    });
}


int main(void)
{
    int numElements = 50000;
    size_t size = numElements * sizeof(float);
    float *h_A = (float *)malloc(size); 
    float *h_B = (float *)malloc(size);
    float *h_C = (float *)malloc(size);

    for (int i = 0; i < numElements; ++i)
    {
        h_A[i] = i;
        h_B[i] = 2*i;
    }

    float *d_A = NULL;
    cudaMalloc((void **)&d_A, size);

    float *d_B = NULL;
    cudaMalloc((void **)&d_B, size);

    float *d_C = NULL;
    cudaMalloc((void **)&d_C, size);

    cudaMemcpy(d_A, h_A, size, cudaMemcpyHostToDevice);
    cudaMemcpy(d_B, h_B, size, cudaMemcpyHostToDevice);
    int threadsPerBlock = 256;
    int blocksPerGrid =(numElements + threadsPerBlock - 1) / threadsPerBlock;
    vectorAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, numElements);
    cudaMemcpy(h_C, d_C, size, cudaMemcpyDeviceToHost);

    printf("%g\n",h_C[0]);


    cudaFree(d_A);
    cudaFree(d_B);
    cudaFree(d_C);

    free(h_A);
    free(h_B);
    free(h_C);

    return 0;
}

每次运行时,它都会正确输出(1到255)* 3结果之和(在每个块的每个起始元素处)。

问题:为什么探查器会显示它即使正常工作也未使用原子带宽?

在192核Kepler GPU上,内核在2.4毫秒内完成(196个块,每个块256个线程)。 GPU是否在每个同步点收集原子并将其转换为效率更高的东西?

它没有给出任何错误,我删除了错误检查以提高可读性。

更改C数组元素的添加范围:

((volatile float *) C)[blockDim.x*blockIdx.x] += A[i] + B[i];

既不改变行为也不改变结果。

使用CUDA工具包9.2和驱动程序v396,Ubuntu 16.04,Quadro K420。

以下是编译命令:

nvcc -ccbin g++ -m64 -gencode arch=compute_30,code=sm_30 -gencode arch=compute_35,code=sm_35 -gencode arch=compute_37,code=sm_37 -gencode arch=compute_50,code=sm_50 -gencode arch=compute_52,code=sm_52 -gencode arch=compute_60,code=sm_60 -gencode arch=compute_61,code=sm_61 -gencode arch=compute_70,code=sm_70 -gencode arch=compute_70,code=compute_70 -o vectorAdd.o -c vectorAdd.cu
nvcc -ccbin g++ -m64 -gencode arch=compute_30,code=sm_30 -gencode arch=compute_35,code=sm_35 -gencode arch=compute_37,code=sm_37 -gencode arch=compute_50,code=sm_50 -gencode arch=compute_52,code=sm_52 -gencode arch=compute_60,code=sm_60 -gencode arch=compute_61,code=sm_61 -gencode arch=compute_70,code=sm_70 -gencode arch=compute_70,code=compute_70 -o vectorAdd vectorAdd.o

cuobjdump的Ptx输出(sass超过50k个字符):

.visible .entry _Z9vectorAddPKfS0_Pfi(
.param .u64 _Z9vectorAddPKfS0_Pfi_param_0,
.param .u64 _Z9vectorAddPKfS0_Pfi_param_1,
.param .u64 _Z9vectorAddPKfS0_Pfi_param_2,
.param .u32 _Z9vectorAddPKfS0_Pfi_param_3
)
{
.reg .pred %p<32>;
.reg .f32 %f<41>;
.reg .b32 %r<35>;
.reg .b64 %rd<12>;

    .shared .align 4 .u32 _ZZ9vectorAddPKfS0_PfiE8ctrBlock;

ld.param.u64 %rd5, [_Z9vectorAddPKfS0_Pfi_param_0];
ld.param.u64 %rd6, [_Z9vectorAddPKfS0_Pfi_param_1];
ld.param.u64 %rd7, [_Z9vectorAddPKfS0_Pfi_param_2];
ld.param.u32 %r13, [_Z9vectorAddPKfS0_Pfi_param_3];
cvta.to.global.u64 %rd1, %rd7;
mov.u32 %r14, %ctaid.x;
mov.u32 %r1, %ntid.x;
mul.lo.s32 %r2, %r14, %r1;
mov.u32 %r3, %tid.x;
add.s32 %r4, %r2, %r3;
setp.ne.s32 %p8, %r4, 0;
@%p8 bra BB0_2;

mov.u32 %r15, 0;
st.global.u32 [%rd1], %r15;

BB0_2:
bar.sync 0;
setp.ne.s32 %p9, %r3, 0;
@%p9 bra BB0_4;

mov.u32 %r16, 0;
st.shared.u32 [_ZZ9vectorAddPKfS0_PfiE8ctrBlock], %r16;

BB0_4:
bar.sync 0;
mov.u32 %r17, _ZZ9vectorAddPKfS0_PfiE8ctrBlock;
atom.shared.add.u32 %r18, [%r17], 0;
shr.u32 %r5, %r1, 5;
setp.ge.u32 %p10, %r18, %r5;
@%p10 bra BB0_27;

shr.u32 %r6, %r3, 5;
and.b32 %r7, %r3, 31;
cvta.to.global.u64 %rd8, %rd5;
mul.wide.s32 %rd9, %r4, 4;
add.s64 %rd2, %rd8, %rd9;
cvta.to.global.u64 %rd10, %rd6;
add.s64 %rd3, %rd10, %rd9;
mul.wide.u32 %rd11, %r2, 4;
add.s64 %rd4, %rd1, %rd11;
neg.s32 %r8, %r7;

BB0_6:
atom.shared.add.u32 %r21, [%r17], 0;
mov.u32 %r34, 0;
setp.ne.s32 %p11, %r21, %r6;
mov.u32 %r33, %r8;
@%p11 bra BB0_26;

BB0_7:
setp.eq.s32 %p12, %r33, 0;
setp.lt.s32 %p13, %r4, %r13;
and.pred %p14, %p12, %p13;
@!%p14 bra BB0_9;
bra.uni BB0_8;

BB0_8:
ld.global.f32 %f1, [%rd2];
ld.global.f32 %f2, [%rd3];
add.f32 %f3, %f1, %f2;
ld.volatile.global.f32 %f4, [%rd4];
add.f32 %f5, %f4, %f3;
st.volatile.global.f32 [%rd4], %f5;

BB0_9:
bar.warp.sync -1;
add.s32 %r22, %r34, 1;
setp.eq.s32 %p15, %r22, %r7;
and.pred %p16, %p15, %p13;
@!%p16 bra BB0_11;
bra.uni BB0_10;

BB0_10:
ld.global.f32 %f6, [%rd2];
ld.global.f32 %f7, [%rd3];
add.f32 %f8, %f6, %f7;
ld.volatile.global.f32 %f9, [%rd4];
add.f32 %f10, %f9, %f8;
st.volatile.global.f32 [%rd4], %f10;

BB0_11:
bar.warp.sync -1;
add.s32 %r23, %r34, 2;
setp.eq.s32 %p17, %r23, %r7;
and.pred %p18, %p17, %p13;
@!%p18 bra BB0_13;
bra.uni BB0_12;

BB0_12:
ld.global.f32 %f11, [%rd2];
ld.global.f32 %f12, [%rd3];
add.f32 %f13, %f11, %f12;
ld.volatile.global.f32 %f14, [%rd4];
add.f32 %f15, %f14, %f13;
st.volatile.global.f32 [%rd4], %f15;

BB0_13:
bar.warp.sync -1;
add.s32 %r24, %r34, 3;
setp.eq.s32 %p19, %r24, %r7;
and.pred %p20, %p19, %p13;
@!%p20 bra BB0_15;
bra.uni BB0_14;

BB0_14:
ld.global.f32 %f16, [%rd2];
ld.global.f32 %f17, [%rd3];
add.f32 %f18, %f16, %f17;
ld.volatile.global.f32 %f19, [%rd4];
add.f32 %f20, %f19, %f18;
st.volatile.global.f32 [%rd4], %f20;

BB0_15:
bar.warp.sync -1;
add.s32 %r25, %r34, 4;
setp.eq.s32 %p21, %r25, %r7;
and.pred %p22, %p21, %p13;
@!%p22 bra BB0_17;
bra.uni BB0_16;

BB0_16:
ld.global.f32 %f21, [%rd2];
ld.global.f32 %f22, [%rd3];
add.f32 %f23, %f21, %f22;
ld.volatile.global.f32 %f24, [%rd4];
add.f32 %f25, %f24, %f23;
st.volatile.global.f32 [%rd4], %f25;

BB0_17:
bar.warp.sync -1;
add.s32 %r26, %r34, 5;
setp.eq.s32 %p23, %r26, %r7;
and.pred %p24, %p23, %p13;
@!%p24 bra BB0_19;
bra.uni BB0_18;

BB0_18:
ld.global.f32 %f26, [%rd2];
ld.global.f32 %f27, [%rd3];
add.f32 %f28, %f26, %f27;
ld.volatile.global.f32 %f29, [%rd4];
add.f32 %f30, %f29, %f28;
st.volatile.global.f32 [%rd4], %f30;

BB0_19:
bar.warp.sync -1;
add.s32 %r27, %r34, 6;
setp.eq.s32 %p25, %r27, %r7;
and.pred %p26, %p25, %p13;
@!%p26 bra BB0_21;
bra.uni BB0_20;

BB0_20:
ld.global.f32 %f31, [%rd2];
ld.global.f32 %f32, [%rd3];
add.f32 %f33, %f31, %f32;
ld.volatile.global.f32 %f34, [%rd4];
add.f32 %f35, %f34, %f33;
st.volatile.global.f32 [%rd4], %f35;

BB0_21:
bar.warp.sync -1;
add.s32 %r28, %r34, 7;
setp.eq.s32 %p27, %r28, %r7;
and.pred %p28, %p27, %p13;
@!%p28 bra BB0_23;
bra.uni BB0_22;

BB0_22:
ld.global.f32 %f36, [%rd2];
ld.global.f32 %f37, [%rd3];
add.f32 %f38, %f36, %f37;
ld.volatile.global.f32 %f39, [%rd4];
add.f32 %f40, %f39, %f38;
st.volatile.global.f32 [%rd4], %f40;

BB0_23:
add.s32 %r34, %r34, 8;
bar.warp.sync -1;
add.s32 %r33, %r33, 8;
setp.ne.s32 %p29, %r34, 32;
@%p29 bra BB0_7;

setp.ne.s32 %p30, %r7, 0;
@%p30 bra BB0_26;

atom.shared.add.u32 %r30, [%r17], 1;

BB0_26:
bar.sync 0;
atom.shared.add.u32 %r32, [%r17], 0;
setp.lt.u32 %p31, %r32, %r5;
@%p31 bra BB0_6;

BB0_27:
ret;
}

1 个答案:

答案 0 :(得分:1)

在这里至少要注意两件事。

  1. 让我们观察一下您的程序正在共享内存位置上使用原子。另外,您还表示您正在为Kepler架构GPU进行编译(并在进行性能分析时运行)。

    在开普勒上,共享内存原子为emulated via a software sequence。在检查PTX代码时,这是不可见的,因为通过ptxas完成了到仿真序列的转换,$ nvcc -o t324 t324.cu -arch=sm_30 $ cuobjdump -sass ./t324 |grep ATOM $ nvcc -o t324 t324.cu -arch=sm_50 $ cuobjdump -sass ./t324 |grep ATOM /*00e8*/ @P2 ATOMS.ADD R6, [RZ], RZ ; /* 0xec0000000ff2ff06 */ /*01b8*/ @P0 ATOMS.ADD R12, [RZ], RZ ; /* 0xec0000000ff0ff0c */ /*10f8*/ @P0 ATOMS.ADD RZ, [RZ], R12 ; /* 0xec00000000c0ffff */ /*1138*/ @P0 ATOMS.ADD R10, [RZ], RZ ; /* 0xec0000000ff0ff0a */ $ 是将PTX转换为SASS代码以在目标设备上执行的工具。

    由于您以开普勒为目标并在其上运行,因此SASS不包含共享内存原子指令(相反,共享原子是通过使用特殊硬件锁的循环进行仿真的,例如,您可以看到LDSLK,即从共享加载-with-lock指令,在您的SASS代码中)。

    由于您的代码没有实际的原子指令(在Kepler上),因此它不会生成可被探查器跟踪的任何原子流量。

    如果要验证这一点,请在已编译的二进制文件上使用cuobjdump tool。对于您将实际用于这种二进制分析的开普勒目标体系结构,我建议仅编译。这是一个示例:

    ATOMS
  2. 如上所述,在Maxwell及更高版本上,SASS代码中有一个本机共享内存原子指令(例如inst_executed_shared_atomics Warp level shared instructions for atom and atom CAS Multi-context )。因此,如果您为Maxwell架构或更高版本编译代码,那么您将在SASS中看到实际的原子指令。

    但是,我不确定在可视化探查器中是否或如何表示。我怀疑共享的原子报告可能会受到限制。通过查看可用的metrics并观察到,对于5.0及更高版本的体系结构,大多数原子量度都是专门针对全局原子的,而我能找到的与共享原子有关的唯一量度是:

    int i = blockDim.x * blockIdx.x + threadIdx.x;
    
    if(i==0)
       C[0]=0.0f;
    __syncthreads();
    

    我不确定这是否足以计算带宽或利用率,所以我不确定视觉分析器是否打算以共享原子使用的方式进行大量报告,即使在5.0+的体系结构上也是如此。当然,欢迎您尝试一下。

顺便说一句,我通常会认为这种结构暗示了代码中的逻辑缺陷:

df = spark.read.format("csv") \
                .option("header", "false") \
                .load(path + "*.csv")
                .toDF('header_1')
                .withColumn("pipeline", lit(path))

但这与此特定查询无关,无论如何,我不确定您的代码的意图。请记住,CUDA没有指定块执行的顺序。