为什么 PTX 对 128 位结构分配显示 32 位加载操作?
Why does PTX shows 32 bit load operation for a 128 bit struct assignment?
我像这样定义了 128 位的自定义结构-
typedef struct dtype{
int val;
int temp2;
int temp3;
int temp4;
}dtype;
然后我做了一个作业:-
dtype temp= h_a[i]; //where h_a is dtype *
我期待的是 128 位加载,但 PTX 显示的是 32 位加载操作-
mul.wide.s32 %rd4, %r18, 16;
add.s64 %rd5, %rd1, %rd4;
ld.global.u32 %r17, [%rd5];
它不应该看起来像 ld.global.v4.u32 %r17, [%rd5];
我哪里错了?
如果保证内存与类型的大小对齐,并且使用了该类型的所有元素,则编译器只会发出向量化加载或存储指令(否则向量指令将被优化为节省带宽的标量指令)。
如果你这样做:
struct dtype{
int val;
int temp2;
int temp3;
int temp4;
};
struct __align__ (16) adtype{
int val;
int temp2;
int temp3;
int temp4;
};
__global__
void kernel(adtype* x, dtype* y)
{
adtype lx = x[threadIdx.x];
dtype ly;
ly.val = lx.temp4;
ly.temp2 = lx.temp3;
ly.temp3 = lx.val;
ly.temp4 = lx.temp2;
y[threadIdx.x] = ly;
}
你应该得到这样的东西:
visible .entry _Z6kernelP6adtypeP5dtype(
.param .u64 _Z6kernelP6adtypeP5dtype_param_0,
.param .u64 _Z6kernelP6adtypeP5dtype_param_1
)
{
ld.param.u64 %rd1, [_Z6kernelP6adtypeP5dtype_param_0];
ld.param.u64 %rd2, [_Z6kernelP6adtypeP5dtype_param_1];
cvta.to.global.u64 %rd3, %rd2;
cvta.to.global.u64 %rd4, %rd1;
mov.u32 %r1, %tid.x;
mul.wide.u32 %rd5, %r1, 16;
add.s64 %rd6, %rd4, %rd5;
ld.global.v4.u32 {%r2, %r3, %r4, %r5}, [%rd6];
add.s64 %rd7, %rd3, %rd5;
st.global.u32 [%rd7], %r5;
st.global.u32 [%rd7+4], %r4;
st.global.u32 [%rd7+8], %r2;
st.global.u32 [%rd7+12], %r3;
ret;
}
这里可以清楚的看到对齐类型的向量化加载,以及non-aligned类型的non-vectorized存储。如果更改内核使存储为对齐版本:
__global__
void kernel(adtype* x, dtype* y)
{
dtype ly = y[threadIdx.x];
adtype lx;
lx.val = ly.temp4;
lx.temp2 = ly.temp3;
lx.temp3 = ly.val;
lx.temp4 = ly.temp2;
x[threadIdx.x] = lx;
}
你会得到这个:
.visible .entry _Z6kernelP6adtypeP5dtype(
.param .u64 _Z6kernelP6adtypeP5dtype_param_0,
.param .u64 _Z6kernelP6adtypeP5dtype_param_1
)
{
ld.param.u64 %rd1, [_Z6kernelP6adtypeP5dtype_param_0];
ld.param.u64 %rd2, [_Z6kernelP6adtypeP5dtype_param_1];
cvta.to.global.u64 %rd3, %rd1;
cvta.to.global.u64 %rd4, %rd2;
mov.u32 %r1, %tid.x;
mul.wide.u32 %rd5, %r1, 16;
add.s64 %rd6, %rd4, %rd5;
add.s64 %rd7, %rd3, %rd5;
ld.global.u32 %r2, [%rd6+12];
ld.global.u32 %r3, [%rd6+8];
ld.global.u32 %r4, [%rd6+4];
ld.global.u32 %r5, [%rd6];
st.global.v4.u32 [%rd7], {%r2, %r3, %r5, %r4};
ret;
}
现在对齐类型存储在向量化指令中。
[使用默认的 Godbolt 工具链 (10.2) 为 sm_53 编译的所有代码]
我补充一点,以防有人碰巧遇到同样的问题。
{
dtype temp = h_a[i]; //Loading data exactly needed
sum.val += temp.val;
}
我按照上面^^答案中给出的步骤进行操作,但是我没有得到 128 位负载,尽管上面的方法是绝对正确的。
问题是编译器看到在结构的 4 个字段中,我在某些加法运算中只使用了 1 个字段。所以它非常聪明地只加载了我需要的块。所以无论我尝试什么,我总是得到 32 位负载。
{
dtype temp = h_a[i]; //Loading data exactly needed
sum.val += temp.val;
sum.temp2 += temp.temp2;
sum.temp3 += temp.temp3;
sum.temp4 += temp.temp4;
}
有点变化。
现在我正在使用所有字段。所以编译器加载了所有字段!
是的,现在使用上面 ^^ 答案中给出的方法,使用 __align __(16) 我得到了正确的 128 位负载。
虽然这对很多人来说可能很明显,但我不是一个资深的编码员。我只在某些地方使用编码来完成我的项目。这对我来说非常有见地,我希望有人也能从中受益!
我像这样定义了 128 位的自定义结构-
typedef struct dtype{
int val;
int temp2;
int temp3;
int temp4;
}dtype;
然后我做了一个作业:-
dtype temp= h_a[i]; //where h_a is dtype *
我期待的是 128 位加载,但 PTX 显示的是 32 位加载操作-
mul.wide.s32 %rd4, %r18, 16;
add.s64 %rd5, %rd1, %rd4;
ld.global.u32 %r17, [%rd5];
它不应该看起来像 ld.global.v4.u32 %r17, [%rd5];
我哪里错了?
如果保证内存与类型的大小对齐,并且使用了该类型的所有元素,则编译器只会发出向量化加载或存储指令(否则向量指令将被优化为节省带宽的标量指令)。
如果你这样做:
struct dtype{
int val;
int temp2;
int temp3;
int temp4;
};
struct __align__ (16) adtype{
int val;
int temp2;
int temp3;
int temp4;
};
__global__
void kernel(adtype* x, dtype* y)
{
adtype lx = x[threadIdx.x];
dtype ly;
ly.val = lx.temp4;
ly.temp2 = lx.temp3;
ly.temp3 = lx.val;
ly.temp4 = lx.temp2;
y[threadIdx.x] = ly;
}
你应该得到这样的东西:
visible .entry _Z6kernelP6adtypeP5dtype(
.param .u64 _Z6kernelP6adtypeP5dtype_param_0,
.param .u64 _Z6kernelP6adtypeP5dtype_param_1
)
{
ld.param.u64 %rd1, [_Z6kernelP6adtypeP5dtype_param_0];
ld.param.u64 %rd2, [_Z6kernelP6adtypeP5dtype_param_1];
cvta.to.global.u64 %rd3, %rd2;
cvta.to.global.u64 %rd4, %rd1;
mov.u32 %r1, %tid.x;
mul.wide.u32 %rd5, %r1, 16;
add.s64 %rd6, %rd4, %rd5;
ld.global.v4.u32 {%r2, %r3, %r4, %r5}, [%rd6];
add.s64 %rd7, %rd3, %rd5;
st.global.u32 [%rd7], %r5;
st.global.u32 [%rd7+4], %r4;
st.global.u32 [%rd7+8], %r2;
st.global.u32 [%rd7+12], %r3;
ret;
}
这里可以清楚的看到对齐类型的向量化加载,以及non-aligned类型的non-vectorized存储。如果更改内核使存储为对齐版本:
__global__
void kernel(adtype* x, dtype* y)
{
dtype ly = y[threadIdx.x];
adtype lx;
lx.val = ly.temp4;
lx.temp2 = ly.temp3;
lx.temp3 = ly.val;
lx.temp4 = ly.temp2;
x[threadIdx.x] = lx;
}
你会得到这个:
.visible .entry _Z6kernelP6adtypeP5dtype(
.param .u64 _Z6kernelP6adtypeP5dtype_param_0,
.param .u64 _Z6kernelP6adtypeP5dtype_param_1
)
{
ld.param.u64 %rd1, [_Z6kernelP6adtypeP5dtype_param_0];
ld.param.u64 %rd2, [_Z6kernelP6adtypeP5dtype_param_1];
cvta.to.global.u64 %rd3, %rd1;
cvta.to.global.u64 %rd4, %rd2;
mov.u32 %r1, %tid.x;
mul.wide.u32 %rd5, %r1, 16;
add.s64 %rd6, %rd4, %rd5;
add.s64 %rd7, %rd3, %rd5;
ld.global.u32 %r2, [%rd6+12];
ld.global.u32 %r3, [%rd6+8];
ld.global.u32 %r4, [%rd6+4];
ld.global.u32 %r5, [%rd6];
st.global.v4.u32 [%rd7], {%r2, %r3, %r5, %r4};
ret;
}
现在对齐类型存储在向量化指令中。
[使用默认的 Godbolt 工具链 (10.2) 为 sm_53 编译的所有代码]
我补充一点,以防有人碰巧遇到同样的问题。
{
dtype temp = h_a[i]; //Loading data exactly needed
sum.val += temp.val;
}
我按照上面^^答案中给出的步骤进行操作,但是我没有得到 128 位负载,尽管上面的方法是绝对正确的。
问题是编译器看到在结构的 4 个字段中,我在某些加法运算中只使用了 1 个字段。所以它非常聪明地只加载了我需要的块。所以无论我尝试什么,我总是得到 32 位负载。
{
dtype temp = h_a[i]; //Loading data exactly needed
sum.val += temp.val;
sum.temp2 += temp.temp2;
sum.temp3 += temp.temp3;
sum.temp4 += temp.temp4;
}
有点变化。 现在我正在使用所有字段。所以编译器加载了所有字段! 是的,现在使用上面 ^^ 答案中给出的方法,使用 __align __(16) 我得到了正确的 128 位负载。 虽然这对很多人来说可能很明显,但我不是一个资深的编码员。我只在某些地方使用编码来完成我的项目。这对我来说非常有见地,我希望有人也能从中受益!