C++ CUDA中从浮点*到浮点3*的转换是否安全?

C++ CUDA中从浮点*到浮点3*的转换是否安全?,c++,cuda,type-conversion,type-safety,C++,Cuda,Type Conversion,Type Safety,我刚刚开始深入研究CUDA代码,这与过去有点不同,使用reinterpret\u cast通过指针进行大量指针访问和类型转换。我有一个我想检查的具体案例,我在代码中看到了以下类型双关的实例: __device__ void func(__restrict__ float* const points, size_t size, __restrict__ float* outputPoints) { for (size_t index = 0; index < size; index

我刚刚开始深入研究CUDA代码,这与过去有点不同,使用
reinterpret\u cast
通过指针进行大量指针访问和类型转换。我有一个我想检查的具体案例,我在代码中看到了以下类型双关的实例:

__device__ void func(__restrict__ float* const points, size_t size, __restrict__ float* outputPoints) {

    for (size_t index = 0; index < size; index += 3) {
        float3* const point = reinterpret_cast<float3* const>(points + index);
        float3* const output = reinterpret_cast<float3* const>(outputPoints + index);
        // operations using point;
    }
}

这种行为保证安全吗?这显然是某种类型的双关语,但我非常担心可能会有一些填充或对齐,或者会以这种方式破坏访问。如果有人能够进一步了解cuda编译器将如何处理这个问题,因为我知道它也进行了一些非常繁重的优化。这些会导致问题吗?

CUDA保证这些内置类型的大小在主机和设备之间保持一致,而无需填充干预(用户定义的结构和类不存在此类保证)

设备上的对齐有一些基本要求,例如,您读取的存储必须与读取的大小对齐。因此,您无法从任意字节边界读取
float3
,但可以安全地从32位对齐的边界读取,而CUDA在主机和设备上公开的内存分配API保证了必要的对齐,以确保您发布的代码是安全的

您发布的代码(经过修改以消除死代码时)基本上只发出三个32位加载和三个32位存储。CUDA只有有限数量的本机事务大小,并且它们没有映射到每个线程96位的请求,因此这样做绝对没有优化:

__device__ void func(float* const points, size_t size, float* outputPoints) {

    for (size_t index = 0; index < size; index += 3) {
        float3* point = reinterpret_cast<float3*>(points + index);
        float3* output = reinterpret_cast<float3*>(outputPoints + index);

    float3 val = *point;
    val.x += 1.f; val.y += 2.f; val.z += 3.f;
    *output = val;
    }
}
请注意,加载和存储现在使用的是指令的矢量化版本。与浮动4相同:

    // .globl   _Z4funcPfmS_
.visible .func _Z4funcPfmS_(
    .param .b64 _Z4funcPfmS__param_0,
    .param .b64 _Z4funcPfmS__param_1,
    .param .b64 _Z4funcPfmS__param_2
)
{
    .reg .pred  %p<3>;
    .reg .f32   %f<12>;
    .reg .b64   %rd<14>;


    ld.param.u64    %rd12, [_Z4funcPfmS__param_0];
    ld.param.u64    %rd8, [_Z4funcPfmS__param_1];
    ld.param.u64    %rd11, [_Z4funcPfmS__param_2];
    setp.eq.s64 %p1, %rd8, 0;
    mov.u64     %rd13, 0;
    @%p1 bra    BB6_2;

BB6_1:
    ld.v4.f32   {%f1, %f2, %f3, %f4}, [%rd12];
    add.f32     %f9, %f3, 0f40400000;
    add.f32     %f10, %f2, 0f40000000;
    add.f32     %f11, %f1, 0f3F800000;
    st.v4.f32   [%rd11], {%f11, %f10, %f9, %f4};
    add.s64     %rd12, %rd12, 8;
    add.s64     %rd11, %rd11, 8;
    add.s64     %rd13, %rd13, 2;
    setp.lt.u64 %p2, %rd13, %rd8;
    @%p2 bra    BB6_1;

BB6_2:
    ret;
}
/.globl\u Z4funcPfmS_
.visible.func_Z4funcPfmS_(
.param.b64_Z4funcPfmS__param_0,
.param.b64_Z4funcPfmS__param_1,
.param.b64_Z4funcPfmS__param_2
)
{
.reg.pred%p;
.reg.f32%f;
.reg.b64%rd;
ld.param.u64%rd12,[\u Z4funcPfmS\u\u param\u 0];
ld.param.u64%rd8,[\u Z4funcPfmS\u\u param\u 1];
ld.param.u64%rd11,[\uz4funcpfms\uuuu param\u2];
setp.eq.s64%p1,%rd8,0;
mov.u64%rd13,0;
@%p1胸罩BB6_2;
BB6_1:
ld.v4.f32{%f1,%f2,%f3,%f4},[%rd12];
添加0.f32%f9%f3,0f44000000;
add.f32%f10、%f2、0F4000000;
添加0.f32%f11、%f1、0f3f80000;
st.v4.f32[%rd11],{%f11,%f10,%f9,%f4};
add.s64%rd12,%rd12,8;
add.s64%rd11,%rd11,8;
add.s64%rd13,%rd13,2;
setp.lt.u64%p2、%rd13、%rd8;
@%p2胸罩BB6_1;
BB6_2:
ret;
}

TLDR:您的担忧是正确的,但是API和编译器会明智地处理合理的情况,但是在尝试编写“最佳代码”之前,您应该非常熟悉对齐和硬件限制,因为除非你确切知道自己在做什么,否则可能会写很多毫无意义的废话。

我想你指的是??这是非常古老的,我不确定它是否有必要了,因为优化器似乎已经改进了很多。另外,我想提到的是,以现代C++风格来做CUDA代码的主机部分。@ GynICyPoptogGe,是的,开始转换的时间。好的,所以CUDA保证了32位边界的对齐,并且没有对这个结构填充。制造这种内存的
cudamaloc
也保证了这一点,所以我们应该做得很好。我的意思是,对编译器来说“不做任何事情”,就像任何语法糖一样,但它更适合人类阅读。是的,但大多数编写GPU代码的人都专注于性能,大多数没有经验的初学者比编译器聪明得多,他们会尝试各种毫无意义的疯狂东西。规则应该是——自然地编写代码,让编译器发挥它的魔力。然后研究它的输出,并思考您的案例是否可以从知情的优化中获益。个人<代码>浮动3分(点[i]、点[i+1]、点[i+2])比您问题中的代码更容易阅读和理解,但这只是我的问题。您是否使用Godbolt进行装配分析?不,我只是使用了本地工具链。AFAIK Godbolt不允许您查看中间汇编程序(我的答案中就是PTX)。对于正确的性能分析,Godbolt所展示的是优越的,但如果您想了解编译器的惯用用法,我更喜欢看PTX,仍然有可能一些PTX指令没有等效的机器代码,并被预先封装的代码节所取代
$ nvcc -arch=sm_75 -std=c++11 -dc -ptx fffloat3.cu 
$ tail -40 fffloat3.ptx 
    // .globl   _Z4funcPfmS_
.visible .func _Z4funcPfmS_(
    .param .b64 _Z4funcPfmS__param_0,
    .param .b64 _Z4funcPfmS__param_1,
    .param .b64 _Z4funcPfmS__param_2
)
{
    .reg .pred  %p<3>;
    .reg .f32   %f<7>;
    .reg .b64   %rd<14>;


    ld.param.u64    %rd11, [_Z4funcPfmS__param_0];
    ld.param.u64    %rd8, [_Z4funcPfmS__param_1];
    ld.param.u64    %rd12, [_Z4funcPfmS__param_2];
    setp.eq.s64 %p1, %rd8, 0;
    mov.u64     %rd13, 0;
    @%p1 bra    BB6_2;

BB6_1:
    ld.f32  %f1, [%rd11];
    ld.f32  %f2, [%rd11+4];
    ld.f32  %f3, [%rd11+8];
    add.f32     %f4, %f1, 0f3F800000;
    add.f32     %f5, %f2, 0f40000000;
    add.f32     %f6, %f3, 0f40400000;
    st.f32  [%rd12], %f4;
    st.f32  [%rd12+4], %f5;
    st.f32  [%rd12+8], %f6;
    add.s64     %rd12, %rd12, 12;
    add.s64     %rd11, %rd11, 12;
    add.s64     %rd13, %rd13, 3;
    setp.lt.u64 %p2, %rd13, %rd8;
    @%p2 bra    BB6_1;

BB6_2:
    ret;
}
.visible .func _Z4funcPfmS_(
    .param .b64 _Z4funcPfmS__param_0,
    .param .b64 _Z4funcPfmS__param_1,
    .param .b64 _Z4funcPfmS__param_2
)
{
    .reg .pred  %p<3>;
    .reg .f32   %f<7>;
    .reg .b64   %rd<14>;


    ld.param.u64    %rd12, [_Z4funcPfmS__param_0];
    ld.param.u64    %rd8, [_Z4funcPfmS__param_1];
    ld.param.u64    %rd11, [_Z4funcPfmS__param_2];
    setp.eq.s64 %p1, %rd8, 0;
    mov.u64     %rd13, 0;
    @%p1 bra    BB6_2;

BB6_1:
    ld.v2.f32   {%f1, %f2}, [%rd12];
    add.f32     %f5, %f2, 0f40000000;
    add.f32     %f6, %f1, 0f3F800000;
    st.v2.f32   [%rd11], {%f6, %f5};
    add.s64     %rd12, %rd12, 8;
    add.s64     %rd11, %rd11, 8;
    add.s64     %rd13, %rd13, 2;
    setp.lt.u64 %p2, %rd13, %rd8;
    @%p2 bra    BB6_1;

BB6_2:
    ret;
}
    // .globl   _Z4funcPfmS_
.visible .func _Z4funcPfmS_(
    .param .b64 _Z4funcPfmS__param_0,
    .param .b64 _Z4funcPfmS__param_1,
    .param .b64 _Z4funcPfmS__param_2
)
{
    .reg .pred  %p<3>;
    .reg .f32   %f<12>;
    .reg .b64   %rd<14>;


    ld.param.u64    %rd12, [_Z4funcPfmS__param_0];
    ld.param.u64    %rd8, [_Z4funcPfmS__param_1];
    ld.param.u64    %rd11, [_Z4funcPfmS__param_2];
    setp.eq.s64 %p1, %rd8, 0;
    mov.u64     %rd13, 0;
    @%p1 bra    BB6_2;

BB6_1:
    ld.v4.f32   {%f1, %f2, %f3, %f4}, [%rd12];
    add.f32     %f9, %f3, 0f40400000;
    add.f32     %f10, %f2, 0f40000000;
    add.f32     %f11, %f1, 0f3F800000;
    st.v4.f32   [%rd11], {%f11, %f10, %f9, %f4};
    add.s64     %rd12, %rd12, 8;
    add.s64     %rd11, %rd11, 8;
    add.s64     %rd13, %rd13, 2;
    setp.lt.u64 %p2, %rd13, %rd8;
    @%p2 bra    BB6_1;

BB6_2:
    ret;
}