如何将结构传递给 JCuda 中的内核
How can I pass a struct to a kernel in JCuda
我已经看过这个 http://www.javacodegeeks.com/2011/10/gpgpu-with-jcuda-good-bad-and-ugly.html,它说我必须修改我的内核以仅采用一维数组。但是我拒绝相信不可能在 JCuda 中创建一个结构并将其复制到设备内存。
我想通常的实现是创建一个 case class(scala 术语)扩展一些原生 api,然后可以将其转换为可以安全传递的结构内核。不幸的是,我在 google 上没有找到任何东西,因此才有了这个问题。
(这里是JCuda的作者(不是"JCUDA",拜托))
正如论坛 post 中提到的,链接自评论:在 CUDA 内核中使用结构并从 JCuda 端填充它们并非不可能。它只是非常复杂,而且很少有用。
由于在 GPU 编程中使用结构很少有好处,您将不得不参考搜索
之间的差异时会找到的结果
"Array Of Structures" versus "Structure Of Arrays".
通常,由于改进了内存合并,后者更适合 GPU 计算,但这超出了我在此答案中可以深刻总结的范围。在这里,我只总结一下为什么在GPU计算中使用struct总体来说有点难,特别是在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代码是用同一个编译器编译的,所以没有太大的理解余地。主机端说 "Here is some memory, interpret this as Vertex
objects",内核将接收相同的内存并使用它。
尽管如此,即使是在纯 C 中,实际上也存在一些意想不到的问题的可能性。编译器通常会在这些结构中引入 paddings,以实现某些 alignments。因此,示例结构实际上可能具有如下布局:
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 headers 中被大量使用。
简而言之:当您在 C 中定义一个 struct
,然后将 sizeof(YourStruct)
(或结构的实际 layout)打印到控制台,您将很难预测它实际打印的内容。期待一些惊喜。
在JCuda/Java,世界不一样了。根本就没有 struct
s。当你创建一个 Java class like
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
object可能会随意分散在内存中。你甚至不知道 Vertex
object 有多大,也很难找到它。因此,尝试在 JCuda 中创建结构数组并将其传递给 CUDA 内核根本没有意义。
但是,如上所述:仍然有可能,以某种形式。 如果您知道您的结构在 CUDA 内核中的内存布局,那么您可以使用此结构布局创建一个 "compatible" 的内存块,并从 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 机器上和 64 位 Windows 机器上编译一次内核(包含 struct
定义)时,结构布局 可能 不同(你的 Java 代码必须意识到这一点)。
(注意:可以定义接口来简化这些访问。对于 JOCL,我尝试创建实用程序 classes,感觉有点像 C 结构和在某种程度上自动化复制过程。但无论如何,与普通 C 相比,它会很不方便(并且没有实现真正好的性能)
我已经看过这个 http://www.javacodegeeks.com/2011/10/gpgpu-with-jcuda-good-bad-and-ugly.html,它说我必须修改我的内核以仅采用一维数组。但是我拒绝相信不可能在 JCuda 中创建一个结构并将其复制到设备内存。
我想通常的实现是创建一个 case class(scala 术语)扩展一些原生 api,然后可以将其转换为可以安全传递的结构内核。不幸的是,我在 google 上没有找到任何东西,因此才有了这个问题。
(这里是JCuda的作者(不是"JCUDA",拜托))
正如论坛 post 中提到的,链接自评论:在 CUDA 内核中使用结构并从 JCuda 端填充它们并非不可能。它只是非常复杂,而且很少有用。
由于在 GPU 编程中使用结构很少有好处,您将不得不参考搜索
之间的差异时会找到的结果"Array Of Structures" versus "Structure Of Arrays".
通常,由于改进了内存合并,后者更适合 GPU 计算,但这超出了我在此答案中可以深刻总结的范围。在这里,我只总结一下为什么在GPU计算中使用struct总体来说有点难,特别是在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代码是用同一个编译器编译的,所以没有太大的理解余地。主机端说 "Here is some memory, interpret this as Vertex
objects",内核将接收相同的内存并使用它。
尽管如此,即使是在纯 C 中,实际上也存在一些意想不到的问题的可能性。编译器通常会在这些结构中引入 paddings,以实现某些 alignments。因此,示例结构实际上可能具有如下布局:
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 headers 中被大量使用。
简而言之:当您在 C 中定义一个 struct
,然后将 sizeof(YourStruct)
(或结构的实际 layout)打印到控制台,您将很难预测它实际打印的内容。期待一些惊喜。
在JCuda/Java,世界不一样了。根本就没有 struct
s。当你创建一个 Java class like
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
object可能会随意分散在内存中。你甚至不知道 Vertex
object 有多大,也很难找到它。因此,尝试在 JCuda 中创建结构数组并将其传递给 CUDA 内核根本没有意义。
但是,如上所述:仍然有可能,以某种形式。 如果您知道您的结构在 CUDA 内核中的内存布局,那么您可以使用此结构布局创建一个 "compatible" 的内存块,并从 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 机器上和 64 位 Windows 机器上编译一次内核(包含 struct
定义)时,结构布局 可能 不同(你的 Java 代码必须意识到这一点)。
(注意:可以定义接口来简化这些访问。对于 JOCL,我尝试创建实用程序 classes,感觉有点像 C 结构和在某种程度上自动化复制过程。但无论如何,与普通 C 相比,它会很不方便(并且没有实现真正好的性能)