2012-12-08 64 views
6

我正在嘗試將CUDA添加到90年代後期寫入的現有單線程C程序中。爲什麼gcc和NVCC(g ++)看到兩種不同的結構尺寸?

爲此,我需要混合兩種語言,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發佈之前已被棄用?也it never appeared to do what it said it would do

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

差異是否存在填充?你確定nvcc和gcc(我認爲你使用的是cc)是兼容的嗎?編輯:其實,閱讀有關與填充問題與nvcc,支持 – 2012-12-08 22:17:38

+0

這是一個32位或64位平臺?你可以嘗試[移動參數](https://devtalk.nvidia.com/default/topic/394418/padding-problem-nvcc-bug-/),看看是否有效? – 2012-12-08 22:20:18

+0

@EsaLakaniemi這是一個32位平臺。 – Mikhail

回答

12

GPU需要所有數據的自然對齊,例如,一個4字節的int需要對齊到一個4字節的邊界,而一個8字節的double或long long需要有8字節的對齊。 CUDA對主機代碼強制執行此操作,以確保結構在代碼的主機和設備部分之間儘可能兼容。另一方面,x86 CPU通常不需要數據自然對齊(儘管性能損失可能是由於缺少對齊)。

在這種情況下,CUDA需要將結構的雙分量與8字節邊界對齊。由於在double之前有奇數個int分量,所以需要填充。切換組件的順序,即首先放置雙組件,並沒有什麼幫助,因爲在這樣的結構數組中,每個結構必須是8字節對齊的,因此結構的大小必須是8字節的倍數才能完成,這也需要填充。

要強制gcc以與CUDA相同的方式對齊雙打,請傳遞標記-malign-double

5

看起來像2個編譯器應用的不同填充:一個是使用4字節對齊方式,另一個使用至少8字節對齊方式。您應該能夠通過編譯器特定的#pragma指令來強制執行所需的對齊方式(請檢查您的編譯器文檔有關特定的#pragma)。

+0

'#pragma pack(4)'和'#pragma pack(8)'似乎沒有幫助,它們導致相同的錯誤。我如何做到這一點的海灣合作委員會? – Mikhail

+5

我認爲你需要的gcc是編譯器標誌-malign-double。由於GPU需要所有數據的自然對齊,因此CUDA在主機上強制執行此操作,以確保代碼的主機和設備部分之間的結構兼容。由於結構中雙精度前的整數個數是奇數,所以需要填充結構。或者,您可以重新排序結構的組件,以使double成爲第一個組件。 – njuffa

+0

@njuffa重新排序不起作用,但'-malign-double'確實有效。你應該把它作爲答案發布,以便我可以給你信用。謝謝! – Mikhail

2

不能保證兩個不同的C編譯器將對同一類型使用相同的表示 - 除非它們都符合一些外部標準(ABI),該標準詳細說明了表示形式。

這很可能是填充差異,其中一個編譯器要求double需要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 */ 

可能是編譯器特定的方式來指定不同的排列,但這種技術是not always safe

理想的解決方案是將相同的編譯器用於C和C++代碼。 C不是C++的子集,但修改現有C代碼通常不應該太難,因此它編譯爲C++。

或者您可能可以重新安排您的結構定義,以便兩個編譯器碰巧以相同的方式進行佈局。首先放置double成員可能會起作用。這仍然不能保證工作,它可能會打破未來版本的編譯器,但它的可能夠好。

不要忘記,在結構的最末端也可能有填充;有時這對於保證結構陣列的正確對齊是有必要的。查看sizeof (struct TB)並將其與最後聲明的成員的大小和偏移量進行比較。

另一種可能性:插入顯式未使用的成員來強制一致對齊。例如,假設如果您有:

struct foo { 
    uint16_t x; 
    uint32_t y; 
}; 

和一個編譯器把y在16位,和其他與填充的16位把它在32位。如果你改變了定義:

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

那麼你就更有可能xy具有相同的兩個編譯器中的偏移。您仍然需要進行試驗以確保一切都一致。

由於C和C++代碼將會是同一個程序的一部分(對吧?),您不必擔心像變化的字節順序這樣的事情。如果您想在不同的程序之間傳輸結構類型的值,比如將它們存儲在文件中或通過網絡傳輸它們,則可能需要定義一種一致的方法來將結構值序列化爲字節序列,反之亦然。