将Java与Nvidia GPU(CUDA)结合使用


144

我正在做一个用Java完成的业务项目,它需要巨大的计算能力才能计算业务市场。简单的数学运算,但具有大量数据。

我们订购了一些CUDA GPU进行尝试,并且由于CUDA不支持Java,所以我想知道从哪里开始。我应该建立一个JNI接口吗?我应该使用JCUDA还是其他方法?

我没有这方面的经验,我想知道是否有人可以指导我从事某些工作,以便我可以开始研究和学习。


2
GPU将帮助您加速特定类型的计算密集型问题。但是,如果您有大量数据,则很有可能受到IO约束。GPU很可能不是解决方案。
史蒂夫·库克

1
“促进使用GPGPUs Java性能” - > arxiv.org/abs/1508.06791
BlackBear

4
有点悬而未决的问题,我很高兴mods没有关闭它,因为Marco13的答案非常有帮助!应该是维基恕我直言
JimLohse

Answers:


442

首先,您应该意识到CUDA不会自动加快计算速度这一事实。在一方面,由于GPU编程是一门艺术,它可以是非常,非常具有挑战性得到它的权利。另一方面,因为GPU仅适合某些类型的计算。

这听起来可能令人困惑,因为您基本上可以在GPU上进行任何计算。关键当然是您是否会实现良好的加速。这里最重要的分类是问题是任务并行还是数据并行。粗略地说,第一个问题是几个线程正在或多或少地独立执行自己的任务的问题。第二个问题涉及许多线程都在做相同的事情 -但是在数据的不同部分上的问题。

后者就是GPU擅长的问题:GPU有很多内核,所有内核都做同样的事情,但是在输入数据的不同部分上运行。

您提到您拥有“简单的数学但有大量数据”。尽管这听起来像是一个完美的数据并行问题,并且因此非常适合GPU,但还需要考虑另一个方面:GPU的理论计算能力(FLOPS,每秒浮点运算)非常快。但是它们经常受内存带宽的限制。

这导致了另一种问题分类。即问题是受内存限制还是受计算限制

第一个是针对每个数据元素执行的指令数量很少的问题。例如,考虑并行向量加法:您必须两个数据元素,然后进行单次加入,然后将总和结果向量。在GPU上执行此操作时不会看到加速,因为单次添加不会补偿读取/写入内存的工作量。

第二个术语“计算边界”是指指令数量比存储器读/写数量多的问题。例如,考虑矩阵乘法:当n是矩阵的大小时,指令数将为O(n ^ 3)。在这种情况下,可以预期GPU在某种矩阵大小下将胜过CPU。另一个示例可能是在“很少”的数据元素上执行许多复杂的三角计算(正弦/余弦等)时。

根据经验:您可以假设从“主” GPU内存中读取/写入一个数据元素的延迟约为500条指令...。

因此,GPU性能的另一个关键点是数据局部性:如果您必须读取或写入数据(在大多数情况下,您必须; ;-)),则应确保数据保持尽可能近的距离。可能的GPU核心。因此,GPU具有某些内存区域(称为“本地内存”或“共享内存”),通常只有几KB大小,但是对于将要包含在计算中的数据特别有效。

因此,再次强调这一点:GPU编程是一门艺术,它仅与CPU上的并行编程密切相关。Java中的诸如Threads之类的东西,以及诸如等等的所有并发基础结构ThreadPoolExecutorsForkJoinPools可能给人的印象是,您只需要以某种方式拆分工作并将其分配到多个处理器中即可。在GPU上,您可能会遇到更低的挑战:占用率,寄存器压力,共享内存压力,内存合并……仅举几例。

但是,当您要解决数据并行,计算受限的问题时,GPU是可行的方法。


一般说明:您特别要求CUDA。但我强烈建议您也来看看OpenCL。它有几个优点。首先,它是独立于供应商的开放行业标准,并且由AMD,Apple,Intel和NVIDIA实施OpenCL。此外,Java世界中对OpenCL的支持更加广泛。我唯一希望使用CUDA的情况是,当您想使用CUDA运行时库时,例如CUFFT用于FFT或CUBLAS用于BLAS(矩阵/矢量运算)。尽管有为OpenCL提供类似库的方法,但是除非您为这些库创建自己的JNI绑定,否则不能直接从Java端使用它们。


您可能还会发现,在2012年10月,OpenJDK HotSpot组启动了“ Sumatra”项目:http//openjdk.java.net/projects/sumatra/。该项目的目标是在JIT的支持下直接在JVM中提供GPU支持。当前状态和最初结果可以在其邮件列表中找到,网址http://mail.openjdk.java.net/mailman/listinfo/sumatra-dev


