Java通过JNI调用CUDA程序 - Go语言中文社区

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);

}

上面通过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

上面的代码读懂了的话也可以自己写,但是强烈建议使用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 , %sn",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;
}

从上面的实现接口代码可以看出,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);    
    }
}

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

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

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/lllzzh123321/article/details/53520815
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2020-03-08 16:12:43
  • 阅读 ( 829 )
  • 分类:

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