使用 CUDA、Eclipse 和 Java(配合 JCuda)进行 GPU 计算






4.71/5 (8投票s)
教程:使用 JCuda 和 Nsight (Eclipse) 进行 GPU 计算
介绍
PC 配备了一个极其强大的设备:图形处理单元(GPU)。它通常被严重低估,大部分时间只用于渲染桌面。然而,当程序中的大部分可以并行运行时,与传统的 CPU 处理相比,在 GPU 上进行计算会快得惊人。其应用领域似乎无穷无尽,包括:矩阵计算、信号变换、随机数生成、分子建模和密码恢复。为什么 GPU 如此高效?因为它们有数百甚至数千个核心可用于并行处理。与当今 PC 上通常的一到四个 CPU 核心相比,这是一个巨大的优势。(更技术性的探讨请参见:graphics.stanford.edu/~mhouston/public_talks/cs448-gpgpu.pdf
在这里,我将介绍一种方法,利用 NVidia 的支持 Cuda 的 GPU 的强大功能,通过 Java 和基于 Eclipse 的 IDE 来进行计算。我的平台是 Debian Wheezy(64 位和 32 位),但我也在 Linux Mint 13 上成功复现了该过程,并且可以在许多其他 Linux 发行版上实现。该方法也可以适应 Windows 安装,这方面的内容 elsewhere 有详细文档。
更新说明
这是对原文章的 2013 年 9 月更新。自从撰写本文以来,涌现了许多新的发展,特别是在 Linux 上安装 NVidia 开发驱动程序的过程中。随着发行版的不断演进,禁用 Nouveau 驱动程序(安装 NVidia 驱动程序的先决条件)变得越来越困难。此外,有时发行版自带的编译器(gcc)与编译操作系统内核本身所用的编译器不同。最后,使用 NVidia Optimus 技术的 Linux 系统需要额外的技巧来配置驱动程序。
背景
为了方便地利用 GPU 的强大功能进行通用计算,需要一个 GPU 编程实用工具,它能暴露一套高级方法,并为我们处理所有底层的、硬件级别的细致工作。常见的选择是 OpenGl 和 Cuda。Cuda 仅适用于 NVidia GPU。我偏爱 NVidia 设备,本文将提供一个 Cuda 解决方案。
Eclipse 是我最喜欢的 Java、C++ 和 PHP 编程 IDE。NVidia 提供了一个名为 Nsight 的基于 Eclipse 的 IDE,它预先配置用于 Cuda C++ 开发。其他功能,如 Java、PHP 等,可以从兼容的 Eclipse 软件存储库添加到您的 Nsight 安装中(例如,Nsight 5.5 与 Eclipse Juno 存储库兼容)。
直接使用 Cuda 进行编程需要使用非托管 C++ 代码。我偏爱使用托管代码进行编程。为此,我使用一种方法,将 Cuda 的 C++ 功能封装在 Java 可以访问的绑定中。过去,在 Windows 7 平台上,我编写了自己的 C#.net 代码的包装器(参见我的 CodeProject 文章)。对于 Java,这并非必需,因为有开源包装器可用。我使用的是 JCuda。
这里将介绍四个基本要素
- 确定您是否拥有兼容的 GPU
- 安装/配置 Cuda
- 配置 Nsight 以支持 Java
- 使用 JCuda
有时教程会介绍作者在已经具备某些先决配置的现有生产机器上遵循的步骤。因此,当读者遵循这些步骤时,程序可能会失败。为了避免这种情况,我从 Mint 13_64 位、Linux Mint 13_32 位、Debian Wheezy x32 和 Debian Wheezy x64 的全新安装开始测试了以下过程。对于 Mint,两种情况我都选择了 Mate 版本。以下是我演示机器的详细信息:
- Mint 13-Mate x64 和 Debian Wheezy x64 用于我的带有 GeForce GTX 560 Ti GPU 的 AMD 64 位机器
- Mint 13-Mate x32 和 Debian Wheezy x32 用于我的带有 Quadro NVS 160M GPU 的 Intel 32 位机器)
- 全新的操作系统安装都已通过更新管理器完全更新。
- 除了 gedit(用于编写本教程的标准化)之外,没有添加任何其他软件。
- 在测试之前,没有进行任何其他硬件配置。
特别注意事项
本项目的发行版均明确选择了稳定、长期服务(LTS)版本。短期版本经常会更改某些基本硬件配置和文件系统布局。在审查和参与了数百个 Linux 论坛帖子后,我确信您会发现这样做会减少很多麻烦。
在 Linux 系统上,使用 NVidia Optimus 技术的系统会带来配置上的复杂性。简单来说,不需要 NVidia GPU 高性能的任务会被委托给一个低性能、低功耗的 GPU,通常是 Intel 设备。这个过程在 Linux 机器上目前实现得并不好。但,它可以正常工作!如果幸运的话,您的机器会在 BIOS 设置中提供禁用 Optimus 集成的选项,但许多 PC 制造商并不提供此选项。这时就需要 Bumblebee,这是一个允许您为给定应用程序指定使用哪个 GPU 的程序。由于我没有在 Optimus 系统上进行过测试,因此这里不提供 Optimus 启用 GPU 的详细信息,您需要自行研究 Bumblebee 的相关设置。之后,当您配置 Eclipse 以使用 JCuda 时,据我所知,Eclipse(和 Nsight)可以通过 optirun eclipse 运行,并且将使用正确的 GPU 来调试您的程序。 以下是一些有前景的资源: http://forums.linuxmint.com/viewtopic.php?f=47&t=144049(帖子 # 7) 和http://bumblebee-project.org/install.html
计算密集型应用程序,例如傅里叶变换,无论是在 CPU 还是 GPU 上执行,都会给您的系统带来压力测试。从小处着手,并在计算量大的时候监控系统温度。
安装
步骤 1:您是否有兼容的 GPU?
NVidia 在其开发者中心网站上有一个详尽的 CUDA 兼容 GPU 列表:http://developer.NVidia.com/Cuda-gpus。检查您的 GPU 是否在此列表中。另外,确定您的机器是否使用 NVidia Optimus 技术,如果使用,请参阅上面的说明。
步骤 2:安装依赖项:
有一些先决条件。从终端,运行以下命令获取它们:
- sudo apt-get update
- sudo apt-get install -y linux-headers-$(uname -r)
- sudo apt-get install freeglut3-dev build-essential libx11-dev libxmu-dev libxi-dev libgl1-mesa-glx libglu1-mesa libglu1-mesa-dev gcc
步骤 3:下载 CUDA 生产发布版并安装
从以下网址下载最新的 CUDA 版本:https://developer.NVidia.com/Cuda-downloads。(注意:NVidia 网站仅显示基于 Debian 的发行版(如 Mint)的 Ubuntu 版本。Ubuntu 的 CUDA 版本在 Mint LTS 13 和 Debian Wheezy 上运行良好。)选择正确的 32/64 位选项,并优先选择 .run 文件而非 .deb 文件。我最近下载的是 cuda_5.5.22_linux_32.run(或 cuda_5.5.22_linux_64.run)。
将安装程序分解为三个组件安装脚本:工具包、驱动程序和示例。这种精细的控制在大规模问题发生时非常有益。以下是分解安装程序的语法。
sh cuda_5.5.22_linux_32.run -extract=<目标路径>
或
sh cuda_5.5.22_linux_64.run -extract=<目标路径>
将生成以下三个文件:
- NVidia-Linux-x86-319.37.run 或 NVidia-Linux-x64-319.37.run(又名开发者驱动程序)
- cuda-linux-rel-5.5.22-16488124.run(又名工具包)
- cuda-samples-linux-5.5.22-16488124.run
安装开发者驱动程序
我们首先安装 NVidia 开发者驱动程序。这一步是 Linux 用户遇到最多麻烦的地方,因为它在不同发行版之间差异很大。在进行任何操作之前;打印此页面,保存您的工作,并确保您已备份。
安装开发者驱动程序时不能有 X 服务器在运行。进行初步测试,确保您可以切换到控制台并停止 X 服务器。同时按下 [ctrl][alt][f2]。如果幸运的话,您的桌面会显示一个提示您登录的控制台。如果是这样,请登录并停止显示管理器:
- sudo service mdm stop (用于 Mint 桌面)
- sudo service gdm3 stop (用于 Gnome 3 桌面)
- sudo service lightdm stop (用于 xfce 桌面)
现在您应该能看到控制台。如果看到空白屏幕,请再次按下 [ctrl]+[alt]+[f2]。现在您可以选择 sudo reboot 或 startx 返回到桌面。如果此测试失败,则应安装您的包管理器中的 NVidia 非自由驱动程序,然后重试……尽管在后续步骤中我们将要移除它。
Debian 及其衍生版使用一个名为 nouveau 的默认驱动程序,这是一个优秀的、开源的 NVidia GPU 解决方案,但它与 NVidia CUDA 开发完全不兼容。必须在启动时禁用它。一种方法是修改 grub:
gksu gedit /etc/default/grub
找到显示:“GRUB_CMDLINE_LINUX_DEFAULT=...”的行,并将其更改为:
GRUB_CMDLINE_LINUX_DEFAULT="quiet nouveau.modeset=0"
保存文件,关闭 gedit,然后运行:
sudo update-grub
sudo reboot
另一种更保守的方法是中断 grub 引导加载程序,并手动插入 nouveau.modeset=0 短语作为一次性启动选项。为此,您的 grub 配置必须有一个超时设置,允许您查看 grub 菜单。在 grub 菜单中,突出显示您的默认启动选项,然后按 e 进入 grub 命令行。找到显示“Linux ...”的行,并在行末添加 nouveau.modeset=0。按 [cntl][x] 启动。 如果您使用此方法,在驱动程序安装并移除 nouveau 之前重启,您需要重复此过程。以下是一个在 Mint 发行版上展示基本思路的参考:http://community.linuxmint.com/tutorial/view/842接下来,编辑您的黑名单配置文件(gksu gedit /etc/modprobe.d/blacklist.conf),并在末尾添加以下行:
- blacklist amd76x_edac
- blacklist vga16fb
- blacklist nouveau
- blacklist rivafb
- blacklist NVidiafb
- blacklist rivatv
然后,使用以下命令从系统中移除所有 NVidia 相关项:
sudo apt-get remove --purge NVidia*
切换到控制台([ctrl][alt][f2]),退出 X 服务器(例如,sudo service mdm stop),然后运行安装程序:
sudo sh NVIDIA-Linux-x86-319.37.run (或 sudo sh NVIDIA-Linux-x64-319.37.run)
- 阅读/接受 EULA
- 在“是否使用 DKMS 注册内核模块源代码”的问题时,我选择“是”。
- 在(仅限 64 位)“安装 32 位 OpenGL 兼容性”的问题时,我选择“否”。
- 在“是否运行 NVidia-xconfig 工具”的问题时,我选择“是”。
- 在一个我没有在启动时禁用 nouveau 的测试机器上,安装程序询问我是否要尝试移除 nouveau。这有时会成功,但不要指望它。
- 完成后,重新启动。希望在您的桌面加载时能看到 NVidia 的启动屏幕。
您的安装程序可能会失败。最常见的错误是显示管理器正在使用或与 nouveau 存在冲突。回溯以上步骤可以解决这些问题。但是,有时如果发行版的内核是使用旧版本 gcc 编译的,则会出现错误。(您可能会看到类似这样的信息:用于编译内核的编译器(gcc 4.6)与当前编译器(gcc 4.7)不完全匹配。)有时选择忽略此问题可以解决,但同样,不要指望它。您需要安装编译内核所用的 gcc 版本(例如,上述示例中的 4.6)。使用您首选的包管理器进行安装。接下来,因为您的机器现在有两个 gcc 版本,我们需要创建 alternatives。使用 gcc 4.6 和 gcc 4.7 的例子,我们运行:
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.6 10
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-4.7 20
现在,当您运行
sudo update-alternatives --config gcc
您可以选择 gcc 4.6 作为 活动 版本。稍后,安装完成后,您可以切换回它。
安装 Cuda 工具包
呼!现在开始变得容易了。接下来,我们使用以下命令安装工具包:
sudo sh cuda-linux-rel-5.5.22-16488124.run (或 sudo sh cuda-linux64-rel-5.5.22-16488124.run)
(如果您看到 gcc 版本错误,请参阅“安装开发者驱动程序”下的“您的安装程序可能失败”部分。)
完成时,您的工具包安装控制台将显示以下文本:
* 请确保您的 PATH 包含 /usr/local/cuda-5.5/bin
* 请确保您的 LD_LIBRARY_PATH
* 对于 32 位 Linux 发行版,包含 /usr/local/cuda-5.5/lib
* 对于 64 位 Linux 发行版,包含 /usr/local/cuda-5.5/lib64:/usr/local/cuda-5.5/lib
* 或者
* 对于 32 位 Linux 发行版,添加 /usr/local/cuda-5.5/lib
* 对于 64 位 Linux 发行版,添加 /usr/local/cuda-5.5/lib64 和 /usr/local/cuda-5.5/lib
* 到 /etc/ld.so.conf 并以 root 身份运行 ldconfig
节省时间和精力
通过编辑您主目录中的 .profile 文件(如果需要则创建),来持久设置您的附加路径。在文件末尾添加 PATH=$PATH:/usr/local/cuda-5.5/bin,保存,然后注销并重新登录。
使用持久、模块化的方法来管理您的 LD_LIBRARY_PATH。我从不直接编辑 /etc/ld.so.conf 文件。相反,我的 ld.so.conf 文件包含一行:include /etc/ld.so.conf.d/*.conf。我在 /etc/ld.so.conf.d 文件夹中创建一个名为 cuda.conf 的新文件,其中包含以下一行(或多行):
- /usr/local/cuda-5.5/lib
- /usr/local/cuda-5.5/lib64 (仅限 64 位安装)
然后运行 sudo ldconfig。
步骤 4:使用 NVidia CUDA 示例测试 CUDA
通过运行您的第三个、分离出来的安装脚本来安装示例:
sudo sh cuda-samples-linux-5.5.22-16488124.run
现在让我们运行一个测试。从终端,切换到 deviceQuery 示例所在的文件夹(默认是 /usr/local/cuda-5.5/samples/1_Utilities/deviceQuery)。使用系统编译器 Make 该示例:
sudo make
(如果在运行 sudo make 时看到 gcc 版本错误,请参阅“安装开发者驱动程序”下的“您的安装程序可能失败”。)
然后,使用以下命令运行示例:
./deviceQuery
我在我的 64 位测试系统上看到以下输出:
/usr/local/cuda-5.5/samples/1_Utilities/deviceQuery $ .
/deviceQuery ./deviceQuery Starting...
Cuda Device Query (Runtime API) version (CudaRT static linking)
检测到 1 个支持 CUDA 的设备
设备 0:“GeForce GTX 560 Ti”
等等,等等,...
Runtime Version = 5.5, NumDevs = 1, Device0 = GeForce GTX 560 T
步骤 5:启动 Nsight Eclipse 版本
Nsight 是 Eclipse 的一个分支,它预先配置了 C++ 和 Cuda。它包含在您的工具包安装中(您已经拥有它)。现在,从终端运行它:/usr/local/cuda-5.5/libnsight/nsight。(不要从文件管理器双击该文件。)稍后您可以创建一个桌面启动器。继续并选择它推荐的用于项目的默认文件夹。
让我们测试一下。
- 文件 > 新建 > Cuda C++ 项目
- 选择导入 Cuda 示例
- 将项目命名为test
- 点击下一步
- 在示例列表中选择带宽测试
- 点击下一步
- 基本设置 - 使用默认值
- 点击完成
- 从项目菜单:项目 > 构建项目
- 从运行菜单:运行 > 运行
我的控制台窗口输出是:
[Cuda Bandwidth Test] - Starting...
运行在..设备 0
GeForce GTX 560 Ti。
等等,...
步骤 6:配置 Nsight 以进行 Java 开发
可以通过帮助 > 安装新软件来扩展 Nsight。要添加 Java 开发,您需要将 http://download.eclipse.org/releases/juno 添加到您的可用软件站点。(注意:截至 Nsight 5.5,Kepler 存储库不起作用。)然后,安装 Eclipse Java 开发工具。
按照安装对话框进行操作并重启 Nsight。
步骤 7:下载 JCuda 绑定并开始使用
从 http://www.jCuda.org/downloads/downloads.html 下载适用于您平台的 zip 文件。将其解压到您主目录中的一个文件夹。然后启动 Nsight。创建一个新的 Java 项目(文件 > 新建 > Java 项目)并命名为 JCudaHello。右键单击项目资源管理器中的 JCudaHello 项目,然后选择属性。转到Java 构建路径树项并选择库选项卡。点击添加外部 JAR,导航到您创建的已解压文件夹,然后选择 jCuda-0.5.5.jar。 在库选项卡仍然打开的情况下,展开您添加的 jCuda-0.5.5.jar 的树,然后单击本地库位置(无)。然后单击编辑按钮。系统会要求您提供位置。单击外部文件夹,再次导航到已解压的文件夹。单击确定。
现在,从项目资源管理器中右键单击 jcudaHello 项目中的 src 文件夹,然后选择新建 > 类。将类命名为 cudaTest,并选择public static void main 方法存根。
点击完成。删除在 cudaTest.java 中由编辑器预先生成的代码,然后粘贴以下内容:
import jcuda.Pointer;
import jcuda.runtime.JCuda;
public class test {
public static
void main(String[] args) {
Pointer pointer = new Pointer();
JCuda.cudaMalloc(pointer, 4);
System.out.println("Pointer: " + pointer);
JCuda.cudaFree(pointer);
}
}
运行它时,您应该会看到类似以下的输出:
指针
Pointer[nativePointer=0x800100000,byteOffset=0]
使用项目代码
项目代码是一个压缩的 Eclipse 工作区,不包含任何隐藏的元数据文件夹或信息文件。将其解压缩到您选择的位置后,您将看到两个子目录:JCudaFftDemo 和 Notes。
首先,我们需要从 JCudaFftDemo 文件夹中的现有源文件创建一个 Nsight Java 项目。启动 Nsight,并在提示您选择工作区时,选择您解压的目录(JCudaFftDemo 的父目录)。从文件菜单创建一个新的 Java 项目,并为其指定确切的名称:JCudaFftDemo。然后,单击完成。如果展开项目资源管理器中项目的树,您应该会看到:
接下来,您需要将 JCuda 绑定添加到 Java 构建路径。在项目资源管理器中右键单击 JCudaFftDemo 项目,然后选择属性。转到Java 构建路径树项并选择库选项卡。点击添加外部 JAR,导航到您在设置 - 步骤 7 中下载的 JCuda 绑定,然后选择 jCuda-0.5.5.jar, jcublas-0.5.5.jar, 和 jcufft-0.5.5.jar。 在库选项卡仍然打开的情况下,逐个展开您添加的 JAR 文件的树,然后单击本地库位置(无)。单击编辑按钮,并将位置设置为与您的 JCuda 绑定目录匹配。(我们正在重复上述设置部分的步骤 7,这次是针对新项目。)
然后,将其作为 Java 应用程序运行。这是我的 Linux Mint 13,32 位笔记本电脑上的输出控制台:
Creating sin wave input data: Frequency = 11.0, N = 1048576, dt = 5.0E-5 ...
L2 Norm of original signal: 724.10583
Performing a 1D C2C FFT on GPU with JCufft...
GPU FFT time: 0.121 seconds
Performing a 1D C2C FFT on CPU...
CPU time: 3.698 seconds
GPU FFT L2 Norm: 741484.3
CPU FFT L2 Norm: 741484.4
Index at maximum in GPU power spectrum = 572, frequency = 10.910034
Index at maximum in CPU power spectrum = 572, frequency = 10.910034
Performing 1D C2C IFFT(FFT) on GPU with JCufft...
GPU time: 0.231 seconds
Performing 1D C2C IFFT(FFT) on CPU...
CPU time: 3.992 seconds
GPU FFT L2 Norm: 724.1056
CPU FFT L2 Norm: 724.10583
更多关于项目代码的信息
首先,关于复数数组:CUDA 和 JCuda 可以处理包含浮点数或双精度复数向量的数据数组,前提是您将数组构造为交错的复数序列。这最好通过示例来演示。假设我们有一个长度为 2 的复数向量:(1 + 2i, 3 + 4i)。对应的交错数据数组长度为 4,形式为:(1, 2, 3, 4)。在项目代码中,我对所有提交给 JCuda 方法的复数向量都使用了此格式。
相比之下,为了简化 CPU 代码,我使用一个 ComplexFloat
类来表示复数。当使用此类创建复数向量时,向量 x = (1 + 2i, 3 + 4i) 的形式为 ComplexFloat[2] = (x[0].Real = 1, x[0].Imaginary = 2, x[1].Real = 3, x[1].Imaginary = 4)。该数组以及它所代表的向量长度相同:2。
Main.java 是应用程序的入口点。它创建一个示例信号并执行演示。产生的信号是:sin(2*pi*FREQ *t),以 dT 为增量采样 N 次。演示计算测试信号的傅里叶正反变换——在 GPU 和 CPU 上都进行——并提供执行时间和结果的信号特性。
CPU FFT 代码部分(FftCpuFloat.java)故意以一种依赖于 ComplexFloat.java 类实例的笨拙方式实现了 Cooley–Tukey 算法。对内存分配和访问的关注很少。此外,虽然我有多个 CPU 核心,但我的 CPU 线程只在一个核心上执行。这样做使得基数-2 过程直观且简单,但会产生一个开销,从而夸大了使用 GPU 的优势。
您可以从 Main.java 类中调整创建测试信号的常量(FREQ、N 和 dT)。使用旧款 Dell 笔记本电脑上的 Linux 32 位安装,我发现通过改变测试信号的长度(N),当信号包含少于 4096 个复数元素时,CPU FFT 的性能优于 JCuda FFT。在此之后,JCuda FFT 的速度压倒了我的 CPU FFT。当 N = 4194304 时,JCuda 的速度是 CPU FFT 的 250 倍(CPU = 23 秒,GPU = 0.9 秒)。在此之后,笔记本电脑风扇在 CPU 计算循环期间发出轰鸣声(系统温度:90 C),并且担心过热迫使我缩短了测试。(我的 Linux 64 位台式机配备了 6 核 AMD Phenom II、Sabretooth 主板、16 GiB 内存、GeForce GTX 560 Ti 显卡,以及一些很棒的风扇。只要我能有效管理内存,它就可以整晚处理 FFT(CPU 或 GPU)。)
我观察到的速度优势有相当一部分归因于我优化不佳的 CPU 实现的低效率。使用优化 CPU 代码进行的更严谨的 CPU/GPU 评估表明,收益约为 10 倍。我宁愿选择 10 倍而不是 1 倍,但实际情况是:CUDA 底层实现效率的强大功能,加上固有的 GPU 增益(无论它真正是什么),共同使我获得了平均 50 倍的提升。
项目下载中的 Notes 文件夹包含一些关于如何运行已部署的、可执行的 jar 文件的提示。基本上,您需要使用 -Djava.libraries.path 开关指向您的 JCuda 绑定文件夹。
结论
设置并熟悉 CUDA、JCuda 和 Nsight 需要大量的工作。但这是值得的。图形处理单元(GPU)上的通用计算(GPGPU)是您编码工具箱中一项非常重要的工具。我希望本文能帮助像我一样的 GPGPU 新手更轻松地完成这个过程。祝您成为一名顶尖的 JCuda 编码者,一切顺利!