但是,不久前,我收集了一些与“ Java on the GPU”有关的资源。我将在这里以没有特定顺序的方式再次总结这些内容。

免责声明:我是http://jcuda.org/http://jocl.org/的作者)

(字节)代码转换和OpenCL代码生成:

https://github.com/aparapi/aparapi:由AMD创建并积极维护的开源库。在一个特殊的“内核”类中,可以重写应并行执行的特定方法。此方法的字节码在运行时使用自己的字节码读取器加载。该代码被翻译成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绑定。允许特殊的Scala集合与OpenCL并行处理。在集合的元素上调用的函数可以是常用的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/CN/research/JavaOpenMP/index.html:适用于具有CUDA后端的OpenMP构造的Java语言扩展

Java OpenCL / 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绑定。似乎不再维护



考虑将两个矩阵相加并将结果存储在第三个矩阵中的操作。如果在没有OpenCL的情况下在CPU上使用多线程,则瓶颈始终是添加操作的步骤。显然,此操作是数据并行的。但是可以说我们不知道是预先绑定计算还是内存绑定。实施需要花费大量时间和资源,然后才能看到CPU在执行此操作方面要好得多。因此,如何在不实施OpenCL代码的情况下预先识别这一点。
Cool_Coder 2014年

2
@Cool_Coder确实,很难事先告知某项任务是否(或多少)可​​以从GPU实现中受益。乍一看,一个人可能需要使用不同用例的经验(我也承认确实没有)。第一步可能是查看nvidia.com/object/cuda_showcase_html.html并查看是否列出了“类似”问题。(它是CUDA,但从概念上讲它与OpenCL如此接近,因此在大多数情况下都可以传输结果)。在大多数情况下,还提到了提速,其中许多都具有指向论文甚至代码的链接
Marco13 2014年

+1为aparapi-它是Java中开始使用opencl的简单方法,并允许您在简单情况下轻松比较CPU与GPU的性能。此外,它由AMD维护,但可以与Nvidia卡配合使用。
史蒂夫·库克

12
这是我在StackOverflow上见过的最好的响应之一。感谢您的时间和精力!
ViggyNash

1
@AlexPunnen这可能超出了评论的范围。据我所知,从docs.opencv.org/2.4/modules/gpu/doc/introduction.html开始,OpenCV具有一些CUDA支持。该developer.nvidia.com/npp有许多图像处理程序,这可能是得心应手。而github.com/GPUOpen-ProfessionalCompute-Tools/HIP可能是一个“另类”的CUDA。它或许可以问这是一个新的问题,但是你要小心这样的措辞得当,以避免downvotes为“基于意见” /“要求第三方库” ...
Marco13


2

根据我所做的研究,如果您针对的是Nvidia GPU,并且已决定将CUDA用于 OpenCL,我发现了三种在Java中使用CUDA API的方法。

  1. JCuda(或替代)- //www.jcuda.org/。这似乎是我正在解决的问题的最佳解决方案。JCuda中提供了许多库,例如CUBLAS。内核仍然是用C编写的。
  2. JNI-JNI接口不是我最喜欢编写的,但功能非常强大,可以让您做CUDA可以做的任何事情。
  3. JavaCPP-这基本上使您可以用Java制作JNI接口,而无需直接编写C代码。这里有一个示例:用Java运行可运行的CUDA代码的最简单方法是什么?如何在CUDA推力中使用它。在我看来,您似乎最好只编写一个JNI接口。

所有这些答案基本上都是在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,它们可以执行您正在寻找的内容,而无需直接编写内核代码。不过,我没有对它们进行过多研究。


1

Marco13已经提供了一个很好的答案

如果您正在寻找不使用CUDA / OpenCL内核而使用GPU的方法,我想添加对finmath-lib-cuda-extensions(finmath-lib-gpu-extensions)的引用http:// finmath .net / finmath-lib-cuda-extensions /(免责声明:我是该项目的维护者)。

确切地说,该项目提供了“向量类”的实现,即一个名为的接口,该接口RandomVariable提供了算术运算并减少了向量。有针对CPU和GPU的实现。有一些使用算法差异或简单估值的实现方式。

目前,GPU上的性能提升很小(但是对于大小为100.000的矢量,您可能会获得> 10的性能提升)。这是由于内核较小。这将在将来的版本中改进。

GPU实现使用JCuda和JOCL,并且可用于Nvidia和ATI GPU。

该库是Apache 2.0,可通过Maven Central获得。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.