GPGPU Java编程

在我们之前的一篇文章中,我们讨论了图形处理单元(GPGPU)的通用处理概念和体系结构。 对于C / C ++程序员来说,这一切都很棒,但是对于Java程序员而言,写C / C ++而不是Java至少可以带来不便。 那么为Java程序员提供哪些工具呢?

在我们开始编写一些背景知识之前。 有两个竞争的GPGPU SDK: OpenCLCUDA 。 OpenCL是所有GPU供应商(即AMD,NVIDIA和Intel)都支持的开放标准,而CUDA是NVIDIA特定的,并且仅在NVIDIA卡上工作。 这两个SDK都支持C / C ++代码,这当然使我们Java开发人员感到不寒而栗。 到目前为止,还没有纯Java OpenCL或CUDA支持。 对于需要利用GPU大量并行潜力的Java程序员来说,这并没有太大帮助,除非她摆弄Java Native接口。 当然,有一些Java工具可以减轻GPGPU Java编程的麻烦。

最受欢迎的两个(IMHO)是jocljcuda 。 使用这些工具,您仍然必须编写C / C ++代码,但至少这仅适用于将在GPU中执行的代码,从而大大减少了工作量。

这次,我将看一看jcuda,看看如何编写一个简单的GPGPU程序。

让我们从设置CUDA GPGPU linux开发环境开始(尽管Windows和Mac环境也不难设置):

步骤1 :在计算机中安装启用了NVIDIA CUDA的GPU。 NVIDIA开发人员网站上有支持CUDA的GPU列表。 新的NVIDIA GPU几乎肯定可以启用CUDA,但以防万一,请检查卡的规格以确保…

步骤2 :安装NVIDIA驱动程序和CUDA SDK。 下载它们并从此处找到安装说明。

步骤3 :转到目录〜/ NVIDIA_GPU_Computing_SDK / C / src / deviceQuery并运行make

步骤4 :如果编译成功,请转到目录〜/ NVIDIA_GPU_Computing_SDK / C / bin / linux / release并运行文件deviceQuery 。 您将获得有关卡的许多技术信息。

这是我的GeForce GT 430卡所获得的:

注意2个具有48个CUDA核心的多处理器,每个核心共有96个核心,对于价值约40欧元的低端视频卡来说还不错!!!

第5步 :现在您有了CUDA环境,让我们用C语言编写和编译CUDA程序。编写以下代码并将其保存为multiple.cu

#include <iostream>

__global__ void multiply(float a, float b, float* c)
{
  *c=a*b;
}

int main()
{
  float a, b, c;
  float *c_pointer;
  a=1.35;
  b=2.5;

  cudaMalloc((void**)&c_pointer, sizeof(float));
  multiply<<<1,1>>>(a, b, c_pointer);
  cudaMemcpy(&c, c_pointer, sizeof(float),cudaMemcpyDeviceToHost);
/*** This is C!!! You manage your garbage on your own!  ***/    
  cudaFree(c_pointer);
  printf("Result = %f\n",c);
}

使用cuda编译器对其进行编译并运行:

$ nvcc multiply.cu -o multiply
$ ./multiply
Result = 3.375000
$

那么上面的代码做什么? 带有__global__限定符的乘法函数称为内核 ,是将在GPU中执行的实际代码。 尽管存在一些语义差异,但main函数中的代码在CPU中作为普通C代码执行:

  1. 用<<< 1,1 >>>括号调用乘法函数。 方括号内的两个数字告诉CUDA代码应执行多少次。 CUDA使我们能够创建所谓的一维,二维甚至三维线程块。 此示例中的数字表示一个维度上运行的单个线程块,因此我们的代码将执行1×1 = 1次。
  2. 使用cudaMalloc,cudaMemcpy和cudaFree函数以与处理C语言中的计算机普通内存类似的方式来处理GPU内存。cudaMemcpy函数非常重要,因为GPU具有自己的RAM,并且在处理内核中的任何数据之前我们需要将它们加载到GPU内存中。 当然,完成后,我们还需要将结果复制回普通内存。

现在我们掌握了如何在GPU中执行代码的基础知识,让我们看看如何从Java运行GPGPU代码。 请记住,内核代码仍将用C编写,但至少现在主要功能是在jcuda的帮助下的Java代码。

下载jcuda二进制文件,解压缩它们,并确保在JVM的java.library.path参数中指定了包含.so文件的目录(对于Windows,则为.dll),或者将其附加到LD_LIBRARY_PATH环境变量中(或Windows中的PATH变量)。 同样,在编译和执行Java程序期间,jcuda-xxxxxxx.jar文件必须位于类路径中。

现在,我们有了jcuda设置,让我们看一下与jcuda兼容的内核:

extern "C"
__global__ void multiply(float *a, float *b, float *c)
        /*************** Kernel Code **************/
{
        c[0]= a[0] * b[0];
}

您会注意到与以前的内核方法有以下区别:

  1. 我们使用extern“ C”限定符来告诉编译器不要混用乘法方法名称,因此可以使用其原始名称进行调用。
  2. 对于a,b和c,我们使用数组而不是基元。 jcuda要求这样做,因为jcuda不支持Java原语。 在jcuda中,数据作为浮点数,整数等东西的数组来回传递给GPU。

将此文件另存为multiple2.cu 。 这次我们不想将文件编译为可执行文件,而是编译为将在我们的Java程序中调用的CUDA库。 我们可以将内核编译为PTX文件或CUBIN文件。 PTX是人类可读的文件,其中包含将即时编译的程序集(如代码)。 CUBIN文件是已编译的CUda BINaries,无需进行即时编译即可直接调用。 除非您需要最佳的启动性能,否则PTX文件是可取的,因为它们与编译时使用的GPU的特定计算能力无关,而CUBIN文件将无法在计算能力较低的GPU上运行。

