GNU/Linux >> Tutoriales Linux >  >> Linux

Sin acceso de root, ejecute R con BLAS sintonizado cuando esté vinculado con BLAS de referencia

por qué mi camino no funciona

Primero, las bibliotecas compartidas en UNIX están diseñadas para imitar la forma en que funcionan las bibliotecas de archivos (las bibliotecas de archivos estaban allí primero). En particular, eso significa que si tiene libfoo.so y libbar.so , ambos definen el símbolo foo , la biblioteca que se cargue primero es la que gana:todas las referencias a foo desde cualquier lugar dentro del programa (incluso desde libbar.so ) se unirá a libfoo.so s definición de foo .

Esto imita lo que sucedería si vinculara su programa contra libfoo.a y libbar.a , donde ambas bibliotecas de archivo definieron el mismo símbolo foo . Más información sobre la vinculación de archivos aquí.

Debería quedar claro desde arriba, que si libblas.so.3 y libopenblas.so.0 definen el mismo conjunto de símbolos (que hacen ), y si libblas.so.3 se carga en el proceso primero, luego las rutinas de libopenblas.so.0 nunca ser llamado.

En segundo lugar, ha decidido correctamente que desde R enlaces directos contra libR.so , y desde libR.so enlaces directos contra libblas.so.3 , se garantiza que libopenblas.so.0 perderá la batalla.

Sin embargo, erróneamente decidió que Rscript es mejor, pero no lo es:Rscript es un pequeño binario (11K en mi sistema; comparar con 2,4 MB para libR.so ), y aproximadamente todo lo que hace es exec de R . Esto es trivial de ver en strace salida:

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

Lo que significa que para cuando su secuencia de comandos comience a ejecutarse, libblas.so.3 ha sido cargado, y libopenblas.so.0 que se cargará como una dependencia de mmperf.so no realmente ser usado para cualquier cosa.

¿Es posible hacer que funcione?

Probablemente. Se me ocurren dos posibles soluciones:

  1. Pretende que libopenblas.so.0 en realidad es libblas.so.3
  2. Reconstruir todo R paquete contra libopenblas.so .

Para el n.º 1, debe ln -s libopenblas.so.0 libblas.so.3 , entonces asegúrese de que su copia de libblas.so.3 se encuentra antes que el del sistema, configurando LD_LIBRARY_PATH apropiadamente.

Esto parece funcionar para mí:

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

Tenga en cuenta cómo obtuve un error (mi "fingir" libblas.so.3 no define los símbolos que se esperan de él, ya que en realidad es una copia de libc.so.6 ).

También puede confirmar qué versión de libblas.so.3 se está cargando de esta manera:

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

Para el #2, dijiste:

No tengo acceso de root en las máquinas que quiero probar, por lo que es imposible vincular a OpenBLAS.

pero eso parece ser un argumento falso:si puedes construir libopenblas , seguramente también puedes construir tu propia versión de R .

Actualización:

Mencionaste al principio que libblas.so.3 y libopenblas.so.0 definen el mismo símbolo, ¿qué significa esto? Tienen SONAME diferente, ¿eso es insuficiente para distinguirlos por el sistema?

Los símbolos y el SONAME no tener nada hacer unos con otros.

Puede ver símbolos en la salida de readelf -Ws libblas.so.3 y readelf -Ws libopenblas.so.0 . Símbolos relacionados con BLAS , como cgemv_ , aparecerá en ambas bibliotecas.

Tu confusión sobre SONAME posiblemente viene de Windows. El DLL s en Windows están diseñados de manera completamente diferente. En particular, cuando FOO.DLL importa símbolo bar de BAR.DLL , ambos el nombre del símbolo (bar ) y el DLL desde el que se importó ese símbolo (BAR.DLL ) se registran en el FOO.DLL s tabla de importación.

Eso hace que sea fácil tener R importar cgemv_ de BLAS.DLL , mientras que MMPERF.DLL importa el mismo símbolo de OPENBLAS.DLL .

Sin embargo, eso dificulta la interposición de bibliotecas y funciona de manera completamente diferente a la forma en que funcionan las bibliotecas de archivos (incluso en Windows).

Las opiniones difieren sobre qué diseño es mejor en general, pero es probable que ningún sistema cambie su modelo.

