GNU/Linux >> Tutoriels Linux >  >> Linux

Sans accès root, exécutez R avec BLAS réglé lorsqu'il est lié à la référence BLAS

pourquoi ma méthode ne fonctionne pas

Tout d'abord, les bibliothèques partagées sous UNIX sont conçues pour imiter le fonctionnement des bibliothèques d'archives (les bibliothèques d'archives étaient les premières). Cela signifie notamment que si vous avez libfoo.so et libbar.so , tous deux définissant le symbole foo , alors la bibliothèque chargée en premier est celle qui gagne :toutes les références à foo de n'importe où dans le programme (y compris de libbar.so ) se liera à libfoo.so s définition de foo .

Cela imite ce qui se passerait si vous liiez votre programme à libfoo.a et libbar.a , où les deux bibliothèques d'archives définissent le même symbole foo . Plus d'informations sur les liens vers les archives ici.

Il devrait être clair d'en haut que si libblas.so.3 et libopenblas.so.0 définir le même ensemble de symboles (ce qu'ils font ), et si libblas.so.3 est d'abord chargé dans le processus, puis les routines de libopenblas.so.0 ne sera jamais être appelé.

Deuxièmement, vous avez correctement décidé que depuis R liens directs vers libR.so , et depuis libR.so liens directs vers libblas.so.3 , il est garanti que libopenblas.so.0 perdra la bataille.

Cependant, vous à tort a décidé que Rscript c'est mieux, mais ce n'est pas le cas :Rscript est un minuscule binaire (11 Ko sur mon système ; comparer à 2,4 Mo pour libR.so ), et à peu près tout ce qu'il fait est exec de R . C'est trivial à voir dans strace sortie :

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

Ce qui signifie qu'au moment où votre script commence à s'exécuter, libblas.so.3 a été chargé, et libopenblas.so.0 qui sera chargé en tant que dépendance de mmperf.so ne sera pas en fait être utilisé pour quoi que ce soit.

est-il possible de le faire fonctionner

Probablement. Je peux penser à deux solutions possibles :

  1. Imaginez que libopenblas.so.0 est en fait libblas.so.3
  2. Reconstruire l'intégralité du R paquet contre libopenblas.so .

Pour #1, vous devez ln -s libopenblas.so.0 libblas.so.3 , puis assurez-vous que votre copie de libblas.so.3 se trouve avant celui du système, en mettant LD_LIBRARY_PATH de manière appropriée.

Cela semble fonctionner pour moi :

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

Notez comment j'ai eu une erreur (mon "faire semblant" libblas.so.3 ne définit pas les symboles attendus de lui, puisqu'il s'agit en fait d'une copie de libc.so.6 ).

Vous pouvez également confirmer quelle version de libblas.so.3 est chargé de cette façon :

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

Pour #2, vous avez dit :

Je n'ai pas d'accès root sur les machines que je veux tester, donc la liaison réelle à OpenBLAS est impossible.

mais cela semble être un faux argument :si vous pouvez construire libopenblas , vous pouvez sûrement aussi construire votre propre version de R .

Mise à jour :

Vous avez mentionné au début que libblas.so.3 et libopenblas.so.0 définissent le même symbole, qu'est-ce que cela signifie ? Ils ont des SONAME différents, est-ce insuffisant pour les distinguer par le système ?

Les symboles et le SONAME n'ont rien à faire les uns avec les autres.

Vous pouvez voir des symboles dans la sortie de readelf -Ws libblas.so.3 et readelf -Ws libopenblas.so.0 . Symboles liés à BLAS , comme cgemv_ , apparaîtra dans les deux bibliothèques.

Votre confusion à propos de SONAME peut-être vient de Windows. Le DLL s sur Windows sont conçus complètement différemment. En particulier, lorsque FOO.DLL importe le symbole bar à partir de BAR.DLL , les deux le nom du symbole (bar ) et le DLL d'où ce symbole a été importé (BAR.DLL ) sont enregistrés dans le FOO.DLL s importer la table.

Cela facilite l'utilisation de R importer cgemv_ à partir de BLAS.DLL , tandis que MMPERF.DLL importe le même symbole depuis OPENBLAS.DLL .

Cependant, cela rend l'interposition de bibliothèques difficile et fonctionne complètement différemment de la façon dont fonctionnent les bibliothèques d'archives (même sous Windows).

Les avis divergent sur la meilleure conception globale, mais aucun des deux systèmes ne changera probablement jamais de modèle.

