Without root access, run R with tuned BLAS when it is linked with reference BLAS

前端 未结 3 1157
温柔的废话
温柔的废话 2020-12-10 17:11

Can any one tell me why I can not successfully test OpenBLAS\'s dgemm performance (in GFLOPs) in R via the following way?

  1. link R
3条回答
  •  一个人的身影
    2020-12-10 18:05

    why my way does not work

    First, shared libraries on UNIX are designed to mimic the way archive libraries work (archive libraries were there first). In particular that means that if you have libfoo.so and libbar.so, both defining symbol foo, then whichever library is loaded first is the one that wins: all references to foo from anywhere within the program (including from libbar.so) will bind to libfoo.sos definition of foo.

    This mimics what would happen if you linked your program against libfoo.a and libbar.a, where both archive libraries defined the same symbol foo. More info on archive linking here.

    It should be clear from above, that if libblas.so.3 and libopenblas.so.0 define the same set of symbols (which they do), and if libblas.so.3 is loaded into the process first, then routines from libopenblas.so.0 will never be called.

    Second, you've correctly decided that since R directly links against libR.so, and since libR.so directly links against libblas.so.3, it is guaranteed that libopenblas.so.0 will lose the battle.

    However, you erroneously decided that Rscript is better, but it's not: Rscript is a tiny binary (11K on my system; compare to 2.4MB for libR.so), and approximately all it does is exec of R. This is trivial to see in strace output:

    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 +++
    

    Which means that by the time your script starts executing, libblas.so.3 has been loaded, and libopenblas.so.0 that will be loaded as a dependency of mmperf.so will not actually be used for anything.

    is it possible at all to make it work

    Probably. I can think of two possible solutions:

    1. Pretend that libopenblas.so.0 is actually libblas.so.3
    2. Rebuild entire R package against libopenblas.so.

    For #1, you need to ln -s libopenblas.so.0 libblas.so.3, then make sure that your copy of libblas.so.3 is found before the system one, by setting LD_LIBRARY_PATH appropriately.

    This appears to work for me:

    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
    

    Note how I got an error (my "pretend" libblas.so.3 doesn't define symbols expected of it, since it's really a copy of libc.so.6).

    You can also confirm which version of libblas.so.3 is getting loaded this way:

    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
    

    For #2, you said:

    I have no root access on machines I want to test, so actual linking to OpenBLAS is impossible.

    but that seems to be a bogus argument: if you can build libopenblas, surely you can also build your own version of R.

    Update:

    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?

    The symbols and the SONAME have nothing to do with each other.

    You can see symbols in the output from readelf -Ws libblas.so.3 and readelf -Ws libopenblas.so.0. Symbols related to BLAS, such as cgemv_, will appear in both libraries.

    Your confusion about SONAME possibly comes from Windows. The DLLs on Windows are designed completely differently. In particular, when FOO.DLL imports symbol bar from BAR.DLL, both the name of the symbol (bar) and the DLL from which that symbol was imported (BAR.DLL) are recorded in the FOO.DLLs import table.

    That makes it easy to have R import cgemv_ from BLAS.DLL, while MMPERF.DLL imports the same symbol from OPENBLAS.DLL.

    However, that makes library interpositioning hard, and works completely differently from the way archive libraries work (even on Windows).

    Opinions differ on which design is better overall, but neither system is likely to ever change its model.

    There are ways for UNIX to emulate Windows-style symbol binding: see RTLD_DEEPBIND in dlopen man page. Beware: these are fraught with peril, likely to confuse UNIX experts, are not widely used, and likely to have implementation bugs.

    Update 2:

    you mean I compile R and install it under my home directory?

    Yes.

    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?

    Either way works.

提交回复
热议问题