牛人源码定位问题原因- 从源码看DL4J中Native BLAS的加载,以及配置

http://blog.csdn.net/oncealong/article/details/50068831


最近在用DeepLearning4J(DL4J)尝试语音识别的深度学习,Git DL4J的代码,用IntelliJ IDEA打开,配置好相关依赖后,运行包org.deeplearning4j.examples.test.Test的main,可以正常运行,但是有警告提示如下:

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. 十一月 27, 2015 12:37:07 下午 com.github.fommil.netlib.BLAS <clinit>  
  2. WARNING: Failed to load implementation from: com.github.fommil.netlib.NativeSystemBLAS  
  3. 十一月 27, 2015 12:37:09 下午 com.github.fommil.jni.JniLoader liberalLoad  
  4. ****************************************************************  
  5. INFO: successfully loaded C:\Users\ADMINI~1\AppData\Local\Temp\jniloader6882206374132167742netlib-native_ref-win-x86_64.dll  
  6. WARNING: COULD NOT LOAD NATIVE SYSTEM BLAS  
  7. ND4J performance WILL be reduced  
  8. Please install native BLAS library such as OpenBLAS or IntelMKL  
  9. See http://nd4j.org/getstarted.html#open for further details  
  10. ****************************************************************  

提示无法加载com.github.fommil.netlib.NativeSystemBLAS,和无法加载native system blas,DN4J的性能会受到影响.

查了github,stackoverflow,quora等,找到了如下网页.github的是一个遇到类似问题的人抱怨native blas难以配置,害的自己在源码中才找到解决方法,而nd4j的程序员回答所有的深度学习框架中的native blas都是难配置的,我在搜索的时候也发现了MLib等库确实也会报这个警告.第二个博客是一篇讲述如何配置blas的文章.

[plain]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. https://github.com/deeplearning4j/nd4j/issues/456  
  2. http://avulanov.blogspot.cz/2014/09/howto-to-run-netlib-javabreeze-in.html  

这篇文章中,我想说明一下几个问题:

1. native blas 是本地库,用C/C++写成,因而运算速度较快.

2.Java如果要调用C/C++的dll,一定要用JNI技术来调用对应的dll,那么dll的路径和名称分别是什么?

3. 如果找到需要的dll,假设叫a.dll,如果a.dll又依赖于b.dll和c.dll,那么把a.dll,b.dll,c.dll都放在Java识别的路径下,是不是就可以解决这个问题了.


1.dll的存放路径和名称

先看DL4J的源码,错误首先出现在如下代码中

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. Nd4j.getRandom().setSeed(seed);  
进入Nd4j的类,加断点,单步调试,经过如下的函数调用栈后,进入NativeSystemBLAS这个类.


我们看下NativeSystemBLAS类的内容,在static静态块中找到如下用于加载dll的代码:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. static {  
  2.         String jnilib = JniNamer.getJniName("netlib-native_system");  
  3.         String natives = System.getProperty("com.github.fommil.netlib.NativeSystemBLAS.natives", jnilib);  
  4.         JniLoader.load(natives.split(","));  
  5.     }  
先加断点单步运行,看看jnilib的内容,运行后得到的是"netlib-native_system-win-x86_64.dll",为什么是这个,我的电脑是64位,如果32位系统又会需要哪个dll库呢?

我们进入getJniName函数,看下其代码,这里arch用于获得架构,os用于获得系统版本,extension获得后缀,然后拼出dll的名称:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public static String getJniName(String stem) {  
  2.     String arch = arch();//获得系统架构,x86,i386对应于i686,x86_64,amd64对应于x86_64,其他还有arm等架构  
  3.     String abi = abi(arch);//判断是不是arm架构,如果是arm架构,需要其他的设置,这里不问  
  4.     String os = os();//判断系统是win,linux.还是mac os x  
  5.     String extension = extension(os);//根据系统版本,确定文件后缀名是dll,还是so,osx  
  6.     return stem + "-" + os + "-" + arch + abi + "." + extension;//返回确定的库名字  
  7. }  

arch代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private static String arch() {  
  2.         String arch = System.getProperty("os.arch""").toLowerCase();  
  3.         if(!arch.equals("x86") && !arch.equals("i386") && !arch.equals("i486") && !arch.equals("i586") && !arch.equals("i686")) {  
  4.             if(!arch.equals("x86_64") && !arch.equals("amd64")) {  
  5.                 if(arch.equals("ia64")) {  
  6.                     return "ia64";  
  7.                 } else if(arch.equals("arm")) {  
  8.                     return "arm";  
  9.                 } else if(arch.equals("armv5l")) {  
  10.                     return "armv5l";  
  11.                 } else if(arch.equals("armv6l")) {  
  12.                     return "armv6l";  
  13.                 } else if(arch.equals("armv7l")) {  
  14.                     return "armv7l";  
  15.                 } else if(arch.equals("sparc")) {  
  16.                     return "sparc";  
  17.                 } else if(arch.equals("sparcv9")) {  
  18.                     return "sparcv9";  
  19.                 } else if(arch.equals("pa_risc2.0")) {  
  20.                     return "risc2";  
  21.                 } else if(arch.equals("ppc")) {  
  22.                     return "ppc";  
  23.                 } else if(arch.startsWith("ppc")) {  
  24.                     return "ppc64";  
  25.                 } else {  
  26.                     log.warning("unrecognised architecture: " + arch);  
  27.                     return "unknown";  
  28.                 }  
  29.             } else {  
  30.                 return "x86_64";  
  31.             }  
  32.         } else {  
  33.             return "i686";  
  34.         }  
  35.     }  