Il existe des moyens pour UNIX d'émuler la liaison de symboles de style Windows :voir RTLD_DEEPBIND dans la page de manuel dlopen. Attention :elles sont pleines de dangers, susceptibles de dérouter les experts UNIX, ne sont pas largement utilisées et sont susceptibles d'avoir des bogues d'implémentation.

Mise à jour 2 :

vous voulez dire que je compile R et l'installe dans mon répertoire personnel ?

Oui.

Ensuite, lorsque je veux l'invoquer, je dois donner explicitement le chemin d'accès à ma version du programme exécutable, sinon celui du système pourrait être invoqué à la place ? Ou puis-je mettre ce chemin en première position de la variable d'environnement $PATH pour tromper le système ?

Dans les deux cas, cela fonctionne.


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

Solution 1 :

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

Grâce à Employed Russian, mon problème est enfin résolu. L'enquête nécessite des compétences importantes en débogage et correction du système Linux , et je crois que c'est un grand atout que j'ai appris. Ici, je publierais une solution, ainsi que la correction de plusieurs points dans mon message d'origine.

1 À propos de l'appel de R

Dans mon message d'origine, j'ai mentionné qu'il existe deux façons de lancer R, soit via R ou Rscript . Cependant, j'ai exagéré à tort leur différence. Examinons maintenant leur processus de démarrage, via une importante fonction de débogage Linux strace (voir man strace ). Il se passe en fait beaucoup de choses intéressantes après avoir tapé une commande dans le shell, et nous pouvons utiliser

strace -e trace=process [command]

tracer tous les appels système impliquant la gestion des processus. En conséquence, nous pouvons observer les étapes de fork, d'attente et d'exécution d'un processus. Bien que cela ne soit pas indiqué dans la page de manuel, @Employed Russian montre qu'il est possible de spécifier uniquement une sous-classe de process , par exemple, execve pour les étapes d'exécution.

Pour R nous avons

~/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

tandis que pour Rscript nous avons

~/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

Nous avons également utilisé time pour mesurer le temps de démarrage. Notez que

  1. Rscript est environ 5,5 fois plus rapide que R . L'une des raisons est que R chargera 6 packages par défaut au démarrage, tandis que Rscript ne charge qu'un seul base package par contrôle :--default-packages=base . Mais c'est toujours beaucoup plus rapide même sans ce paramètre.
  2. En fin de compte, les deux processus de démarrage sont dirigés vers $(R RHOME)/bin/exec/R , et dans mon message d'origine, j'ai déjà exploité readelf -d pour montrer que cet exécutable va charger libR.so , qui sont liés à libblas.so.3 . Selon l'explication de @Employed Russian, la bibliothèque BLAS chargée en premier gagnera, il n'y a donc aucun moyen que ma méthode originale fonctionne.
  3. Pour exécuter avec succès strace , nous avons utilisé l'incroyable fichier /dev/null comme fichier d'entrée et fichier de sortie si nécessaire. Par exemple, Rscript demande un fichier d'entrée, tandis que R exige les deux. Nous alimentons le périphérique nul à la fois pour que la commande s'exécute correctement et que la sortie soit propre. Le périphérique nul est un fichier existant physiquement, mais fonctionne étonnamment. En le lisant, il ne contient rien; en lui écrivant, il supprime tout.

2. Triche R

Maintenant depuis libblas.so sera chargé de toute façon, la seule chose que nous pouvons faire est de fournir notre propre version de cette bibliothèque. Comme je l'ai dit dans le message d'origine, si nous avons un accès root, c'est vraiment facile, en utilisant update-alternatives --config libblas.so.3 , de sorte que le système Linux nous aidera à terminer ce changement. Mais @Employed Russian offre un moyen génial de tromper le système sans accès root :vérifions comment R trouve la bibliothèque BLAS au démarrage, et assurons-nous d'alimenter notre version avant que la valeur par défaut du système ne soit trouvée ! Pour surveiller la façon dont les bibliothèques partagées sont trouvées et chargées, utilisez la variable d'environnement LD_DEBUG .

Il existe un certain nombre de variables d'environnement Linux avec le préfixe LD_ , comme documenté dans man ld.so . Ces variables peuvent être affectées avant un exécutable, afin que nous puissions modifier la fonctionnalité d'exécution d'un programme. Certaines variables utiles incluent :

  • LD_LIBRARY_PATH pour définir le chemin de recherche de la bibliothèque d'exécution ;
  • LD_DEBUG pour tracer la recherche et le chargement des bibliothèques partagées ;
  • LD_TRACE_LOADED_OBJECTS pour afficher toutes les bibliothèques chargées par un programme (se comporte comme ldd );
  • LD_PRELOAD pour forcer l'injection d'une bibliothèque dans un programme au tout début, avant que toutes les autres bibliothèques ne soient recherchées ;
  • LD_PROFILE et LD_PROFILE_OUTPUT pour profiler un bibliothèque partagée spécifiée. Utilisateur R ayant lu la section 3.4.1.1 sprof of Writing R extensions doivent se rappeler que cela est utilisé pour profiler le code compilé à partir de R.

