6

我正在尝试将 CUDA 添加到 90 年代后期某个时候编写的现有单线程 C 程序中。

为此,我需要混合两种语言,C 和 C++(nvcc 是一个 c++ 编译器)。

问题是 C++ 编译器将结构视为特定大小,而 C 编译器将相同结构视为大小略有不同。那很糟。我对此感到非常困惑,因为我找不到导致 4 字节差异的原因。

/usr/lib/gcc/i586-suse-linux/4.3/../../../../i586-suse-linux/bin/ld: Warning: size of symbol `tree' changed from 324 in /tmp/ccvx8fpJ.o to 328 in gpu.o

我的 C++ 看起来像

#include <stdio.h>
#include <stdlib.h>
#include "assert.h"
extern "C"
{
#include "structInfo.h" //contains the structure declaration
}
...

我的 C 文件看起来像

#include "structInfo.h"
...

structInfo.h 看起来像

struct TB {
   int  nbranch, nnode, root, branches[NBRANCH][2];
         double lnL;
}  tree;
...

我的make文件看起来像

PRGS =  prog
CC = cc
CFLAGS=-std=gnu99 -m32
CuCC = nvcc
CuFlags =-arch=sm_20
LIBS = -lm -L/usr/local/cuda-5.0/lib -lcuda -lcudart
all : $(PRGS)
prog: 
        $(CC) $(CFLAGS) prog.c gpu.o $(LIBS) -o prog
gpu.o:
        $(CuCC) $(CuFlags) -c gpu.cu

有人问我为什么不使用不同的主机编译选项。我认为主机编译选项自 2 版前已被弃用?而且它似乎从来没有像它所说的那样做

nvcc warning : option 'host-compilation' has been deprecated and is ignored
4

3 回答 3

17

GPU 要求所有数据自然对齐,例如 4 字节的 int 需要与 4 字节的边界对齐,而 8 字节的 double 或 long long 需要有 8 字节的对齐。CUDA 也对主机代码强制执行此操作,以确保代码的主机和设备部分之间的结构尽可能兼容。另一方面,x86 CPU 通常不要求数据自然对齐(尽管缺乏对齐可能会导致性能下降)。

在这种情况下,CUDA 需要将结构的 double 组件与 8 字节边界对齐。由于在 double 之前有奇数个 int 组件,因此这需要填充。切换组件的顺序,即将双组件放在首位,并没有帮助,因为在此类结构的数组中,每个结构都必须是 8 字节对齐的,因此结构的大小必须是 8 字节的倍数才能实现这一点,这也需要填充。

要强制 gcc 以与 CUDA 相同的方式对齐双精度,请传递 flag -malign-double

于 2012-12-09T01:50:12.453 回答
5

似乎 2 个编译器应用了不同的填充:一个使用 4 字节对齐,另一个使用至少 8 字节对齐。您应该能够通过特定于编译器的#pragma指令强制您想要的对齐(检查您的编译器文档关于特定的#pragma)。

于 2012-12-08T22:24:23.093 回答
3

不能保证两个不同的 C 编译器会对同一类型使用相同的表示形式——除非它们都符合某个足够详细地指定表示形式的外部标准(ABI)。

这很可能是填充的差异,其中一个编译器要求 adouble是 4 字节对齐的,而另一个要求它是 8 字节对齐的。就 C 和 C++ 标准而言,这两种选择都是完全有效的。

您可以通过打印出结构中所有成员的大小和偏移量来更详细地研究这一点:

printf("nbranch: size %3u offset %3u\n",
       (unsigned)sizeof tree.nbranch,
       (unsigned)offsetof(struct TB, nbranch));
/* and similarly for the other members */

可能有一种特定于编译器的方式来指定不同的对齐方式,但这种技术并不总是安全的。

理想的解决方案是对 C 和 C++ 代码使用相同的编译器。C 不是 C++ 的子集,但修改现有 C 代码通常应该不会太难,因此它可以编译为 C++。

或者您可以重新排列您的结构定义,以便两个编译器碰巧以相同的方式布置它。将double成员放在首位可能会奏效。这仍然不能保证有效,并且它可能会与任一编译器的未来版本中断,但它可能已经足够好了。

不要忘记在结构的最后也可能有填充;这有时对于保证结构数组的正确对齐是必要的。查看sizeof (struct TB)并将其与最后声明的成员的大小和偏移量进行比较。

另一种可能性:插入显式未使用的成员以强制一致对齐。例如,假设您有:

struct foo {
    uint16_t x;
    uint32_t y;
};

一个编译器放在y16 位上,另一个编译器放在 32 位上,有 16 位填充。如果将定义更改为:

struct foo {
    uint16_t x;
    uint16_t unused_padding;
    uint32_t y;
};

那么你更有可能在两个编译器下拥有x并拥有相同的偏移量。y您仍然需要进行试验以确保一切都是一致的。

由于 C 和 C++ 代码将成为同一程序的一部分(对吗?),您不必担心字节顺序变化等问题。如果您想在单独的程序之间传输结构类型的值,例如通过将它们存储在文件中或通过网络传输它们,您可能需要定义一种一致的方式将结构值序列化为字节序列,反之亦然。

于 2012-12-08T23:16:53.787 回答