将 Java 与 Nvidia GPU (CUDA) 结合使用
我正在开发一个用Java完成的商业项目,它需要巨大的计算能力来计算商业市场。简单的数学运算,但数据量巨大。
我们订购了一些CUDA GPU来试用它,由于CUDA不支持Java,我想知道从哪里开始。我应该构建一个 JNI 接口吗?我应该使用JCUDA还是有其他方法?
我没有这个领域的经验,我希望有人可以指导我做一些事情,这样我就可以开始研究和学习。
我正在开发一个用Java完成的商业项目,它需要巨大的计算能力来计算商业市场。简单的数学运算,但数据量巨大。
我们订购了一些CUDA GPU来试用它,由于CUDA不支持Java,我想知道从哪里开始。我应该构建一个 JNI 接口吗?我应该使用JCUDA还是有其他方法?
我没有这个领域的经验,我希望有人可以指导我做一些事情,这样我就可以开始研究和学习。
首先,您应该意识到CUDA不会自动使计算更快。一方面,因为GPU编程是一门艺术,要把它做好可能非常非常具有挑战性。另一方面,因为GPU仅适用于某些类型的计算。
这听起来可能令人困惑,因为您基本上可以在GPU上计算任何内容。当然,关键点是,你是否能实现良好的加速。这里最重要的分类是问题是否为任务并行或数据并行。粗略地说,第一个指的是几个线程或多或少独立地处理自己的任务的问题。第二个是指许多线程都在做同样的事情的问题 - 但是在数据的不同部分。
后者是GPU擅长的问题:它们有许多内核,所有内核都做同样的事情,但对输入数据的不同部分进行操作。
你提到你有“简单的数学,但有大量的数据”。虽然这听起来像是一个完美的数据并行问题,因此非常适合GPU,但还有另一个方面需要考虑:GPU在理论计算能力(FLOPS,每秒浮点运算)方面快得离谱。但它们经常受到内存带宽的限制。
这导致了另一种问题分类。即问题是否受内存限制或计算限制。
第一个是指为每个数据元素执行的指令数量较少的问题。例如,考虑一个并行向量加法:您必须读取两个数据元素,然后执行单个加法,然后将总和写入结果向量。在 GPU 上执行此操作时,您不会看到加速,因为单个添加不能补偿读取/写入内存的工作量。
第二个术语“计算绑定”是指指令数与内存读取/写入数相比较高的问题。例如,考虑一个矩阵乘法:当 n 是矩阵的大小时,指令数将为 O(n^3)。在这种情况下,可以预期GPU在特定矩阵大小下的性能将优于CPU。另一个例子可能是当许多复杂的三角函数计算(正弦/余弦等)在“很少”数据元素上执行时。
根据经验:您可以假设从“主”GPU内存读取/写入一个数据元素的延迟约为500条指令。
因此,GPU性能的另一个关键点是数据局部性:如果您必须读取或写入数据(在大多数情况下,您必须;-)),那么您应该确保数据尽可能靠近GPU内核。因此,GPU 具有某些内存区域(称为“本地内存”或“共享内存”),这些内存区域的大小通常只有几 KB,但对于即将参与计算的数据尤其有效。
因此,再次强调这一点:GPU编程是一门艺术,仅与CPU上的并行编程远程相关。像Java中的Threads这样的东西,以及所有并发基础设施,如,可能会给人一种印象,你只需要以某种方式拆分你的工作,并将其分布在几个处理器之间。在GPU上,您可能会遇到更低级别的挑战:占用,寄存器压力,共享内存压力,内存合并......仅举几例。ThreadPoolExecutors
ForkJoinPools
但是,当您需要解决数据并行、计算密集型问题时,GPU 就是要走的路。
一般评论:您特别要求CUDA。但我强烈建议你也看看OpenCL。它有几个优点。首先,它是一个独立于供应商的开放行业标准,AMD,Apple,Intel和NVIDIA实现了OpenCL。此外,在Java世界中对OpenCL的支持要广泛得多。我宁愿选择CUDA的唯一情况是当你想要使用CUDA运行时库时,比如CUFFT for FFT或CUBLAS for BLAS(Matrix/Vector operations)。尽管有为 OpenCL 提供类似库的方法,但除非您为这些库创建自己的 JNI 绑定,否则不能直接从 Java 端使用它们。
您可能还会发现有趣的是,在2012年10月,OpenJDK HotSpot小组启动了“苏门答腊岛”项目:http://openjdk.java.net/projects/sumatra/。该项目的目标是直接在 JVM 中提供 GPU 支持,并得到 JIT 的支持。当前状态和第一个结果可以在他们的邮件列表中看到 http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev
但是,不久前,我收集了一些与“GPU上的Java”相关的资源。我将在这里再次总结这些,没有特别的顺序。
(免责声明:我是 http://jcuda.org/ 和 http://jocl.org/ 的作者 )
https://github.com/aparapi/aparapi:由AMD创建并积极维护的开源库。在特殊的“Kernel”类中,可以重写应该并行执行的特定方法。此方法的字节码在运行时使用自己的字节码读取器加载。代码被转换为 OpenCL 代码,然后使用 OpenCL 编译器进行编译。然后,可以在 OpenCL 设备(可以是 GPU 或 CPU)上执行结果。如果无法编译到 OpenCL 中(或者没有可用的 OpenCL),则仍将使用线程池并行执行代码。
https://github.com/pcpratts/rootbeer1:一个开源库,用于将Java的一部分转换为CUDA程序。它提供了专用接口,可以实现这些接口以指示应在GPU上执行某个类。与 Aparapi 相反,它尝试自动将“相关”数据(即对象图的完整相关部分!)序列化为适合 GPU 的表示形式。
https://code.google.com/archive/p/java-gpu/ :一个用于将带注释的Java代码(有一些限制)转换为CUDA代码的库,然后将其编译为在GPU上执行代码的库。该图书馆是在博士论文的背景下开发的,该论文包含有关翻译过程的深刻背景信息。
https://github.com/ochafik/ScalaCL:OpenCL的Scala绑定。允许与 OpenCL 并行处理特殊的 Scala 集合。在集合的元素上调用的函数可以是通常的 Scala 函数(有一些限制),然后将其转换为 OpenCL 内核。
http://www.ateji.com/px/index.html:Java的语言扩展,允许并行构造(例如并行循环,OpenMP样式),然后使用OpenCL在GPU上执行。不幸的是,这个非常有前途的项目不再得到维护。
http://www.habanero.rice.edu/Publications.html(JCUDA):一个可以将特殊Java代码(称为JCUDA代码)转换为Java和CUDA-C代码的库,然后可以在GPU上编译和执行。但是,该库似乎没有公开可用。
https://www2.informatik.uni-erlangen.de/EN/research/JavaOpenMP/index.html:用于 OpenMP 构造的 Java 语言扩展,带有 CUDA 后端
https://github.com/ochafik/JavaCL: OpenCL 的 Java 绑定:一个面向对象的 OpenCL 库,基于自动生成的低级绑定
http://jogamp.org/jocl/www/:OpenCL的Java绑定:一个面向对象的OpenCL库,基于自动生成的低级绑定
http://www.lwjgl.org/:OpenCL 的 Java 绑定:自动生成的低级绑定和面向对象的便利类
http://jocl.org/: OpenCL 的 Java 绑定:低级绑定,它是原始 OpenCL API 的 1:1 映射
http://jcuda.org/:CUDA 的 Java 绑定:原始 CUDA API 的 1:1 映射的低级绑定
http://sourceforge.net/projects/jopencl/:OpenCL的Java绑定。自2010年以来似乎不再维护
http://www.hoopoe-cloud.com/:CUDA 的 Java 绑定。似乎不再维护
根据我所做的研究,如果您的目标是Nvidia GPU并决定使用CUDA而不是OpenCL,我发现了三种在java中使用CUDA API的方法。
所有这些答案基本上只是在Java中使用C / C++代码的方法。你应该问问自己为什么需要使用Java,如果你不能用C / C++来代替。
如果你喜欢Java并且知道如何使用它,并且不想使用C / C++附带的所有指针管理和什么,那么JCuda可能是答案。另一方面,CUDA Thrust库和其他类似的库可以用来在C / C++中做很多指针管理,也许你应该看看这一点。
如果你喜欢 C/C++ 并且不介意指针管理,但是还有其他约束迫使你使用 Java,那么 JNI 可能是最好的方法。但是,如果你的 JNI 方法只是内核命令的包装器,那么你最好只使用 JCuda。
JCuda有一些替代品,如Cuda4J和Root Beer,但这些似乎没有得到维护。而在撰写本文时,此 JCuda 支持 CUDA 10.1。这是最新的 CUDA SDK。
此外,还有一些使用CUDA的java库,如deeplearning4j和Hadoop,它们可能能够完成你正在寻找的东西,而不需要你直接编写内核代码。不过,我没有研究过它们。