L'utilisation de LD_DEBUG peut être vu par :

~/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.

Ici, nous sommes particulièrement intéressés par l'utilisation de LD_DEBUG=libs . Par exemple,

~/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]

montre diverses tentatives que le programme R a tenté de localiser et de charger libblas.so.3 . Donc, si nous pouvions fournir notre propre version de libblas.so.3 , et assurez-vous que R le trouve en premier, puis le problème est résolu.

Faisons d'abord un lien symbolique libblas.so.3 dans notre chemin de travail vers la bibliothèque OpenBLAS libopenblas.so , puis développez le LD_LIBRARY_PATH par défaut avec notre chemin de travail (et exporter il):

~/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

Maintenant, vérifions à nouveau le processus de chargement de la bibliothèque :

~/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]

Super! Nous avons trompé R avec succès.

3. Expérimentez avec OpenBLAS

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

Maintenant, tout fonctionne comme prévu !

4. Désactiver LD_LIBRARY_PATH (pour être en sécurité)

Il est recommandé de désactiver LD_LIBRARY_PATH après utilisation.

~/Desktop/dgemm$ unset LD_LIBRARY_PATH

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

Solution 2 :

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

Nous proposons ici une autre solution, en exploitant la variable d'environnement LD_PRELOAD mentionné dans notre solution 1 . L'utilisation de LD_PRELOAD est plus "brutal", car il force le chargement d'une bibliothèque donnée dans le programme avant tout autre programme, même avant la bibliothèque C libc.so ! Ceci est souvent utilisé pour les correctifs urgents dans le développement Linux.

Comme indiqué dans la partie 2 du message d'origine , la bibliothèque partagée BLAS libopenblas.so a SONAME libopenblas.so.0 . Un SONAME est un nom interne que le chargeur de bibliothèque dynamique rechercherait au moment de l'exécution, nous devons donc créer un lien symbolique vers libopenblas.so avec ce SONAME :

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

puis on l'exporte :

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

Notez qu'un chemin complet à libopenblas.so.0 doit être envoyé à LD_PRELOAD pour un chargement réussi, même si libopenblas.so.0 est sous $(pwd) .

Maintenant, nous lançons Rscript et vérifiez ce qui se passe par 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]

En comparaison avec ce que nous avons vu dans la solution 1 en trichant R avec notre propre version de libblas.so.3 , nous pouvons voir que

  • libopenblas.so.0 est chargé en premier, donc trouvé en premier par Rscript;
  • après libopenblas.so.0 est trouvé, Rscript continue à chercher et à charger libblas.so.3 . Cependant, cela n'aura aucun effet par le "premier arrivé, premier servi" règle, expliquée dans la réponse d'origine.

Bon, tout fonctionne, alors on teste notre mmperf.c programme :

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

Le résultat 9,62 est plus grand que 8,77 que nous avons vu dans la solution précédente simplement par hasard. En tant que test d'utilisation d'OpenBLAS, nous n'exécutons pas l'expérience plusieurs fois pour un résultat plus précis.

Ensuite, comme d'habitude, nous désactivons la variable d'environnement à la fin :

~/Desktop/dgemm$ unset LD_PRELOAD

Linux
  1. Comment exécuter une commande sans propriétés racine ?

  2. lors de l'utilisation de CPAN sous linux ubuntu, dois-je l'exécuter en utilisant sudo / en tant que root ou en tant qu'utilisateur par défaut

  3. Installer zsh sans accès root ?

  4. Comment puis-je créer un utilisateur avec un accès en lecture seule à tous les fichiers ? (c'est-à-dire root sans autorisations d'écriture)

  5. Comment configurer ssh sans mot de passe avec des clés RSA

Comment ajouter des référentiels à Red Hat Linux avec et sans proxy

HOWTO :Exécuter Linux sur Android sans racine

Exécutez ifconfig sans sudo

Comment installer localement .deb sans accès apt-get, dpkg ou root ?

Autoriser l'accès root mysql à l'utilisateur root linux sans mot de passe

Linux :des sysadmins productifs sans root (sécurisant la propriété intellectuelle) ?