如果没有root访问权限,则在与参考BLAS链接时使用调优的BLAS运行R
任何人都可以告诉我为什么我不能通过以下方式成功测试R中OpenBLAS的dgemm性能(在GFLOP中)?
>链接R与“参考BLAS”libblas.so 第1点看起来很奇怪,但我别无选择,因为我在我想测试的机器上没有root访问权限,所以实际链接到OpenBLAS是不可能的. “不成功”我的意思是我的程序最终报告dgemm性能参考BLAS而不是OpenBLAS.我希望有人可以向我解释一下: >为什么我的方式不起作用; 我已经调查了这个问题两天,在这里我将包括各种系统输出,以帮助您进行诊断.为了使事情可以重现,我还将包括代码,makefile以及shell命令. 第1部分:测试前的系统环境 有两种方法可以使用R或Rscript来调用R.调用它们时加载的内容有一些差异: ~/Desktop/dgemm$readelf -d $(R RHOME)/bin/exec/R | grep "NEEDED" 0x00000001 (NEEDED) Shared library: [libR.so] 0x00000001 (NEEDED) Shared library: [libpthread.so.0] 0x00000001 (NEEDED) Shared library: [libc.so.6] ~/Desktop/dgemm$readelf -d $(R RHOME)/bin/Rscript | grep "NEEDED" 0x00000001 (NEEDED) Shared library: [libc.so.6] 这里我们需要选择Rscript,因为R加载libR.so,它会自动加载引用BLAS libblas.so.3: ~/Desktop/dgemm$readelf -d $(R RHOME)/lib/libR.so | grep blas 0x00000001 (NEEDED) Shared library: [libblas.so.3] ~/Desktop/dgemm$ls -l /etc/alternatives/libblas.so.3 ... 31 May /etc/alternatives/libblas.so.3 -> /usr/lib/libblas/libblas.so.3.0 ~/Desktop/dgemm$readelf -d /usr/lib/libblas/libblas.so.3 | grep SONAME 0x0000000e (SONAME) Library soname: [libblas.so.3] 相比之下,Rscript提供了一个更清洁的环境. 第2部分:OpenBLAS 从OpenBLAS下载源文件和简单的make命令后,形式为libopenblas-< arch> – < release> .so-< version>的共享库.可以生成.请注意,我们没有root权限来安装它;相反,我们将这个库复制到我们的工作目录?/ Desktop / dgemm中,并将其重命名为libopenblas.so.同时我们必须创建另一个名为libopenblas.so.0的副本,因为这是运行时加载器将寻找的SONAME: ~/Desktop/dgemm$readelf -d libopenblas.so | grep "RPATH|SONAME" 0x0000000e (SONAME) Library soname: [libopenblas.so.0] 请注意,没有给出RPATH属性,这意味着该库应该放在/usr/lib中,我们应该调用ldconfig将它添加到ld.so.cache中.但是我们再次没有root权限来执行此操作.事实上,如果可以做到这一点,那么所有的困难都消失了.然后我们可以使用update-alternatives –config libblas.so.3来有效地将R链接到OpenBLAS. 第3部分:C代码,Makefile和R代码 这是一个C脚本mmperf.c计算乘以2个大小为N的矩阵的GFLOP: #include <R.h> #include <Rmath.h> #include <Rinternals.h> #include <R_ext/BLAS.h> #include <sys/time.h> /* standard C subroutine */ double mmperf (int n) { /* local vars */ int n2 = n * n,tmp; double *A,*C,one = 1.0; struct timeval t1,t2; double elapsedTime,GFLOPs; /* simulate N-by-N matrix A */ A = (double *)calloc(n2,sizeof(double)); GetRNGstate(); tmp = 0; while (tmp < n2) {A[tmp] = runif(0.0,1.0); tmp++;} PutRNGstate(); /* generate N-by-N zero matrix C */ C = (double *)calloc(n2,sizeof(double)); /* time 'dgemm.f' for C <- A * A + C */ gettimeofday(&t1,NULL); F77_CALL(dgemm) ("N","N",&n,&one,A,C,&n); gettimeofday(&t2,NULL); /* free memory */ free(A); free(C); /* compute and return elapsedTime in microseconds (usec or 1e-6 sec) */ elapsedTime = (double)(t2.tv_sec - t1.tv_sec) * 1e+6; elapsedTime += (double)(t2.tv_usec - t1.tv_usec); /* convert microseconds to nanoseconds (1e-9 sec) */ elapsedTime *= 1e+3; /* compute and return GFLOPs */ GFLOPs = 2.0 * (double)n2 * (double)n / elapsedTime; return GFLOPs; } /* R wrapper */ SEXP R_mmperf (SEXP n) { double GFLOPs = mmperf(asInteger(n)); return ScalarReal(GFLOPs); } 这是一个简单的R脚本mmperf.R,用于报告案例N = 2000的GFLOP mmperf <- function (n) { dyn.load("mmperf.so") GFLOPs <- .Call("R_mmperf",n) dyn.unload("mmperf.so") return(GFLOPs) } GFLOPs <- round(mmperf(2000),2) cat(paste("GFLOPs =",GFLOPs,"n")) 最后有一个简单的makefile来生成共享库mmperf.so: mmperf.so: mmperf.o gcc -shared -L$(shell pwd) -Wl,-rpath=$(shell pwd) -o mmperf.so mmperf.o -lopenblas mmperf.o: mmperf.c gcc -fpic -O2 -I$(shell Rscript --default-packages=base --vanilla -e 'cat(R.home("include"))') -c mmperf.c 将所有这些文件放在工作目录?/ Desktop / dgemm下,并编译它: ~/Desktop/dgemm$make ~/Desktop/dgemm$readelf -d mmperf.so | grep "NEEDED|RPATH|SONAME" 0x00000001 (NEEDED) Shared library: [libopenblas.so.0] 0x00000001 (NEEDED) Shared library: [libc.so.6] 0x0000000f (RPATH) Library rpath: [/home/zheyuan/Desktop/dgemm] 输出向我们保证OpenBLAS已正确链接,并且正确设置了运行时加载路径. 第4部分:在R中测试OpenBLAS 让我们做 ~/Desktop/dgemm$Rscript --default-packages=base --vanilla mmperf.R 注意我们的脚本只需要R中的基本包,而–vanilla用于忽略R启动时的所有用户设置.在我的笔记本电脑上,我的程序返回 GFLOPs = 1.11 哎呀!这确实是参考BLAS性能而不是OpenBLAS(约为8-9 GFLOP). 第5部分:为什么? 说实话,我不知道为什么会这样.每个步骤似乎都正常工作.调用R时是否会发生微妙的事情?例如,出于某种原因,某些时候参考BLAS会覆盖OpenBLAS库的任何可能性吗?任何解释和解决方案?谢谢! 解决方法
首先,UNIX上的共享库旨在模仿归档库的工作方式(归档库首先存在).特别是这意味着如果你有libfoo.so和libbar.so,两者都定义符号foo,那么首先加载的是获胜者:从程序中的任何地方(包括来自libbar.so)对foo的所有引用都将绑定到foo的libfoo.sos定义. 这模仿了如果将程序与libfoo.a和libbar.a链接起来会发生什么,其中两个归档库都定义了相同的符号foo.有关存档链接here的更多信息. 从上面应该清楚,如果libblas.so.3和libopenblas.so.0定义了相同的符号集(他们这样做),并且如果首先将libblas.so.3加载到进程中,那么来自libopenblas的例程永远不会调用.so.0. 其次,你已经正确地决定,因为R直接链接到libR.so,并且由于libR.so直接链接到libblas.so.3,所以保证libopenblas.so.0将失败. 但是,你错误地认为Rscript更好,但事实并非如此:Rscript是一个很小的二进制文件(在我的系统上是11K;相比之下,libR.so是2.4MB),而且它所做的几乎都是R的执行.这是微不足道的.在strace输出: strace -e trace=execve /usr/bin/Rscript --default-packages=base --vanilla /dev/null execve("/usr/bin/Rscript",["/usr/bin/Rscript","--default-packages=base","--vanilla","/dev/null"],[/* 42 vars */]) = 0 execve("/usr/lib/R/bin/R",["/usr/lib/R/bin/R","--slave","--no-restore","--file=/dev/null","--args"],[/* 43 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_code=CLD_EXITED,si_pid=89625,si_status=0,si_utime=0,si_stime=0} --- --- SIGCHLD {si_signo=SIGCHLD,si_pid=89626,si_stime=0} --- execve("/usr/lib/R/bin/exec/R",["/usr/lib/R/bin/exec/R",[/* 51 vars */]) = 0 --- SIGCHLD {si_signo=SIGCHLD,si_pid=89630,si_stime=0} --- +++ exited with 0 +++ 这意味着当脚本开始执行时,已经加载了libblas.so.3,并且将作为mmperf.so的依赖项加载的libopenblas.so.0实际上不会用于任何事情.
大概.我可以想到两种可能的解决方案: >假设libopenblas.so.0实际上是libblas.so.3 对于#1,你需要ln -s libopenblas.so.0 libblas.so.3,然后通过适当地设置LD_LIBRARY_PATH,确保在系统之前找到你的libblas.so.3副本. 这似乎对我有用: mkdir /tmp/libblas # pretend that libc.so.6 is really libblas.so.3 cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/libblas/libblas.so.3 LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null Error in dyn.load(file,DLLpath = DLLpath,...) : unable to load shared object '/usr/lib/R/library/stats/libs/stats.so': /usr/lib/liblapack.so.3: undefined symbol: cgemv_ During startup - Warning message: package ‘stats’ in options("defaultPackages") was not found 注意我是如何得到一个错误的(我的“假装”libblas.so.3没有定义它所期望的符号,因为它实际上是libc.so.6的副本). 您还可以确认以这种方式加载了哪个版本的libblas.so.3: LD_DEBUG=libs LD_LIBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/null |& grep 'libblas.so.3' 91533: find library=libblas.so.3 [0]; searching 91533: trying file=/usr/lib/R/lib/libblas.so.3 91533: trying file=/usr/lib/x86_64-linux-gnu/libblas.so.3 91533: trying file=/usr/lib/jvm/java-7-openjdk-amd64/jre/lib/amd64/server/libblas.so.3 91533: trying file=/tmp/libblas/libblas.so.3 91533: calling init: /tmp/libblas/libblas.so.3 对于#2,你说:
但这似乎是一个虚假的论点:如果你可以构建libopenblas,当然你也可以构建自己的R版本. 更新:
符号和SONAME彼此无关. 您可以在readelf -Ws libblas.so.3和readelf -Ws libopenblas.so.0的输出中看到符号.与BLAS相关的符号(如cgemv_)将出现在两个库中. 您对SONAME的困惑可能来自Windows. Windows上的DLL设计完全不同.特别是,当FOO.DLL从BAR.DLL导入符号栏时,符号(bar)的名称和导入该符号的DLL(BAR.DLL)都记录在FOO.DLLs导入表中. 这样可以很容易地从BLAS.DLL导入R import cgemv_,而MMPERF.DLL从OPENBLAS.DLL导入相同的符号. 但是,这使得library interpositioning很难,并且与归档库的工作方式完全不同(即使在Windows上). 关于哪种设计总体上更好的意见不同,但两种系统都不可能改变其模型. UNIX可以通过多种方式模拟Windows样式的符号绑定:请参阅dlopen man page中的RTLD_DEEPBIND.注意:这些都充满了危险,可能会使UNIX专家感到困惑,没有被广泛使用,并且可能存在实施错误. 更新2:
是.
无论哪种方式都有效. (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |