gpt4 book ai didi

r - 在没有 root 访问权限的情况下,当 R 与引用 BLAS 链接时,使用调整后的 BLAS 运行

转载 作者:IT王子 更新时间:2023-10-29 00:54:07 26 4
gpt4 key购买 nike

谁能告诉我为什么我可以不成功测试 OpenBLAS dgemm通过以下方式在 R 中性能(在 GFLOP 中)?

  • 将 R 与“引用 BLAS”链接起来 libblas.so
  • 编译我的 C 程序 mmperf.c使用 OpenBLAS 库 libopenblas.so
  • 加载生成的共享库 mmperf.so进入R,调用R包装函数mmperf并举报 dgemm GFLOP 中的性能。

  • 第 1 点看起来很奇怪,但我别无选择,因为我在要测试的机器上没有 root 访问权限,因此无法实际链接到 OpenBLAS。来自 “未成功”我的意思是我的程序最终会报告 dgemm引用 BLAS 而不是 OpenBLAS 的性能。我希望有人可以向我解释:
  • 为什么我的方法行不通;
  • 是否有可能让它工作(这很重要,因为如果不可能,我必须编写一个 C main 函数并在 C 程序中完成我的工作。)

  • 这个问题我调查了两天,这里我将包括各种系统输出来帮助您进行诊断。为了使事情可重现,我还将包括代码、makefile 以及 shell 命令。

    第 1 部分:测试前的系统环境

    有两种方法可以调用 R,使用 RRscript .调用时加载的内容有一些不同:
    ~/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 个平方矩阵相乘的 GFLOPs N :
    #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, &n, &n, &one, A, &n, A, &n, &one, 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报告案例的 GFLOP N = 2000
    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

    注意我们的脚本只需要 base R 中的包,和 --vanilla用于忽略 R 启动时的所有用户设置。在我的笔记本电脑上,我的程序返回:
    GFLOPs = 1.11

    哎呀!这是真正引用 BLAS 性能而不是 OpenBLAS(大约 8-9 GFLOP)。

    第 5 部分:为什么?

    老实说,我不知道为什么会发生这种情况。每一步似乎都能正常工作。调用 R 时是否会发生一些微妙的事情?例如,由于某种原因,OpenBLAS 库在某些时候被引用 BLAS 覆盖的可能性有多大?有什么解释和解决办法吗?谢谢!

    最佳答案

    why my way does not work



    首先,UNIX 上的共享库旨在模仿归档库的工作方式(首先是归档库)。特别是这意味着如果您有 libfoo.solibbar.so , 均定义符号 foo ,那么首先加载的库就是获胜的库:所有对 foo 的引用从程序中的任何位置(包括来自 libbar.so )都将绑定(bind)到 libfoo.so foo的定义.

    这模拟了如果您将程序链接到 libfoo.a 会发生的情况。和 libbar.a ,其中两个存档库定义了相同的符号 foo .有关文件链接的更多信息 here .

    从上面应该清楚,如果 libblas.so.3libopenblas.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),它所做的几乎就是 execR .这在 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", "--vanilla", "--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_code=CLD_EXITED, si_pid=89626, si_status=0, si_utime=0, si_stime=0} ---
    execve("/usr/lib/R/bin/exec/R", ["/usr/lib/R/bin/exec/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null", "--args"], [/* 51 vars */]) = 0
    --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=89630, si_status=0, si_utime=0, si_stime=0} ---
    +++ exited with 0 +++

    这意味着当你的脚本开始执行时, libblas.so.3已加载, libopenblas.so.0将作为 mmperf.so 的依赖项加载实际上不会用于任何事情。

    is it possible at all to make it work



    大概。我可以想到两种可能的解决方案:
  • 假装libopenblas.so.0实际上是 libblas.so.3
  • 重建整个 R包针对libopenblas.so .

  • 对于#1,您需要 ln -s libopenblas.so.0 libblas.so.3 ,然后确保您的副本 libblas.so.3在系统一之前找到,通过设置 LD_LIBRARY_PATH适本地。

    这似乎对我有用:
    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,你说:

    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。 DLL Windows 上的 s 设计完全不同。特别是当 FOO.DLL进口符号 bar来自 BAR.DLL , 符号的名称 ( bar ) 和 DLL从中导入该符号( BAR.DLL )记录在 FOO.DLL 中s 导入表。

    这使得拥有 R 变得容易进口 cgemv_来自 BLAS.DLL , 而 MMPERF.DLLOPENBLAS.DLL 导入相同的符号.

    然而,这使得 library interpositioning很难,并且与存档库的工作方式完全不同(即使在 Windows 上)。

    关于哪种设计总体上更好,意见不一,但两个系统都不太可能改变其模型。

    UNIX 有多种方法可以模拟 Windows 风格的符号绑定(bind):参见 RTLD_DEEPBIND在 dlopen man page .当心:这些都充满了危险,可能会使 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?



    无论哪种方式都有效。

    关于r - 在没有 root 访问权限的情况下,当 R 与引用 BLAS 链接时,使用调整后的 BLAS 运行,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37041819/

    26 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com