从OpenCL生成的PTX二进制文件中不必要的CVT指令

时间:2017-08-14 18:09:51

标签: performance opencl nvidia ptx

我编写了一个非常简单的OpenCL代码,并尝试在Tesla K40m GPU上执行它并测量GFlops。这是我写的代码:

__kernel void test(__global float *GIn, __global float *GOut, int M, int N, int P) {
    int X = get_global_id(0);
    // Just a private variable
    float temp = 1.0;


    // Start of a new level of for loop
    int baseIndex1 = (X) * 512;
    temp += GIn[baseIndex1 + 0] * var;
    temp += GIn[baseIndex1 + 1] * var;
    temp += GIn[baseIndex1 + 2] * var;
    temp += GIn[baseIndex1 + 3] * var;
    temp += GIn[baseIndex1 + 4] * var;
    temp += GIn[baseIndex1 + 5] * var;
    temp += GIn[baseIndex1 + 6] * var;
    temp += GIn[baseIndex1 + 7] * var;
    temp += GIn[baseIndex1 + 8] * var;
    temp += GIn[baseIndex1 + 9] * var;
    temp += GIn[baseIndex1 + 10] * var;
    ...
    temp += GIn[baseIndex1 + 510] * var;
    temp += GIn[baseIndex1 + 511] * var;
    GOut[baseIndex1] = temp;
}

我已经在我的GPU上部署了这个内核,其global_work_size为[1048576],local_work_size为[128]。它每秒可以执行的浮点运算总数约为1.6 GFlops,这是非常低的。我假设我只做单个操作,并且还按顺序读取内存。我决定查看生成的PTX代码:

.version 5.0
.target sm_35, texmode_independent
.address_size 64

    // .globl   test
.func  (.param .b64 func_retval0) get_global_id
(
    .param .b32 get_global_id_param_0
)
;

.entry test(
    .param .u64 .ptr .global .align 4 test_param_0,
    .param .u64 .ptr .global .align 4 test_param_1,
    .param .u32 test_param_2,
    .param .u32 test_param_3,
    .param .u32 test_param_4
)
{
    .reg .f32   %f<1537>;
    .reg .b32   %r<515>;
    .reg .b64   %rd<1543>;


    ld.param.u64    %rd1, [test_param_0];
    ld.param.u64    %rd2, [test_param_1];
    mov.u32     %r1, 0;
    // Callseq Start 0
    {
    .reg .b32 temp_param_reg;
    // <end>}
    .param .b32 param0;
    st.param.b32    [param0+0], %r1;
    .param .b64 retval0;
    call.uni (retval0), 
    get_global_id, 
    (
    param0
    );
    ld.param.b64    %rd3, [retval0+0];

    //{
    }// Callseq End 0
    cvt.u32.u64 %r2, %rd3;
    mul.lo.s32  %r3, %r2, 512;
    cvt.s64.s32 %rd4, %r3;
    shl.b64     %rd5, %rd4, 2;
    add.s64     %rd6, %rd1, %rd5;
    ld.global.f32   %f1, [%rd6];
    mul.f32     %f2, %f1, 0f3FC00000;
    add.f32     %f3, %f2, 0f3F800000;
    add.s32     %r4, %r3, 1;
    cvt.s64.s32 %rd7, %r4;
    shl.b64     %rd8, %rd7, 2;
    add.s64     %rd9, %rd1, %rd8;
    ld.global.f32   %f4, [%rd9];
    mul.f32     %f5, %f4, 0f3FC00000;
    add.f32     %f6, %f3, %f5;
    add.s32     %r5, %r3, 2;
    cvt.s64.s32 %rd10, %r5;
    shl.b64     %rd11, %rd10, 2;
    add.s64     %rd12, %rd1, %rd11;
    ld.global.f32   %f7, [%rd12];
    mul.f32     %f8, %f7, 0f3FC00000;
    add.f32     %f9, %f6, %f8;
    add.s32     %r6, %r3, 3;
    cvt.s64.s32 %rd13, %r6;
    shl.b64     %rd14, %rd13, 2;
    add.s64     %rd15, %rd1, %rd14;
    ld.global.f32   %f10, [%rd15];
    mul.f32     %f11, %f10, 0f3FC00000;
    add.f32     %f12, %f9, %f11;
    add.s32     %r7, %r3, 4;
    cvt.s64.s32 %rd16, %r7;
    shl.b64     %rd17, %rd16, 2;
    add.s64     %rd18, %rd1, %rd17;
    ld.global.f32   %f13, [%rd18];
    mul.f32     %f14, %f13, 0f3FC00000;
    add.f32     %f15, %f12, %f14;
    add.s32     %r8, %r3, 5;
    cvt.s64.s32 %rd19, %r8;
    shl.b64     %rd20, %rd19, 2;
    add.s64     %rd21, %rd1, %rd20;
    ld.global.f32   %f16, [%rd21];
    mul.f32     %f17, %f16, 0f3FC00000;
    add.f32     %f18, %f15, %f17;

由于它在代码中很清楚,我有不必要的 cvt shl 指令,我认为这是导致开销的一个潜在原因。

现在我有两个问题:(1)我应该如何重写内核以摆脱两条提到的指令并使内核执行得更快? (2)我的代码中是否还有其他任何开销来源,我不知道?

1 个答案:

答案 0 :(得分:1)

İfvar是double类型,可以转换指令源为float cant直接添加它。

使用相同的温度来添加所有东西是一个管道阻塞。

以512个浮点数的步幅访问数组,一次只能使用1个内存通道,甚至只能使用1个内存库。这可能会在每个线程已经序列化的指令之上序列化内存操作。

远程项目之间的减少,而不是邻居和每个线程只有Pairs或4个项目来解决内存问题。

对管道问题使用多个临时值。

如果浮点数不适用于双打,请将f后缀放入浮点数。尝试避免添加double并重复浮动。

每个线程使用不同的内存通道是好的。

让编译器/硬件重命名一些寄存器是好的。

在同一个寄存器中添加较少数量的值意味着减少舍入错误的概率大于增加值,这是好的。

移动似乎是浮动的地址计算为长度为4.向左移动2以获得adr。也许缓冲区没有对齐?计算基本索引加指针然后添加其他值而不是重新计算基数及其在每一行的添加,这使得速度变慢。在输入自动优化注意事项之前,gin参数可能需要restrict或const关键字。