Hay formas en que UNIX puede emular el enlace de símbolos al estilo de Windows:consulte RTLD_DEEPBIND en la página man de dlopen. Cuidado:estos están llenos de peligros, es probable que confundan a los expertos de UNIX, no se usan mucho y es probable que tengan errores de implementación.

Actualización 2:

¿Quieres decir que compilo R y lo instalo en mi directorio de inicio?

Sí.

Luego, cuando quiero invocarlo, debería dar explícitamente la ruta a mi versión del programa ejecutable, de lo contrario, ¿se podría invocar el del sistema? ¿O puedo poner esta ruta en la primera posición de la variable de entorno $PATH para engañar al sistema?

De cualquier manera funciona.


********************

Solución 1:

********************

Gracias a Employeeed Russian, mi problema finalmente se resolvió. La investigación requiere habilidades importantes en depuración y parches del sistema Linux , y creo que esto es un gran activo que aprendí. Aquí publicaría una solución, además de corregir varios puntos en mi publicación original.

1 Acerca de la invocación de R

En mi publicación original, mencioné que hay dos formas de iniciar R, ya sea a través de R o Rscript . Sin embargo, he exagerado erróneamente su diferencia. Ahora investiguemos su proceso de inicio, a través de una importante función de depuración de Linux strace (ver man strace ). En realidad, suceden muchas cosas interesantes después de que escribimos un comando en el shell, y podemos usar

strace -e trace=process [command]

rastrear todas las llamadas al sistema relacionadas con la gestión de procesos. Como resultado, podemos observar los pasos de bifurcación, espera y ejecución de un proceso. Aunque no se indica en la página del manual, @Employed Russian muestra que es posible especificar solo una subclase de process , por ejemplo, execve para los pasos de ejecución.

Para R tenemos

~/Desktop/dgemm$ time strace -e trace=execve R --vanilla < /dev/null > /dev/null
execve("/usr/bin/R", ["R", "--vanilla"], [/* 70 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5777, si_status=0, si_utime=0, si_stime=0} ---
execve("/usr/lib/R/bin/exec/R", ["/usr/lib/R/bin/exec/R", "--vanilla"], [/* 79 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5778, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

real    0m0.345s
user    0m0.256s
sys     0m0.068s

mientras que para Rscript tenemos

~/Desktop/dgemm$ time strace -e trace=execve Rscript --default-packages=base --vanilla /dev/null
execve("/usr/bin/Rscript", ["Rscript", "--default-packages=base", "--vanilla", "/dev/null"], [/* 70 vars */]) = 0
execve("/usr/lib/R/bin/R", ["/usr/lib/R/bin/R", "--slave", "--no-restore", "--vanilla", "--file=/dev/null"], [/* 71 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5822, si_status=0, si_utime=0, si_stime=0} ---
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5823, 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"], [/* 80 vars */]) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=5827, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

real    0m0.063s
user    0m0.020s
sys     0m0.028s

También hemos usado time para medir el tiempo de arranque. Tenga en cuenta que

  1. Rscript es aproximadamente 5,5 veces más rápido que R . Una razón es que R cargará 6 paquetes predeterminados al inicio, mientras que Rscript solo carga un base paquete por control:--default-packages=base . Pero sigue siendo mucho más rápido incluso sin esta configuración.
  2. Al final, ambos procesos de inicio se dirigen a $(R RHOME)/bin/exec/R , y en mi publicación original, ya he explotado readelf -d para mostrar que este ejecutable cargará libR.so , que están vinculados con libblas.so.3 . De acuerdo con la explicación de @Employed Russian, la biblioteca BLAS cargada primero ganará, por lo que no hay forma de que mi método original funcione.
  3. Para ejecutar con éxito strace , hemos utilizado el increíble archivo /dev/null como archivo de entrada y archivo de salida cuando sea necesario. Por ejemplo, Rscript exige un archivo de entrada, mientras que R exige ambos. Alimentamos el dispositivo nulo a ambos para que el comando se ejecute sin problemas y la salida sea limpia. El dispositivo nulo es un archivo físicamente existente, pero funciona de manera sorprendente. Al leerlo, no contiene nada; mientras escribe en él, descarta todo.

2. Hacer trampa R