为了编译我们的内核,输入以下内容:

$ nvcc -ptx multiply2.cu -o multiply2.ptx

成功创建我们的PTX文件后,让我们看一下与我们的C示例中使用的main方法等效的java:

import static jcuda.driver.JCudaDriver.*;
import jcuda.*;
import jcuda.driver.*;
import jcuda.runtime.JCuda;

public class MultiplyJ {
  public static void main(String[] args) {

     float[] a = new float[] {(float)1.35};
     float[] b = new float[] {(float)2.5};
     float[] c = new float[1];
 
     cuInit(0);
     CUcontext pctx = new CUcontext();
     CUdevice dev = new CUdevice();
     cuDeviceGet(dev, 0);
     cuCtxCreate(pctx, 0, dev);
     
     CUmodule module = new CUmodule();
     cuModuleLoad(module, "multiply2.ptx");
     CUfunction function = new CUfunction();
     cuModuleGetFunction(function, module, "multiply");

     CUdeviceptr a_dev = new CUdeviceptr();
     cuMemAlloc(a_dev, Sizeof.FLOAT);
     cuMemcpyHtoD(a_dev, Pointer.to(a), Sizeof.FLOAT);

     CUdeviceptr b_dev = new CUdeviceptr();
     cuMemAlloc(b_dev, Sizeof.FLOAT);
     cuMemcpyHtoD(b_dev, Pointer.to(b), Sizeof.FLOAT);

     CUdeviceptr c_dev = new CUdeviceptr();
     cuMemAlloc(c_dev, Sizeof.FLOAT);

     Pointer kernelParameters = Pointer.to(
                                Pointer.to(a_dev),
                                Pointer.to(b_dev),
                                Pointer.to(c_dev)
                                );

     cuLaunchKernel(function, 1, 1, 1, 1, 1, 1, 0, null, kernelParameters, null);
     cuMemcpyDtoH(Pointer.to(c), c_dev, Sizeof.FLOAT);
     JCuda.cudaFree(a_dev);
     JCuda.cudaFree(b_dev);
     JCuda.cudaFree(c_dev);

     System.out.println("Result = "+c[0]);
  }
}

好的,看起来很多代码只是将两个数字相乘,但是请记住,有关Java和C指针的限制。 因此,从第9至11行开始,我们将a,b和c参数转换为名为a,b和c的数组,每个数组仅包含一个浮点数。

在第13至17行中,我们告诉jcuda我们将在系统中使用第一个GPU(高端系统中可能有多个GPU。)

在第19至22行中,我们告诉jcuda我们的PTX文件是,以及我们要使用的内核方法的名称(在我们的例子中是乘法)

在第24行,事情变得有趣起来,在第24行,我们使用了特殊的jcuda类CUdeviceptr,它充当指针占位符。 在第25行中,我们使用刚创建的CUdeviceptr指针分配GPU内存。 请注意,如果我们的数组有多个项目,则需要将Sizeof.FLOAT常数乘以数组中元素的数量。 最后,在第26行中,我们将第一个数组的内容复制到GPU。 同样,我们创建指针并将内容复制到第二个数组(b)的GPU RAM中。 对于我们的输出数组(c),我们现在只需要分配GPU内存。

在第35行中,我们创建一个Pointer对象,该对象将保存我们要传递给乘法方法的所有参数。

我们在第41行执行内核代码,在此执行实用程序方法cuKernelLaunch,将函数和指针类作为参数传递。 在功能参数之后的前六个参数定义了网格的数量(一个网格是一组块),并且在我们的示例中块均为1,因为我们只执行一次内核。 接下来的两个参数是0和null ,用于标识我们可能定义的任何共享内存(可以在线程之间共享的内存),在本例中为无。 下一个参数包含我们创建的Pointer对象,其中包含a,b,c设备指针,最后一个参数用于其他选项。

内核返回后,我们只需将dev_c的内容复制到我们的c数组,释放我们在GPU中分配的所有内存,并打印存储在c [0]中的结果,这当然与我们的C示例相同。

这是我们编译和执行MultiplyJ.java程序的方式(假设multiple2.ptx在同一目录中):

$ javac -cp ~/GPGPU/jcuda/JCuda-All-0.4.0-beta1-bin-linux-x86_64/jcuda-0.4.0-beta1.jar MultiplyJ.java

$ java -cp ~/GPGPU/jcuda/JCuda-All-0.4.0-beta1-bin-linux-x86_64/jcuda-0.4.0-beta1.jar:. MultiplyJ
Result = 3.375
$

请注意,在此示例中,目录〜/ GPGPU / jcuda / JCuda-All-0.4.0-beta1-bin-linux-x86_64已经在我的LD_LIBRARY_PATH中,因此不需要在java.library.path参数上设置JVM。

希望到目前为止,jcuda的机制已经明确,尽管我们并未真正涉及到GPU真正的强大功能,即大规模并行性。 在以后的文章中,我将提供一个示例,说明如何使用java在CUDA中运行并行线程,并附带一个示例,说明哪些内容不能在GPU中运行。 GPU处理对于非常专业的任务很有意义,大多数任务最好由我们受信任的旧CPU处理。

参考: W4G合作伙伴 Spyros Sakellariou的 GPGPU Java编程

相关文章 :


翻译自: https://www.javacodegeeks.com/2011/09/gpgpu-java-programming.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值