如何在JCuda中将结构传递给内核

时间:2015-06-15 05:39:12

标签: java struct cuda java-native-interface jcuda

我已经看过这个http://www.javacodegeeks.com/2011/10/gpgpu-with-jcuda-good-bad-and-ugly.html,它说我必须修改我的内核才能只采用单维数组。但是我拒绝相信在JCuda中创建结构并将其复制到设备内存是不可能的。

我认为通常的实现是创建一个扩展一些原生api的case类(scala术语),然后可以将其转换为可以安全地传递给内核的结构。不幸的是,我还没有在谷歌上找到任何东西,因此这个问题。

1 个答案:

答案 0 :(得分:4)

(JCuda的作者在这里(不是#34; JCUDA",请))

正如评论中链接的论坛帖子所述:在CUDA内核中使用结构并从JCuda端填充它们并非不可能。这只是非常复杂,而且很少有益。

由于为什么在GPU编程中根本不使用结构体的原因,你必须参考你在搜索

之间的差异时会发现的结果。
  

"结构阵列"与#34;阵列的结构"。

通常,由于改进的内存合并,后者是GPU计算的首选,但这超出了我在这个答案中可以深刻总结的内容。在这里,我将总结一下为什么在GPU计算中使用结构通常有点困难,在JCuda / Java中尤其困难。

在简单的C中,关于内存布局,结构(理论上!)非常简单。想象一下像

这样的结构
struct Vertex {
    short a;
    float x;
    float y;
    float z;
    short b;
};

现在您可以创建这些结构的数组:

Vertex* vertices = (Vertex*)malloc(n*sizeof(Vertex));

这些结构将保证被布局为一个连续的内存块:

            |   vertices[0]      ||   vertices[1]      |
            |                    ||                    |
vertices -> [ a|  x |  y |  z | b][ a|  x |  y |  z | b]....

由于CUDA内核和C代码是使用相同的编译器编译的,因此没有太多的理解空间。主持人说"这是一些内存,将其解释为Vertex个对象",内核将收到相同的内存并使用它。

尽管如此,即使在简单的C中,实际上也存在一些意外问题的可能性。编译器通常会在这些结构中引入 paddings ,以实现某些对齐。因此,示例结构实际上可能具有如下布局:

struct Vertex {
    short a;        // 2 bytes
    char PADDING_0  // Padding byte
    char PADDING_1  // Padding byte
    float x;        // 4 bytes
    float y;        // 4 bytes
    float z;        // 4 bytes
    short b;        // 2 bytes
    char PADDING_2  // Padding byte
    char PADDING_3  // Padding byte
};

这样的事情可能是为了确保结构与32位(4字节)字边界对齐。此外,某些编译指示和编译器指令可能会影响此对齐。 CUDA还更喜欢某些内存对齐,因此这些指令在CUDA头文件中使用很多。

简而言之:当您在C中定义struct,然后将sizeof(YourStruct)(或结构的实际布局)打印到控制台时,您将拥有很难预测实际打印的内容。期待一些惊喜。

在JCuda / Java中,世界是不同的。根本没有struct。当您创建类似

的Java类时
class Vertex {
    short a;
    float x;
    float y;
    float z;
    short b;
}

然后创建一个这些

的数组
Vertex vertices[2] = new Vertex[2];
vertices[0] = new Vertex();
vertices[1] = new Vertex();

然后这些Vertex对象可能会分散在内存中。你甚至不知道一个Vertex对象有多大,几乎无法找到它。因此,试图在JCuda中创建一个结构数组并将其传递给CUDA内核根本没有意义。

然而,如上所述:在某种形式下,它仍然是可能的。 如果你知道你的结构将在CUDA内核中拥有的内存布局,那么你可以创建一个兼容"的内存块。使用这种结构布局,并从Java端填充它。对于像上面提到的struct Vertex这样的内容,这可能粗略(涉及一些伪代码)如下所示:

// 1 short + 3 floats + 1 short, no paddings
int sizeOfVertex = 2 + 4 + 4 + 4 + 2; 

// Allocate data for 2 vertices
ByteBuffer data = ByteBuffer.allocateDirect(sizeOfVertex * 2);

// Set vertices[0].a and vertices[0].x and vertices[0].y
data.position(0).asShortBuffer().put(0, a0);
data.position(2).asFloatBuffer().put(0, x0);
data.position(2).asFloatBuffer().put(1, y0);

// Set vertices[1].a and vertices[1].x and vertices[1].y
data.position(sizeOfVertex+0).asShortBuffer().put(0, a1);
data.position(sizeOfVertex+2).asFloatBuffer().put(0, x1);
data.position(sizeOfVertex+2).asFloatBuffer().put(1, y1);

// Copy the Vertex data to the device
cudaMemcpy(deviceData, Pointer.to(data), cudaMemcpyHostToDevice);

它基本上归结为将内存保持在ByteBuffer,并且手动访问与所需结构的所需字段对应的内存区域。

然而,警告:您必须考虑在几个CUDA-C编译器版本或平台之间无法完全移植的可能性。在32位Linux机器上编译内核(包含struct定义)一次,在64位Windows机器上编译一次,结构布局可能不同(和您的Java代码)必须要注意这一点。)

(注意:可以定义接口来简化这些访问。对于JOCL,我尝试创建的实用程序类感觉更像C结构并在某种程度上自动化复制过程。但是无论如何,与普通C相比,它会带来不便(并没有达到真正好的表现)