Ahora desde libblas.so se cargará de todos modos, lo único que podemos hacer es proporcionar nuestra propia versión de esta biblioteca. Como dije en la publicación original, si tenemos acceso de root, esto es realmente fácil, usando update-alternatives --config libblas.so.3 , por lo que el sistema Linux nos ayudará a completar este cambio. Pero @Employed Russian ofrece una forma increíble de engañar al sistema sin acceso a la raíz:¡Veamos cómo R encuentra la biblioteca BLAS en el inicio y asegurémonos de alimentar nuestra versión antes de que se encuentre el sistema predeterminado! Para monitorear cómo se encuentran y cargan las bibliotecas compartidas, use la variable de entorno LD_DEBUG .

Hay una serie de variables de entorno de Linux con el prefijo LD_ , como se documenta en man ld.so . Estas variables se pueden asignar antes de un ejecutable, de modo que podamos cambiar la función de ejecución de un programa. Algunas variables útiles incluyen:

  • LD_LIBRARY_PATH para configurar la ruta de búsqueda de la biblioteca en tiempo de ejecución;
  • LD_DEBUG para rastrear, buscar y cargar bibliotecas compartidas;
  • LD_TRACE_LOADED_OBJECTS para mostrar toda la biblioteca cargada por un programa (se comporta de manera similar a ldd );
  • LD_PRELOAD para forzar la inyección de una biblioteca en un programa desde el principio, antes de que se busquen todas las demás bibliotecas;
  • LD_PROFILE y LD_PROFILE_OUTPUT para perfilar uno biblioteca compartida especificada. Usuario R que ha leído la sección 3.4.1.1 sprof of Writing R extensions debe recordar que esto se usa para generar perfiles de código compilado desde dentro de R.

El uso de LD_DEBUG puede ser visto por:

~/Desktop/dgemm$ LD_DEBUG=help cat
Valid options for the LD_DEBUG environment variable are:

  libs        display library search paths
  reloc       display relocation processing
  files       display progress for input file
  symbols     display symbol table processing
  bindings    display information about symbol binding
  versions    display version dependencies
  scopes      display scope information
  all         all previous options combined
  statistics  display relocation statistics
  unused      determined unused DSOs
  help        display this help message and exit

  To direct the debugging output into a file instead of standard output a filename can be specified using the LD_DEBUG_OUTPUT environment variable.

Aquí estamos particularmente interesados ​​en usar LD_DEBUG=libs . Por ejemplo,

~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas
  5974: find library=libblas.so.3 [0]; searching
  5974:   trying file=/usr/lib/R/lib/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3
  5974:   trying file=/usr/lib/i386-linux-gnu/libblas.so.3
  5974:   trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3
  5974:   trying file=/usr/lib/libblas.so.3
  5974: calling init: /usr/lib/libblas.so.3
  5974: calling fini: /usr/lib/libblas.so.3 [0]

muestra varios intentos que el programa R intentó localizar y cargar libblas.so.3 . Entonces, si pudiéramos proporcionar nuestra propia versión de libblas.so.3 y asegúrese de que R lo encuentre primero, entonces el problema está resuelto.

Primero hagamos un enlace simbólico libblas.so.3 en nuestra ruta de trabajo a la biblioteca OpenBLAS libopenblas.so , luego expanda el LD_LIBRARY_PATH predeterminado con nuestra ruta de trabajo (y exportar es):

~/Desktop/dgemm$ ln -sf libopenblas.so libblas.so.3
~/Desktop/dgemm$ export LD_LIBRARY_PATH = $(pwd):$LD_LIBRARY_PATH  ## put our working path at top

Ahora revisemos nuevamente el proceso de carga de la biblioteca:

~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas
  6063: find library=libblas.so.3 [0]; searching
  6063:   trying file=/usr/lib/R/lib/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3
  6063:   trying file=/usr/lib/i386-linux-gnu/libblas.so.3
  6063:   trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3
  6063:   trying file=/home/zheyuan/Desktop/dgemm/libblas.so.3
  6063: calling init: /home/zheyuan/Desktop/dgemm/libblas.so.3
  6063: calling fini: /home/zheyuan/Desktop/dgemm/libblas.so.3 [0]

¡Excelente! Hemos engañado con éxito a R.

3. Experimente con OpenBLAS

~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R
GFLOPs = 8.77

¡Ahora, todo funciona como se esperaba!

