>链接R与“参考BLAS”libblas.so
>使用OpenBLAS库libopenblas.so编译我的C程序mmperf.c
>将生成的共享库mmperf.so加载到R中,调用R包装函数mmperf并在GFLOP中报告dgemm性能.
第1点看起来很奇怪,但我别无选择,因为我在我想测试的机器上没有root访问权限,所以实际链接到OpenBLAS是不可能的. “不成功”我的意思是我的程序最终报告dgemm性能参考BLAS而不是OpenBLAS.我希望有人可以向我解释一下:
>为什么我的方式不起作用;
>是否有可能使它工作(这很重要,因为如果不可能,我必须编写一个C main函数并在C程序中完成我的工作.)
我已经调查了这个问题两天,在这里我将包括各种系统输出,以帮助您进行诊断.为了使事情可以重现,我还将包括代码,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 blas0x00000001 (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 SOname0x0000000e (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 -lopenblasmmperf.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库的任何可能性吗?任何解释和解决方案?谢谢!
解决方法why my way does not work
首先,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/nullexecve("/usr/bin/Rscript",["/usr/bin/Rscript","--default-packages=base","--vanilla","/dev/null"],[/* 42 vars */]) = 0execve("/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实际上不会用于任何事情.
is it possible at all to make it work
大概.我可以想到两种可能的解决方案:
>假设libopenblas.so.0实际上是libblas.so.3
>针对libopenblas.so重建整个R包.
对于#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.3cp /lib/x86_64-linux-gnu/libc.so.6 /tmp/libblas/libblas.so.3LD_liBRARY_PATH=/tmp/libblas /usr/bin/Rscript /dev/nullError 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,你说:
I have no root access on machines I want to test,so actual linking to OpenBLAS is impossible.
但这似乎是一个虚假的论点:如果你可以构建libopenblas,当然你也可以构建自己的R版本.
更新:
You mentioned in the beginning that libblas.so.3 and libopenblas.so.0 define the same symbol,what does this mean? They have different SOname,is that insufficIEnt to distinguish them by the system?
符号和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:
you mean I compile R and install it under my home directory?
是.
Then when I want to invoke it,I should explicitly give the path to my version of executable program,otherwise the one on the system might be invoked instead? Or,can I put this path at the first position of environment variable $PATH to cheat the system?
无论哪种方式都有效.
总结以上是内存溢出为你收集整理的如果没有root访问权限,则在与参考BLAS链接时使用调优的BLAS运行R.全部内容,希望文章能够帮你解决如果没有root访问权限,则在与参考BLAS链接时使用调优的BLAS运行R.所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)