abi代码如下:
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private static String abi(String arch) {  
  2.         if(!arch.startsWith("arm")) {  
  3.             return "";  
  4.         } else {  
  5.             try {  
  6.                 String[] e = new String[]{"sun.boot.library.path""java.library.path""java.home"};  
  7.                 int len$ = e.length;  
  8.   
  9.                 int i$;  
  10.                 String dir;  
  11.                 for(i$ = 0; i$ < len$; ++i$) {  
  12.                     dir = e[i$];  
  13.                     String file = System.getProperty(dir, "");  
  14.                     log.config(dir + ": " + file);  
  15.                     if(file.matches(".*(gnueabihf|armhf).*")) {  
  16.                         return "hf";  
  17.                     }  
  18.                 }  
  19.   
  20.                 e = new String[]{"/lib/arm-linux-gnueabihf""/usr/lib/arm-linux-gnueabihf"};  
  21.                 len$ = e.length;  
  22.   
  23.                 for(i$ = 0; i$ < len$; ++i$) {  
  24.                     dir = e[i$];  
  25.                     File var7 = new File(dir);  
  26.                     if(var7.exists()) {  
  27.                         return "hf";  
  28.                     }  
  29.                 }  
  30.   
  31.                 return "";  
  32.             } catch (SecurityException var6) {  
  33.                 log.log(Level.WARNING, "unable to detect ABI", var6);  
  34.                 return "unknown";  
  35.             }  
  36.         }  
  37.     }  
os代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private static String os() {  
  2.         String os = System.getProperty("os.name""").toLowerCase();  
  3.         if(os.startsWith("linux")) {  
  4.             return "linux";  
  5.         } else if(os.startsWith("windows")) {  
  6.             return "win";  
  7.         } else if(!os.startsWith("mac os x") && !os.startsWith("darwin")) {  
  8.             if(os.startsWith("freebsd")) {  
  9.                 return "freebsd";  
  10.             } else if(os.startsWith("android")) {  
  11.                 return "android";  
  12.             } else if(os.startsWith("sunos")) {  
  13.                 return "sun";  
  14.             } else if(os.startsWith("hp-ux")) {  
  15.                 return "hpux";  
  16.             } else if(os.startsWith("kd")) {  
  17.                 return "kd";  
  18.             } else {  
  19.                 log.warning("unable to detect OS type: " + os);  
  20.                 return "unknown";  
  21.             }  
  22.         } else {  
  23.             return "osx";  
  24.         }  
  25.     }  
extension代码如下:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private static String extension(String os) {  
  2.        return os.equals("win")?"dll":(os.equals("osx")?"jnilib":"so");  
  3.    }  


这样,我就知道了dll的具体名字为什么是netlib-native_system-win-x86_64.dll了.

接下来,我还需要知道这个dll要放在哪里,才能被nd4j代码找到!

接下来,我们跳进JniLoader.load(natives.split(",")),看看load是如何执行的:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. public static synchronized void load(String... paths) {  
  2.     if(paths != null && paths.length != 0) {  
  3.         String[] javaLibPath = paths;  
  4.         int arr$ = paths.length;  
  5.   
  6.         int len$;  
  7.         String path;  
  8.         for(len$ = 0; len$ < arr$; ++len$) {  
  9.             String i$ = javaLibPath[len$];  
  10.             path = (new File(i$)).getName();  
  11.             if(loaded.contains(path)) {  
  12.                 log.info("already loaded " + i$);  
  13.                 return;  
  14.             }  
  15.         }  
  16.   
  17.         javaLibPath = System.getProperty("java.library.path").split(File.pathSeparator);//这里获得windows下path对应的路径,是个字符串数组  
  18.         String[] var11 = paths;  
  19.         len$ = paths.length;  
  20.   
  21.         for(int var12 = 0; var12 < len$; ++var12) {  
  22.             path = var11[var12];  
  23.             log.config("JNI LIB = " + path);  
  24.             String[] extracted = javaLibPath;  
  25.             int len$1 = javaLibPath.length;  
  26.   
  27.             for(int i$1 = 0; i$1 < len$1; ++i$1) {  
  28.                 String libPath = extracted[i$1];  
  29.                 File file = (new File(libPath, path)).getAbsoluteFile();//这里根据path路径和dll的名称拼成一个文件路径,下面检查这个文件是否存在,如果不存在,拼下一个文件路径,如果存在,就加载这个dll  
  30.                 log.finest("checking " + file);  
  31.                 if(file.exists() && file.isFile() && liberalLoad(file, path)) {  
  32.                     return;  
  33.                 }  
  34.             }  
  35.   
  36.             File var13 = extract(path);  
  37.             if(var13 != null && liberalLoad(var13, path)) {  
  38.                 return;  
  39.             }  
  40.         }  
  41.   
  42.         throw new ExceptionInInitializerError("unable to load from " + Arrays.toString(paths));  
  43.     } else {  
  44.         throw new ExceptionInInitializerError("invalid parameters");  
  45.     }  
  46. }  
