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 :
- Imaginez que
libopenblas.so.0
est en faitlibblas.so.3
- Reconstruire l'intégralité du
R
paquet contrelibopenblas.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
Rscript
est environ 5,5 fois plus rapide queR
. L'une des raisons est queR
chargera 6 packages par défaut au démarrage, tandis queRscript
ne charge qu'un seulbase
package par contrôle :--default-packages=base
. Mais c'est toujours beaucoup plus rapide même sans ce paramètre.- 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 chargerlibR.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. - 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 queR
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 commeldd
);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
etLD_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 parRscript
;- après
libopenblas.so.0
est trouvé,Rscript
continue à chercher et à chargerlibblas.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