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



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

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

(这里是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 代码是使用相同的编译器编译的,因此没有太多的空间可供 musinderstanding 使用。主机端说"这里有一些内存,将其解释为Vertex对象",内核将接收相同的内存并使用它。

尽管如此,即使在普通 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
};

可以这样做,以确保结构与 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 相比,它会很不方便(并且没有达到非常好的性能)

相关内容

  • 没有找到相关文章

最新更新