Я пытаюсь добавить CUDA в существующую однопоточную программу на C, написанную в конце 90-х годов.
Для этого мне нужно смешать два языка, 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-байтовое int должно быть выровнено с 4-байтовой границей, а 8-байтовый двойной или длинный длинный должен иметь 8-байтовое выравнивание. CUDA применяет это и для кода хоста, чтобы обеспечить максимально возможную совместимость структур между частями кода хоста и устройства. Процессоры x86, с другой стороны, обычно не требуют естественного выравнивания данных (хотя снижение производительности может быть вызвано отсутствием выравнивания).
В этом случае CUDA необходимо выровнять двойной компонент структуры по 8-байтовой границе. Поскольку нечетное число компонентов int предшествует двойному, это требует заполнения. Переключение порядка компонентов, т.е. размещение двойного компонента первым, не помогает, потому что в массиве таких структур каждая структура должна быть выровнена по 8 байтам, и поэтому размер структуры должен быть кратным 8 байтам для достижения этой цели. , что также требует заполнения.
Чтобы заставить gcc выровнять двойники так же, как это делает CUDA, передайте флаг -malign-double
,
Похоже на то, что два компилятора применяют разные отступы: один работает с 4-байтовым выравниванием, а другой — как минимум с 8-байтовым выравниванием. Вы должны быть в состоянии принудительно выровнять выравнивание в зависимости от компилятора #pragma
директивы (проверьте документацию вашего компилятора о конкретных #pragma
).
Нет гарантии, что два разных компилятора 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 */
Там может быть специфичным для компилятора способом указать другое выравнивание, но такие методы не всегда безопасно.
Идеальным решением было бы использовать один и тот же компилятор для кода C и C ++. C не является подмножеством C ++, но обычно не должно быть слишком сложно изменить существующий код C, чтобы он компилировался как C ++.
Или вы можете изменить свое определение структуры так, чтобы оба компилятора выстроили его одинаково. Размещение double
Первый член, скорее всего, будет работать. Это по-прежнему не гарантируется, и это может порвать с будущими версиями любого компилятора, но это наверное достаточно хорошо.
Не забывайте, что в самом конце конструкции также может быть заполнение; иногда это необходимо для обеспечения правильного выравнивания массивов конструкций. смотреть на sizeof (struct TB)
и сравните его с размером и смещением последнего объявленного члена.
Другая возможность: вставить явные неиспользуемые элементы, чтобы обеспечить согласованное выравнивание. Например, предположим, если у вас есть:
struct foo {
uint16_t x;
uint32_t y;
};
и один компилятор ставит y
в 16 битах, и другой помещает это в 32 бита с 16 битами заполнения. Если вы измените определение на:
struct foo {
uint16_t x;
uint16_t unused_padding;
uint32_t y;
};
тогда у вас больше шансов x
а также y
имеют одинаковое смещение под обоими компиляторами. Вам все равно придется экспериментировать, чтобы убедиться, что все соответствует.
Поскольку код на C и C ++ будет частью одной и той же программы (верно?), Вам не нужно беспокоиться о таких вещах, как изменение порядка байтов. Если вы хотите передавать значения вашего типа структуры между отдельными программами, например, сохраняя их в файлах или передавая их по сети, вам может потребоваться определить согласованный способ сериализации значения структуры в последовательность байтов и наоборот.