其实这段代码本来我没有看太懂,但是架不住调试功能强大,运行一次,加断点,看看内容怎么变化,就知道是什么意思了.

这里假设我的windows下Path环境变量设置为:"D:\Python\Python35\Scripts\;D:\Program Files\Java\jdk1.7.0_15\bin;C:\WINDOWS;D:\BLAS;",那么javaLibPath的内容就会是这四个组成的字符串数组.接下来取出来第一个,和dll名称(netlib-native_system-win-x86_64.dll)一起拼成一个文件路径"D:\Python\Python35\Scripts\netlib-native_system-win-x86_64.dll",接下来检查这个路径是否是文件,如果不是,就会继续拼下一个,直到拼出"D:\BLAS\netlib-native_system-win-x86_64.dll",然后我的dll文件确实放在这里,程序就会加载.


现在我们找到对应的dll文件,这里为netlib-native_system-win-x86_64.dll,然后放在D:\BLAS\这个位置,把D:\BLAS加入path变量,然后重启Intellij(这一步不一定需要,大家自己尝试),让它重新读取path值.

重新运行下Test里的main函数,然后运行结果竟然还是如上的错误,额,是我们刚才的分析都是错误了么?

2.dll库的依赖和依赖查找

我们在跳进加载dll的地方,看看究竟是哪里错了:

[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1. private static boolean liberalLoad(File file, String name) {  
  2.         try {  
  3.             log.finest("attempting to load " + file);  
  4.             System.load(file.getAbsolutePath());  
  5.             log.info("successfully loaded " + file);  
  6.             loaded.add(name);  
  7.             return true;  
  8.         } catch (UnsatisfiedLinkError var6) {  
[java]  view plain  copy
 print ? 在CODE上查看代码片 派生到我的代码片
  1.         //这里捕获到一个异常,说Can't find dependent libraries.  
  2.         log.log(Level.FINE, "skipping load of " + file, var6);  
  3.         String tmpdir = System.getProperty("java.io.tmpdir");  
  4.         if(tmpdir != null && tmpdir.trim().length() > 2 && file.getAbsolutePath().startsWith(tmpdir)) {  
  5.             log.log(Level.FINE, "deleting " + file);  
  6.   
  7.             try {  
  8.                 file.delete();  
  9.             } catch (Exception var5) {  
  10.                 log.info("failed to delete " + file);  
  11.             }  
  12.         }  
  13.   
  14.         return false;  
  15.     } catch (SecurityException var7) {  
  16.         log.log(Level.INFO, "skipping load of " + file, var7);  
  17.         return false;  
  18.     } catch (Throwable var8) {  
  19.         throw new ExceptionInInitializerError(var8);  
  20.     }  
  21. }  


这里我们捕获到一个异常,说 D:\BLAS\netlib-native_system-win-x86_64.dll: Can't find dependent libraries.

原来如此我们的库还需要一些依赖库,这些依赖库没有找到.但是我们怎么知道这个库依赖于哪些库呢?哪些是已经有的,哪些是没有的?这里推荐一个工具,叫PEStudio,可以查看dll文件或exe文件依赖于哪些库:


这里显示了netlib-native_system-win-x86_64.dll依赖于哪些库,其中liblapack3.dll和libblas3.dll是我们需要找到了.其实到这一步,问题就算解决了,参见OpenBlas的官网,这些文件都可以很轻松的下载到,就是要注意首先需要知道你要的是32位还是64位,其次所有dll需要是统一的,不能部分32,部分64. 谢谢曲奇饼的提醒, 这里需要注意的还有jdk的版本, 在64位系统下, 应该使用64的jdk, 大家注意尝试.

这里是我用PEStudio找到的依赖关系:



至此,问题圆满解决.


=========================

最后,把在win10 64位系统下成功运行的库文件放在这:

csdn:http://download.csdn.NET/detail/u201011221/9355487

baiduyun: http://pan.baidu.com/s/1jGO5waE

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值