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

Java 如何在JCuda中将结构传递给内核,java,struct,cuda,java-native-interface,jcuda,Java,Struct,Cuda,Java Native Interface,Jcuda,我已经看过这篇文章了,它说我必须修改我的内核,只接受一维数组。然而,我拒绝相信在JCuda中创建一个结构并将其复制到设备内存是不可能的 我认为通常的实现是创建一个case类(scala术语),该类扩展一些本机api,然后可以将其转换为可以安全地传递到内核的结构。不幸的是,我没有在谷歌上找到任何东西,因此我提出了这个问题。(这里是JCuda的作者(请不要叫“JCuda”) 正如评论中链接的论坛帖子所述:在CUDA内核中使用结构并从JCuda端填充它们并非不可能。这是非常复杂的,很少有好处 由于在G

我已经看过这篇文章了,它说我必须修改我的内核,只接受一维数组。然而,我拒绝相信在JCuda中创建一个结构并将其复制到设备内存是不可能的

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

(这里是JCuda的作者(请不要叫“JCuda”)

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

由于在GPU编程中使用结构很少有好处,因此您必须参考在搜索不同结构之间的差异时找到的结果

“结构阵列”与“阵列结构”

通常情况下,后者是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代码是用同一个编译器编译的,因此没有太多的音乐理解空间。主机端说“这里有一些内存,将其解释为
顶点
对象”,内核将接收相同的内存并使用它

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

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
};
// 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);
这样做是为了确保结构与32位(4字节)字边界对齐。此外,某些pragmas和编译器指令可能会影响这种对齐。CUDA另外更喜欢某些内存对齐,因此这些指令在CUDA头中大量使用

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


在JCuda/Java中,世界是不同的。根本没有
struct
s。当您创建一个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();
然后这些
顶点
对象可能会在内存中任意分散。您甚至不知道一个
顶点
对象有多大,也很难找到它。因此,试图在JCuda中创建一个结构数组并将其传递给CUDA内核是没有意义的


然而,如上所述:它仍然是可能的,以某种形式如果您知道您的结构在CUDA内核中的内存布局,那么您可以创建一个与此结构布局“兼容”的内存块,并从Java端填充它。对于上面提到的
struct Vertex
之类的东西,这可能大致如下(涉及一些伪代码):

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
};
// 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);
它基本上归结为将内存保存在
字节缓冲区中,并手动访问与所需结构的所需字段相对应的内存区域

但是,<强>警告< /强>:您必须考虑在几个CUDA—C编译器版本或平台之间不完全可移植的可能性。当您在32位Linux机器和64位Windows机器上编译内核(包含

struct
定义)一次时,结构布局可能会有所不同(您的Java代码必须知道这一点)

(注意:可以定义接口来简化这些访问。例如,我尝试创建一些更像C结构的实用程序类,并在某种程度上自动化复制过程。但无论如何,与普通C相比,这将是不方便的(并且没有实现真正好的性能)

(这里是JCuda的作者(不是“JCuda”),请

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

由于在GPU编程中使用结构很少有好处,因此您必须参考在搜索不同结构之间的差异时找到的结果

“结构阵列”与“阵列结构”

通常情况下,后者是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代码是用同一个编译器编译的,因此没有太多的音乐理解空间。主机端说“这里有一些内存,将其解释为
顶点
对象”,内核将接收相同的内存并使用它

尽管如此,即使是在平原C,也有在实践中