分析CUDA代码:合并内存读取的意外指令计数
我正在为小输入数据(=512个元素)分析一个非常转储的排序算法。我正在调用一个内核,该内核读取合并后的结构数组 结构如下所示:分析CUDA代码:合并内存读取的意外指令计数,cuda,profiling,instructions,Cuda,Profiling,Instructions,我正在为小输入数据(=512个元素)分析一个非常转储的排序算法。我正在调用一个内核,该内核读取合并后的结构数组 结构如下所示: struct __align__(8) Elements { float weight; int value; }; Invocations Avg Min Max Event Name Device 0 Kernel: sort(Elements*)
struct __align__(8) Elements
{
float weight;
int value;
};
Invocations Avg Min Max Event Name
Device 0
Kernel: sort(Elements*)
500 0 0 0 gld_inst_8bit
500 0 0 0 gld_inst_16bit
500 0 0 0 gld_inst_32bit
500 512 512 512 gld_inst_64bit
500 0 0 0 gld_inst_128bit
500 0 0 0 l1_global_load_hit
500 120 120 120 l1_global_load_miss
500 0 0 0 uncached_global_load_tr.
nvprof为L1未命中/命中和gdl指令提供以下指令计数:
Invocations Avg Min Max Event Name
Kernel: sort(Elements*)
500 0 0 0 gld_inst_8bit
500 0 0 0 gld_inst_16bit
500 1024 1024 1024 gld_inst_32bit
500 0 0 0 gld_inst_64bit
500 0 0 0 gld_inst_128bit
500 120 120 120 l1_global_load_hit
500 120 120 120 l1_global_load_miss
500 0 0 0 uncached_global_load_tr.
如果我更改结构的布局,如下所示:
struct __align__(8) Elements
{
float weight;
float value;
};
分析输出如下所示:
struct __align__(8) Elements
{
float weight;
int value;
};
Invocations Avg Min Max Event Name
Device 0
Kernel: sort(Elements*)
500 0 0 0 gld_inst_8bit
500 0 0 0 gld_inst_16bit
500 0 0 0 gld_inst_32bit
500 512 512 512 gld_inst_64bit
500 0 0 0 gld_inst_128bit
500 0 0 0 l1_global_load_hit
500 120 120 120 l1_global_load_miss
500 0 0 0 uncached_global_load_tr.
执行时间根本没有影响,但我不明白为什么GPU在第一个代码变量上执行32位加载指令,在第二个代码变量上执行64位指令
内核由1个块和512个线程调用(因此l1_全局_加载_x计数器可能不正确)。所有这些都发生在配备CUDA 5.0的GeForce 480上
编辑:
排序内核(稍微缩短):
其基本原因是编译器生成代码。PTX汇编程序对于浮点和整数有不同的虚拟寄存器状态空间,并且(我认为)不可能在不同状态空间中对两个寄存器执行64位加载。因此,编译器在混合整数/浮点结构中发出两个32位加载,但在浮点/浮点结构的情况下,可以向两个寄存器发出64位向量加载 这可以通过考虑以下代码模型来说明:
struct __align__(8) ElementsB
{
float weight;
float value;
};
struct __align__(8) ElementsA
{
float weight;
int value;
};
template<typename T>
__global__ void kernel(const T* __restrict__ in, T* __restrict__ out, bool flag)
{
int idx = threadIdx.x + blockIdx.x * blockDim.x;
T ival = in[idx];
if (flag) {
out[idx] = ival;
}
}
template __global__ void kernel<ElementsA>(const ElementsA *, ElementsA *, bool);
template __global__ void kernel<ElementsB>(const ElementsB *, ElementsB *, bool);
(为了强调,增加了评论)
对于元素B
实例:
ld.param.u32 %r4, [_Z6kernelI9ElementsAEvPKT_PS1_b_param_0];
ld.param.u32 %r5, [_Z6kernelI9ElementsAEvPKT_PS1_b_param_1];
ld.param.u8 %rc1, [_Z6kernelI9ElementsAEvPKT_PS1_b_param_2];
cvta.to.global.u32 %r1, %r5;
cvta.to.global.u32 %r6, %r4;
.loc 2 16 1
mov.u32 %r7, %ntid.x;
mov.u32 %r8, %ctaid.x;
mov.u32 %r9, %tid.x;
mad.lo.s32 %r2, %r7, %r8, %r9;
.loc 2 18 1
shl.b32 %r10, %r2, 3;
add.s32 %r11, %r6, %r10;
ld.global.u32 %r3, [%r11+4]; // 32 bit integer load
ld.global.f32 %f1, [%r11]; // 32 bit floating point load
ld.param.u32 %r3, [_Z6kernelI9ElementsBEvPKT_PS1_b_param_0];
ld.param.u32 %r4, [_Z6kernelI9ElementsBEvPKT_PS1_b_param_1];
ld.param.u8 %rc1, [_Z6kernelI9ElementsBEvPKT_PS1_b_param_2];
cvta.to.global.u32 %r1, %r4;
cvta.to.global.u32 %r5, %r3;
.loc 2 16 1
mov.u32 %r6, %ntid.x;
mov.u32 %r7, %ctaid.x;
mov.u32 %r8, %tid.x;
mad.lo.s32 %r2, %r6, %r7, %r8;
.loc 2 18 1
shl.b32 %r9, %r2, 3;
add.s32 %r10, %r5, %r9;
ld.global.v2.f32 {%f9, %f10}, [%r10]; // 64 bit float2 load
这两者之间没有性能损失的原因是底层硬件使用128字节的回迁进行合并的扭曲级加载,在这两种情况下,事务都会产生同一对128字节的回迁。你能添加内核的代码吗?GPU在第一种情况下执行32位加载,因为编译器在这种情况下生成32位加载指令(在第二种情况下生成64位加载)。我想你的问题是“为什么当我的结构有两个顺序的
float
类型时,编译器生成64位加载,而当我的结构有一个float
后跟一个int
时,编译器生成两个32位加载?”?