使用 Java 和 Nvidia 图形处理器(CUDA)

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

我们订购了一些 CUDA 图形处理器来尝试使用它,而且由于 CUDA 不支持 Java,我想知道从哪里开始。我应该构建一个 JNI 接口吗?我应该使用 JCUDA 还是有其他方法?

我在这个领域没有经验,我希望有人可以指导我做一些事情,这样我就可以开始研究和学习。

118348 次浏览

首先,我将使用 Java 和 CUDA: http://www.jcuda.org/的一个项目

首先,您应该意识到这样一个事实,即 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,以及 ThreadPoolExecutorsForkJoinPools等所有并发基础设施可能给人的印象是,您只需要以某种方式分割工作并将其分布到几个处理器中。在 GPU 上,您可能会遇到更低层次的挑战: 占用、注册压力、共享内存压力、内存合并... ... 仅举几个例子。

然而,当你需要解决一个数据并行的、计算绑定的问题时,GPU 就是最好的选择。


一个一般性的说明: 你特别要求 CUDA。但是我强烈建议您也看一看 OpenCL。它有几个优点。首先,它是一个独立于供应商的开放行业标准,并且 AMD、苹果、英特尔和 NVIDIA 都实现了 OpenCL。此外,在 Java 世界中,对 OpenCL 的支持范围更广。唯一的情况下,我宁愿解决 CUDA 是当你想使用 CUDA 运行时库,如 CUFFT 的 FFT 或 CUBLAS 的 BLAS (矩阵/矢量运算)。尽管有一些方法可以为 OpenCL 提供类似的库,但它们不能直接从 Java 端使用,除非您为这些库创建自己的 JNI 绑定。


您可能还会感兴趣地听到,在2012年10月,OpenJDK HotSpot 小组启动了项目“ Sumatra”: http://openjdk.java.net/projects/sumatra/。该项目的目标是在 JVM 中提供 GPU 支持 直接,并得到 JIT 的支持。当前的状态和第一个结果可以看到在他们的邮件列表在 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 代码的库,然后将其编译为在图形处理器上执行代码的库。该图书馆是在一篇博士论文的背景下开发的,其中包含有关翻译过程的深刻背景信息。

Https://github.com/ochafik/scalacl : 用于 OpenCL 的 Scala 绑定。允许与 OpenCL 并行处理特殊的 Scala 集合。对集合元素调用的函数可以是通常的 Scala 函数(有一些限制) ,然后将其转换为 OpenCL 内核。

语言扩展

Http://www.ateji.com/px/index.html : Java 的语言扩展,允许并行构造(例如,并行 for 循环,OpenMP 风格) ,然后在使用 OpenCL 的 GPU 上执行。不幸的是,这个非常有前途的项目不再维护。

Http://www.habanero.rice.edu/publications.html (JCUDA) : 一个可以将特殊的 Java 代码(称为 JCUDA 代码)翻译成 Java 和 CUDA-C 代码的库,然后可以在图形处理器上编译和执行。然而,这个图书馆似乎并不公开。

Https://www2.informatik.uni-erlangen.de/en/research/javaopenmp/index.html : 用于 OpenMP 构造的 Java 语言扩展,带有一个 CUDA 后端

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 绑定

Http://www.hoopoe-cloud.com/ : 用于 CUDA 的 Java 绑定。似乎不再维护


从我所做的 研究,如果你是针对 Nvidia 图形处理器,并已决定使用 CUDA 超过 OpenCL,我发现了三种方式使用的 CUDA API 在 Java。

  1. JCuda (或替代品)-http://www.jcuda.org/。这似乎是我正在研究的问题的最佳解决方案。许多库,如 CUBLAS,在 JCuda 中都是可用的。但是内核仍然是用 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 Thust 库和其他类似的库可以用来做很多 C/C + + 中的指针管理,也许你应该看看这个。

如果您喜欢 C/C + + 并且不介意指针管理,但是有其他的约束迫使您使用 Java,那么 JNI 可能是最好的方法。尽管如此,如果您的 JNI 方法只是内核命令的包装器,那么您还是可以使用 JCuda。

有一些替代品,如 Cuda4J 和根啤酒,但这些似乎没有得到维护。而在撰写本文时,JCuda 支持 CUDA 10.1。这是最新的 CUDA SDK。

此外,还有一些使用 CUDA 的 Java 库,比如 DeepLearning4j 和 Hadoop,它们可能能够完成您正在寻找的任务,而不需要您直接编写内核代码。不过我还没有深入调查过。

马可13已经提供了一个很好的答案。

如果你正在寻找一种不用实现 CUDA/OpenCL 内核就可以使用 GPU 的方法,我想添加一个参考到 finmath-lib-CUDA- (finmath-lib-GPU-) http://finmath.net/finmath-lib-cuda-extensions/(免责声明: 我是这个项目的维护者)。

该项目提供了一个“向量类”的实现,准确地说,是一个称为 RandomVariable的接口,它提供算术运算和向量约简。有 CPU 和 GPU 的实现。有使用算法微分或平面估值的实现。

GPU 的性能改进目前还很小(但是对于100.000大小的向量,性能改进可能大于10倍)。这是由于内核大小较小。这将在未来的版本中得到改进。

GPU 实现使用了 JCuda 和 JOCL,并且可用于 Nvidia 和 ATI 图形处理器。

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

有关问题的性质和数据的信息不多,因此很难给出建议。但是,建议评估其他解决方案的可行性,这些解决方案可以更容易地与 Java 集成,并支持水平和垂直扩展。我建议首先看看一个名为 Apache Spark https://spark.apache.org/的开源分析引擎,它可以在微软 Azure 上使用,但可能也可以在其他云 IaaS 提供商上使用。如果你坚持使用你的 GPU,那么建议你看看市场上其他支持 GPU 的分析数据库,这些数据库适合你的组织的预算。