CUDA内存对齐

19

在我的代码中,我使用结构体来方便地将参数传递给函数(我不使用结构体数组,而是通常使用数组的结构体)。 当我在cuda-gdb中查看内核中给一个简单结构体赋值的点时,

struct pt{
int i;
int j;
int k;
}

尽管我没有做什么复杂的事情,而且成员应该有指定的值是很明显的,但我还是遇到了问题......

要求获取栈中位置0的元素,但是栈上没有任何元素。

所以我想即使它不是一个数组,也可能存在内存对齐问题。因此我更改了头文件中的定义为:

struct __align__(16) pt{
int i;
int j;
int k;
}

但是当编译器尝试编译使用相同定义的主机代码文件时,会出现以下错误:

error: expected unqualified-id before numeric constant error: expected ‘)’ before numeric constant error: expected constructor, destructor, or type conversion before ‘;’ token

那么我是否应该为主机和设备结构定义两个不同的定义呢?

另外,我想问如何推广对齐逻辑。 我不是计算机科学家,因此编程指南中的两个示例无法帮助我把握大局。

例如,下面这两个应该如何排列? 或者,一个由6个浮点数组成的结构体应该如何排列? 还有4个整数吗? 再次说明一下,我没有使用这些数组,但我在内核或 _device_ 函数中定义了许多具有这些结构的变量。

struct {
    int a;
    int b;
    int c;
    int d;
    float* el;    
} ;

 struct {
    int a;
    int b
    int c
    int d
    float* i;
    float* j;
    float* k;
} ;

提前感谢任何建议或提示


我认为你正在寻找由@harrism本人回答的这个https://dev59.com/LGw05IYBdhLWcg3w3lkl问题。 - eLRuLL
2个回答

32

这篇文章里有很多问题。由于CUDA编程指南已经非常好地解释了CUDA中的对齐问题,我只会解释一些指南中不太明显的内容。

首先,你的主机编译器会报错是因为它不知道__align(n)__是什么意思,所以它给出了语法错误。你需要在项目头文件中添加类似以下的内容:

#if defined(__CUDACC__) // NVCC
   #define MY_ALIGN(n) __align__(n)
#elif defined(__GNUC__) // GCC
  #define MY_ALIGN(n) __attribute__((aligned(n)))
#elif defined(_MSC_VER) // MSVC
  #define MY_ALIGN(n) __declspec(align(n))
#else
  #error "Please provide a definition for MY_ALIGN macro for your host compiler!"
#endif

所以,我应该为主机结构和设备结构分别定义两个不同的结构体吗?

不需要,只需使用MY_ALIGN(n),像这样:

struct MY_ALIGN(16) pt { int i, j, k; }
例如,以下两个应该如何对齐?
首先,__align(n)__(或任何主机编译器风格)强制结构体的内存从在内存中是n字节的倍数的地址开始。如果结构体的大小不是n的倍数,则在这些结构体的数组中会插入填充以确保每个结构体都正确对齐。要选择合适的值n,您需要尽量减少所需的填充量。正如编程指南中所解释的那样,硬件要求每个线程读取1、2、4、8或16字节对齐的字。因此...
struct MY_ALIGN(16) {
  int a;
  int b;
  int c;
  int d;
  float* el;    
};

假设我们选择16字节的对齐方式,在32位机器上,指针占用4个字节,因此结构体占用20个字节。16字节的对齐方式会浪费16 * (ceil(20/16) - 1) = 12个字节每个结构体。在64位机器上,它只会浪费每个结构体8个字节,由于指针为8个字节。我们可以使用MY_ALIGN(8)来减少浪费。这样做的折衷是硬件需要使用3个8字节的加载操作,而不是2个16字节的加载操作从内存中加载结构体。如果你不被加载操作所限制,这可能是一个值得做的折衷。请注意,你不想让这个结构体对齐小于4个字节。

struct MY_ALIGN(16) {
  int a;
  int b
  int c
  int d
  float* i;
  float* j;
  float* k;
};

如果按16字节对齐,32位机器上每个结构体只浪费4个字节,64位机器上则是8个字节。这将需要两次16字节的加载(64位机器上为3次)。如果我们按8字节对齐,则可以通过4字节对齐(64位机器上为8字节对齐)消除浪费,但这将导致过多的加载。再次强调,这是权衡的结果。

那么带有6个浮点数的结构应该如何对齐?

同样是一个权衡:每个结构体浪费8个字节或每个结构体需要两次加载。

那带有4个整数的结构呢?

这里没有任何权衡。MY_ALIGN(16)

虽然我不使用数组,但我在内核或_device_函数中为这些结构定义了许多变量。

如果您不使用这些结构的数组,则可能根本不需要对齐。但是您是如何对其进行赋值的呢?正如您可能已经看到的那样,所有这些浪费都很重要,因此更好的选择是使用数组结构而不是结构体数组。


1
非常感谢您的回答。我最希望得到的是一个外部参考链接,但这比我期望的更多,是一整个有关对齐的课程。我感到十分荣幸。我的代码确实使用了数组结构。我在较小的规模上使用像pt(如上所述)这样的结构,以便于从内核中传递参数到被调用的_device_函数。但当我尝试从cuda-gdb查询它们的值时,它们就像是不可见的。 - Panagiotis
很高兴能够帮忙。点个赞也不错。 :) 不确定这是否有助于解决cuda-gdb问题。根据我的经验,设备代码调试器并不总是显示所有值 - 只显示当前暂停代码位置处立即在范围/活动中的值。 - harrism
所以,为了澄清一下思路:当我通过gdb收到“要求堆栈位置0,但堆栈上只有0个元素。”这样的信息时...这是意味着gdb没有使该值对我可查询,还是该变量尚未定义,和/或没有分配任何值?当然,我更关心程序执行本身发生了什么,而不是我可以通过gdb看到什么。 - Panagiotis
是的,我做了。我还使用了 -O0,这应该被包含在 -G 中,但在几个情况下,我发现这并不是这样的。 - Panagiotis
2
多年后,仍然是一个很好的答案!值得一提的是,英特尔的编译器也使用__declspec(align(n))。编译器定义了__INTEL_COMPILER,因此您可以将其添加到您的MSVC中。而Clang定义了__clang__,并使用GCC的版本(__attribute__((aligned(n)))),因此您也可以在那里添加它。这几乎涵盖了我需要处理的所有主要(非专业即ARM,我对此一无所知)编译器;)我曾经遇到过与英特尔的问题,因为他们定义了许多意想不到的东西。我记不清具体情况了,但解决方案是首先检查它。 - svenevs
显示剩余2条评论

10

现在应该使用C++11中的alignas指定符号,它被GCC(包括与当前CUDA兼容的版本)、自2015版本以来的MSVC以及nvcc支持。这可以避免您需要使用宏。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接