4. Desarmar LD_LIBRARY_PATH (estar a salvo)

Es una buena práctica desarmar LD_LIBRARY_PATH después de su uso.

~/Desktop/dgemm$ unset LD_LIBRARY_PATH

********************

Solución 2:

********************

Aquí ofrecemos otra solución, explotando la variable de entorno LD_PRELOAD mencionado en nuestra solución 1 . El uso de LD_PRELOAD es más "brutal", ya que obliga a cargar una biblioteca dada en el programa antes que cualquier otro programa, incluso antes que la biblioteca C libc.so ! Esto se usa a menudo para parches urgentes en el desarrollo de Linux.

Como se muestra en la parte 2 de la publicación original , la biblioteca BLAS compartida libopenblas.so tiene SONAME libopenblas.so.0 . Un SONAME es un nombre interno que el cargador de bibliotecas dinámicas buscaría en tiempo de ejecución, por lo que debemos crear un enlace simbólico a libopenblas.so con este SONAME :

~/Desktop/dgemm$ ln -sf libopenblas.so libopenblas.so.0

luego lo exportamos:

~/Desktop/dgemm$ export LD_PRELOAD=$(pwd)/libopenblas.so.0

Tenga en cuenta que una ruta completa a libopenblas.so.0 debe enviarse a LD_PRELOAD para una carga exitosa, incluso si libopenblas.so.0 está bajo $(pwd) .

Ahora lanzamos Rscript y verifique lo que sucede por LD_DEBUG :

~/Desktop/dgemm$ LD_DEBUG=libs Rscript --default-packages=base --vanilla /dev/null |& grep blas
  4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4865: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4868: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0]
  4870: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4869: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4867: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0]
  4860: find library=libblas.so.3 [0]; searching
  4860:   trying file=/usr/lib/R/lib/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/i686/sse2/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/i686/cmov/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/i686/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/sse2/libblas.so.3
  4860:   trying file=/usr/lib/i386-linux-gnu/libblas.so.3
  4860:   trying file=/usr/lib/jvm/java-7-openjdk-i386/jre/lib/i386/client/libblas.so.3
  4860:   trying file=/usr/lib/libblas.so.3
  4860: calling init: /usr/lib/libblas.so.3
  4860: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4874: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4876: calling init: /home/zheyuan/Desktop/dgemm/libopenblas.so
  4860: calling fini: /home/zheyuan/Desktop/dgemm/libopenblas.so [0]
  4860: calling fini: /usr/lib/libblas.so.3 [0]

Comparando con lo que vimos en la solución 1 engañando a R con nuestra propia versión de libblas.so.3 , podemos ver que

  • libopenblas.so.0 se carga primero, por lo tanto, se encuentra primero por Rscript;
  • después de libopenblas.so.0 se encuentra, Rscript continúa buscando y cargando libblas.so.3 . Sin embargo, esto no afectará al "primero en llegar, primero en servir" regla, explicada en la respuesta original.

Bien, todo funciona, así que probamos nuestro mmperf.c programa:

~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R
GFLOPs = 9.62

El resultado 9,62 es mayor que el 8,77 que vimos en la solución anterior simplemente por casualidad. Como prueba para usar OpenBLAS, no ejecutamos el experimento muchas veces para obtener un resultado más preciso.

Luego, como de costumbre, desactivamos la variable de entorno al final:

~/Desktop/dgemm$ unset LD_PRELOAD

Linux
  1. ¿Cómo ejecutar un programa específico como root sin una solicitud de contraseña?

  2. ¿Cómo ejecutar un comando que implique redirigir o canalizar con Sudo?

  3. Cuando se ejecuta a un nivel de ejecución, ¿ejecuta niveles de ejecución anteriores?

  4. ¿Instalar zsh sin acceso de root?

  5. sudo sin contraseña cuando se inicia sesión con claves privadas SSH

Cómo agregar repositorios a Red Hat Linux con y sin proxy

CÓMO:Ejecutar Linux en Android sin root

Ejecutar ifconfig sin sudo

¿Cómo instalar localmente .deb sin apt-get, dpkg o acceso de root?

Enlace a puertos inferiores a 1024 sin acceso a la raíz

Linux:¿administradores de sistemas productivos sin root (asegurando la propiedad intelectual)?