Можно ли отправить данные в память GPU, которая определяется как Union, из JCuda? - PullRequest
2 голосов
/ 25 июня 2019

Я определил новый тип данных, например, на стороне GPU (cuda):

typedef union {
    int i;
    double d;
    long l;
    char s[16];
} data_unit;

data_unit *d_array;

А в Java у нас есть массив одного из типов, доступных в определенном объединении.Обычно мы можем сделать следующее в Java (JCuda), если у нас есть массив типа int, например:

import static jcuda.driver.JCudaDriver.*;


int data_size;
CUdeviceptr d_array;
int[] h_array = new int[data_size];

cuMemAlloc(d_array, data_size * Sizeof.INT);
cuMemcpyHtoD(d_array, Pointer.to(h_array), data_size * Sizeof.INT);

Но как это сделать, если на устройстве есть массив, его тип является нашимсоюз?(предположим, что h_array по-прежнему имеет тип int)

int data_size;
CUdeviceptr d_array;
int[] h_array = new int[data_size];

cuMemAlloc(d_array, data_size * Sizeof.?);
// Here we should have some type of alignment (?)
cuMemcpyHtoD(d_array, Pointer.to(h_array), data_size * Sizeof.?);

Ответы [ 2 ]

5 голосов
/ 25 июня 2019

Я считаю, что существует фундаментальное недопонимание того, что такое союз.

Давайте подумаем об этом. Что отличает объединение от структуры? Он может хранить разные типы данных в разное время.

Как он совершает этот подвиг? Хорошо, можно использовать какую-то отдельную переменную для динамического определения типа или объема памяти, который он занимает, но Union этого не делает, он полагается на то, что программист точно знает, какой тип они хотят получить и когда. Таким образом, единственная альтернатива, если тип фактически известен только программисту в любой данный момент времени, это просто убедиться, что для вашей переменной union достаточно места, чтобы можно было всегда использовать его для любого типа.

Действительно, это то, что делает объединение, см. здесь (да, я знаю, что это C / C ++, но это также относится и к CUDA). Что это значит для вас? Это означает, что размер вашего массива объединения должен соответствовать размеру его наибольшего члена x числу элементов, поскольку размер объединения равен размеру его наибольшего члена.

Давайте посмотрим на ваш союз, чтобы понять, как это выяснить.

typedef union {
    int i;
    double d;
    long l;
    char s[16];
} data_unit;

Ваш союз имеет:

  • int i, который мы предполагаем равным 4 байта
  • double d, что составляет 8 байтов
  • long l, что сбивает с толку, потому что в зависимости от компилятора / платформы может быть 4 или 8 байтов, мы предполагаем 8 байтов на данный момент.
  • char s[16], легкий, 16 байтов

Таким образом, наибольшее количество байтов, которое занимает любой член, это ваша переменная char s[16], 16 байтов. Это означает, что вам нужно изменить код на:

int data_size;
int union_size = 16;
CUdeviceptr d_array;
// copying this to the device will not result in what you expect with out over allocating
// if you just copy over integers, which occupy 4 bytes each, your integers will fill less space than the number of unions 
//  we need to make sure that there is a "stride" here if we want to actually copy real data from host to device. 
// union_size / Sizeof.INT = 4, so there will be 4 x as many ints, 4 for each union. 
int[] h_array = new int[data_size * (union_size / Sizeof.INT)];


// here we aren't looking for size of int to allocate, but the size of our union. 
cuMemAlloc(d_array, data_size * union_size);
// we are copying, again, data_size * union_size bytes
cuMemcpyHtoD(d_array, Pointer.to(h_array), data_size * union_size);

Примечание

Если вы хотите скопировать целые числа, это в основном означает, что вам нужно будет присваивать каждые 4-е целые числа фактическому целому числу, которое вы хотите для этого индекса.

int 0 равно h_array[0], int 1 равно h_array[4] int 2 равно h_array[8] int n равно h_array[n * 4] и т. Д.

1 голос
/ 26 июня 2019

Я сделал выравнивание и заполнение с небольшим количеством грязного кодирования.Также важно обратить внимание на разницу в байтовом порядке между компиляторами.Кажется, Java хранит байты в формате BIG_ENDIAN .Поэтому здесь я должен был изменить его на LITTLE_ENDIAN , чтобы сделать это.У меня ушло 2 часа на отладку.Вот как это выглядит сейчас:

int data_size;
int union_size = 16;
// Device Array
CUdeviceptr d_array; 
// Host Array
int[] h_array = new int[data_size];
byte[] h_array_bytes = new byte[data_size * union_size];

// Data allocation on GPU memory
cuMemAlloc(d_array, data_size * union_size);

// Alignment and padding
byte[] tempBytes;

for(int i = 0; i < data_size; i++){
    tempBytes = ByteBuffer.allocate(Integer.BYTES).order(ByteOrder.LITTLE_ENDIAN)
                .putInteger(h_array[i]).array();
    int start = i * union_size;
    for(int j = start, k = 0; k < union_size; k++, j++){
        if(k < tempBytes.length){
            h_array_bytes[j] = tempBytes[k];
        } else {
            h_array_bytes[j] = 0;
        }
    }
}
// And then simply do the copy 
cuMemcpyHtoD(d_array, Pointer.to(h_array_bytes), data_size * union_size);
...