Java通过JNI调用CUDA程序

在这里不讨论为什么不使用Jcuda,只是说明如何通过JNI(Java Native Interface)调用CUDA程序
(1)编写java程序通过native关键字声明调用接口
(2)生成调用头文件xx.h,使用javah命令生成(如果你够牛逼可以自己手写,但是这样容易出错)
(3)编写CUDA程序(跟编写C/C++差不多)
(4)编译生成动态链接库libxx.so文件(windows下应该是xx.dll文件,我这里讲的都是基于linux下的)
(5)java加载上述动态链接库,然后就可以通过接口调用CUDA程序了
(6)编译运行,OK

说的挺简单了,其实有很多细节要注意的

(1)编写java程序通过native关键字声明调用接口

package com.lzh
public class Hello
{
    static 
    {
        try
        {
            System.loadLibrary("sharedLibraryProject");
        }
        catch(UnsatisfiedLinkError e)
        {
            System.err.println("can not load hello library :\n"+
            e.toString());
        }

    }
    public native void SayHello(String strName);
    public native int Add(int a,int b);

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

上面通过native声明了两个接口函数SayHello和Add
SayHello没有返回值,有一个字符串参数
Add返回一个int类型,有两个int参数
上面加载动态链接库的代码可以先忽略
上面的代码可以使用Eclipse等IDE编写,也可以直接vim编写(注意后缀文件名为.java)

(2)生成调用头文件xx.h,使用javah命令生成
将上述代码保存好,使用javac编译,使用javah生成,xx.h头文件(也可以在IDE直接build)
例如:我在Eclipse先build生成Hello.class
然后进入项目的/bin/
使用 javah com.lzh.Hello
ls一下可以看到生成com_lzh_Hello.h
查看com_lzh_Hello.h里面的内容

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_lzh_Hello */

#ifndef _Included_com_lzh_Hello
#define _Included_com_lzh_Hello
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_lzh_Hello
 * Method:    SayHello
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_com_lzh_Hello_SayHello
  (JNIEnv *, jobject, jstring);

/*
 * Class:     com_lzh_Hello
 * Method:    Add
 * Signature: (II)I
 */
JNIEXPORT jint JNICALL Java_com_lzh_Hello_Add
  (JNIEnv *, jobject, jint, jint);

#ifdef __cplusplus
}
#endif
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30

上面的代码读懂了的话也可以自己写,但是强烈建议使用javah生成(省时省力,不容易出错)

要注意1)使用javah命令的位置;2)javah后面的文件名。
1)如果使用IDE建立的工程,javah一定要位于包名的外面。直接进入Hello.class文件的目录下会报错
Error: Could not find class file for ‘Hello’
2)javah后面的文件名为 包名.文件名(不跟后缀.class)例如 javah com.lzh.Hello
不能写成 javah com/lzh/Hello否则会报错
Exception in thread “main” java.lang.IllegalArgumentException: Not a valid class name: com/lzh/Hello
at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:129)
at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:107)
at com.sun.tools.javac.api.JavacTool.getTask(JavacTool.java:64)
at com.sun.tools.javah.JavahTask.run(JavahTask.java:503)
at com.sun.tools.javah.JavahTask.run(JavahTask.java:329)
at com.sun.tools.javah.Main.main(Main.java:46)

(3)编写CUDA程序(跟编写C/C++差不多)
我使用的是Nsight编写的,主要是方便编译生成动态链接库
首先新建一个shared Library project(其他Project应该也可以,但编译的时候要做适当的配置)
然后将刚刚生成的com_lzh_Hello.h拷贝到该工程下
最后实现接口的编写
这里写图片描述

#include "com_lzh_Hello.h"
#include <stdio.h>

extern "C" int Sum(int a,int b);

JNIEXPORT void JNICALL Java_com_lzh_Hello_SayHello(JNIEnv *env,jobject obj,jstring instring)
{
    const jbyte *str=(const jbyte *)env->GetStringUTFChars(instring , JNI_FALSE);
    printf("hello , %s\n",str);
    env->ReleaseStringUTFChars(instring,(const char *)str);

    return;
}

JNIEXPORT jint JNICALL Java_com_lzh_Hello_Add(JNIEnv *, jobject, jint a, jint b)
{
    int sum=0;
    sum = Sum(a,b);
    return sum;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

从上面的实现接口代码可以看出,java代码和c/c++代码的基本数据类型是一样的(int和jint等),可以直接使用
extern “C” int Sum(int a,int b);实际编写CUDA程序的接口函数(这里就不展开),当然也可以在同一个文件编写CUDA程序

(4)编译生成动态链接库libxx.so文件
接口函数实现完了就编译,如果是纯C/C++程序还是比较好编译生成动态链接库的(网上很多这种命令),加上CUDA难度就大了,故这里使用Nsight
编译包含路径设置:添加包含java native的相关文件(比如java安装目录下的include和include/linux);CUDA包含相关文件
右键属性,Build,Settings,NVCC Compiler,includes
这里写图片描述
把编译命令(点击上面的NVCC Complier看到Command)
设置为nvcc -fPIC -shared
意思是生成路径无关的共享库文件
一切大功告成,只需ok,然后进行编译可以生成Debug或者Release版本
比如我生成Debug然后就可以去Debug下找到一个libXX.so的文件,只需把这个动态链接库发布出去,其他程序就可以调用他了
命令:(那个引号是键盘的左上角esc下边的引号)
这里写图片描述
上面这个办法是临时生效的,切换用户或者重启就失效了
解决办法(你有更好的办法可以共享哦,欢迎评论)
新建一个文件夹,这个文件夹专门存放自己生成的动态链接库,然后修改用户配置文件,是library path指向这个目录
这里写图片描述
我新建了一个libfile文件夹存放,然后修改配置文件,使其生效

(5)java加载上述动态链接库,然后就可以通过接口调用CUDA程序了
加载动态链接库的代码为
System.loadLibrary(“sharedLibraryProject”);
(1)里面的代码是加了异常处理的
这里需要特别注意的是:编译生成的是libxx.so
加载的时候只需加载xx即可
比如我编译生成的是libsharedLibraryProject.so
我只需加载sharedLibraryProject

然后就可以通过接口调用了
我新建了一个ToSay类

package com.lzh;
import com.lzh.Hello;
public class ToSay {    
public static void main(String[] args) {
        Hello h=new Hello();
        int sum=h.Add(1,65536);
        h.SayHello("jerry");
        System.out.println(sum);    
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

编译运行成功~~
这里写图片描述
上面的Add的方法是通过GPU求a到b之间的数字的和
SayHello方法是通过C语言输出打招呼
输出的第一行时间(ms)是GPU是运行的时间

好了,就写到这里,祝大家生活愉快~~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值