warum mein Weg nicht funktioniert
Erstens sind gemeinsam genutzte Bibliotheken unter UNIX so konzipiert, dass sie die Funktionsweise von Archivbibliotheken nachahmen (Archivbibliotheken waren zuerst da). Das heißt insbesondere, wenn Sie libfoo.so
haben und libbar.so
, die beide das Symbol foo
definieren , dann gewinnt die Bibliothek, die zuerst geladen wird:alle Verweise auf foo
von überall innerhalb des Programms (einschließlich von libbar.so
) wird an libfoo.so
gebunden s-Definition von foo
.
Dies ahmt nach, was passieren würde, wenn Sie Ihr Programm mit libfoo.a
verknüpfen würden und libbar.a
, wobei beide Archivbibliotheken dasselbe Symbol foo
definiert haben . Weitere Informationen zum Verlinken von Archiven finden Sie hier.
Oben sollte klar sein, dass wenn libblas.so.3
und libopenblas.so.0
denselben Satz von Symbolen definieren (was sie tun ), und wenn libblas.so.3
wird zuerst in den Prozess geladen, dann Routinen aus libopenblas.so.0
wird nie angerufen werden.
Zweitens haben Sie das seit R
richtig entschieden direkt gegen libR.so
verlinkt , und seit libR.so
direkt gegen libblas.so.3
verlinkt , ist garantiert, dass libopenblas.so.0
wird den Kampf verlieren.
Sie haben jedoch fälschlicherweise entschieden, dass Rscript
ist besser, ist es aber nicht:Rscript
ist winzig binär (11 KB auf meinem System; im Vergleich zu 2,4 MB für libR.so
), und ungefähr alles, was es tut, ist exec
von R
. Dies ist trivial in strace
zu sehen Ausgabe:
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 +++
Das bedeutet, dass zu dem Zeitpunkt, an dem Ihr Skript ausgeführt wird, libblas.so.3
geladen wurde und libopenblas.so.0
die als Abhängigkeit von mmperf.so
geladen wird wird eigentlich nicht für alles verwendet werden.
ist es überhaupt möglich, es zum Laufen zu bringen
Wahrscheinlich. Mir fallen zwei mögliche Lösungen ein:
- Stellen Sie sich vor, dass
libopenblas.so.0
ist eigentlichlibblas.so.3
- Gesamtes
R
neu erstellen Paket gegenlibopenblas.so
.
Für #1 müssen Sie ln -s libopenblas.so.0 libblas.so.3
eingeben , dann Stellen Sie sicher, dass Ihre Kopie von libblas.so.3
wird vor dem System gefunden, indem LD_LIBRARY_PATH
gesetzt wird angemessen.
Das scheint bei mir zu funktionieren:
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
Beachten Sie, wie ich einen Fehler bekommen habe (mein "vorgetäuschter" libblas.so.3
definiert keine Symbole, die von ihm erwartet werden, da es wirklich eine Kopie von libc.so.6
ist ).
Sie können auch die Version von libblas.so.3
bestätigen wird so geladen:
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
Für #2 sagten Sie:
Ich habe keinen Root-Zugriff auf Maschinen, die ich testen möchte, daher ist eine tatsächliche Verknüpfung mit OpenBLAS unmöglich.
aber das scheint ein falsches Argument zu sein:wenn Sie libopenblas
bauen können , sicherlich können Sie auch Ihre eigene Version von R
bauen .
Aktualisierung:
Sie haben am Anfang erwähnt, dass libblas.so.3 und libopenblas.so.0 dasselbe Symbol definieren, was bedeutet das? Sie haben unterschiedliche SONAME, reicht das nicht aus, um sie vom System zu unterscheiden?
Die Symbole und der SONAME
habe nichts miteinander zu tun.
Sie können Symbole in der Ausgabe von readelf -Ws libblas.so.3
sehen und readelf -Ws libopenblas.so.0
. Symbole im Zusammenhang mit BLAS
, wie zum Beispiel cgemv_
, wird in beiden Bibliotheken angezeigt.
Ihre Verwirrung über SONAME
möglicherweise kommt von Windows. Der DLL
s unter Windows sind völlig anders aufgebaut. Insbesondere bei FOO.DLL
importiert Symbol bar
von BAR.DLL
, beides der Name des Symbols (bar
) und der DLL
aus der dieses Symbol importiert wurde (BAR.DLL
) werden im FOO.DLL
erfasst s Importtabelle.
Das macht es einfach, R
zu haben cgemv_
importieren von BLAS.DLL
, während MMPERF.DLL
importiert dasselbe Symbol aus OPENBLAS.DLL
.
Das erschwert jedoch das Einfügen von Bibliotheken und funktioniert völlig anders als Archivbibliotheken (selbst unter Windows).
Die Meinungen darüber, welches Design insgesamt besser ist, gehen auseinander, aber wahrscheinlich wird kein System jemals sein Modell ändern.
Es gibt Möglichkeiten für UNIX, die Symbolbindung im Windows-Stil zu emulieren:siehe RTLD_DEEPBIND
in der dlopen-Manpage. Achtung:Diese sind voller Gefahren, können UNIX-Experten verwirren, sind nicht weit verbreitet und enthalten wahrscheinlich Implementierungsfehler.
Aktualisierung 2:
Sie meinen, ich kompiliere R und installiere es in meinem Home-Verzeichnis?
Ja.
Wenn ich es dann aufrufen möchte, sollte ich explizit den Pfad zu meiner Version des ausführbaren Programms angeben, sonst könnte stattdessen die auf dem System aufgerufen werden? Oder kann ich diesen Pfad an die erste Position der Umgebungsvariable $PATH setzen, um das System zu betrügen?
So oder so funktioniert.
********************
Lösung 1:
********************
Dank Employed Russian ist mein Problem endlich gelöst. Die Untersuchung erfordert wichtige Kenntnisse im Debuggen und Patchen von Linux-Systemen , und ich glaube, das ist eine große Bereicherung, die ich gelernt habe. Hier würde ich eine Lösung posten, sowie mehrere Punkte in meinem ursprünglichen Post korrigieren.
1 Über den Aufruf von R
In meinem ursprünglichen Beitrag habe ich erwähnt, dass es zwei Möglichkeiten gibt, R zu starten, entweder über R
oder Rscript
. Ich habe jedoch fälschlicherweise ihren Unterschied übertrieben. Untersuchen wir nun ihren Startprozess über eine wichtige Linux-Debugging-Funktion strace
(siehe man strace
). Es passieren tatsächlich viele interessante Dinge, nachdem wir einen Befehl in die Shell eingegeben haben, und wir können
strace -e trace=process [command]
um alle Systemaufrufe zu verfolgen, die das Prozessmanagement betreffen. Als Ergebnis können wir die Fork-, Warte- und Ausführungsschritte eines Prozesses beobachten. Obwohl in der Handbuchseite nicht angegeben, zeigt @Employed Russian, dass es möglich ist, nur eine Unterklasse von process
anzugeben , zum Beispiel execve
für die Ausführungsschritte.
Für R
wir haben
~/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
während für Rscript
wir haben
~/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
Wir haben auch time
verwendet um die Startzeit zu messen. Beachten Sie das
Rscript
ist etwa 5,5 mal schneller alsR
. Ein Grund ist, dassR
lädt 6 Standardpakete beim Start, währendRscript
lädt nur einenbase
Paket nach Kontrolle:--default-packages=base
. Aber auch ohne diese Einstellung ist es immer noch viel schneller.- Am Ende werden beide Startvorgänge zu
$(R RHOME)/bin/exec/R
geleitet , und in meinem ursprünglichen Beitrag habe ich bereitsreadelf -d
ausgenutzt um zu zeigen, dass diese ausführbare DateilibR.so
lädt , die mitlibblas.so.3
verknüpft sind . Gemäß der Erklärung von @Employed Russian gewinnt die zuerst geladene BLAS-Bibliothek, sodass meine ursprüngliche Methode auf keinen Fall funktionieren wird. - Um
strace
erfolgreich auszuführen , haben wir das erstaunliche verwendet Datei/dev/null
als Eingabedatei und bei Bedarf als Ausgabedatei. Beispiel:Rscript
verlangt eine Eingabedatei, währendR
verlangt beides. Wir füttern beide mit dem Null-Gerät, damit der Befehl reibungslos abläuft und die Ausgabe sauber ist. Das Null-Gerät ist eine physisch vorhandene Datei, funktioniert aber erstaunlich gut. Wenn man daraus liest, enthält es nichts; beim Schreiben wird alles verworfen.
2. Cheat R
Jetzt seit libblas.so
trotzdem geladen werden, das Einzige, was wir tun können, ist, unsere eigene Version dieser Bibliothek bereitzustellen. Wie ich im ursprünglichen Beitrag gesagt habe, ist dies wirklich einfach, wenn wir Root-Zugriff haben, indem wir update-alternatives --config libblas.so.3
verwenden , damit uns das System Linux dabei hilft, diesen Wechsel abzuschließen. Aber @Employed Russian bietet eine großartige Möglichkeit, das System ohne Root-Zugriff zu betrügen:überprüfen wir, wie R die BLAS-Bibliothek beim Start findet, und stellen Sie sicher, dass wir unsere Version füttern, bevor der Systemstandard gefunden wird! Verwenden Sie die Umgebungsvariable LD_DEBUG
, um zu überwachen, wie gemeinsam genutzte Bibliotheken gefunden und geladen werden .
Es gibt eine Reihe von Linux-Umgebungsvariablen mit dem Präfix LD_
, wie in man ld.so
dokumentiert . Diese Variablen können vor einer ausführbaren Datei zugewiesen werden, sodass wir die Ausführungsfunktion eines Programms ändern können. Einige nützliche Variablen sind:
LD_LIBRARY_PATH
zum Festlegen des Suchpfads der Laufzeitbibliothek;LD_DEBUG
zum Nachschlagen und Laden von gemeinsam genutzten Bibliotheken;LD_TRACE_LOADED_OBJECTS
zum Anzeigen aller geladenen Bibliotheken durch ein Programm (verhält sich ähnlich wieldd
);LD_PRELOAD
um das Einfügen einer Bibliothek in ein Programm ganz am Anfang zu erzwingen, bevor nach allen anderen Bibliotheken gesucht wird;LD_PROFILE
undLD_PROFILE_OUTPUT
für die Profilerstellung eines angegebene gemeinsam genutzte Bibliothek. R-Benutzer, die Abschnitt 3.4.1.1 sprof gelesen haben des Schreibens von R-Erweiterungen sollte daran erinnern, dass dies zum Profilieren von kompiliertem Code aus R heraus verwendet wird.
Die Verwendung von LD_DEBUG
kann gesehen werden von:
~/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.
Hier interessiert uns besonders die Verwendung von LD_DEBUG=libs
. Zum Beispiel
~/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]
zeigt verschiedene Versuche, die das R-Programm versuchte, libblas.so.3
zu lokalisieren und zu laden . Wenn wir also unsere eigene Version von libblas.so.3
bereitstellen könnten , und stellen Sie sicher, dass R es zuerst findet, dann ist das Problem gelöst.
Lassen Sie uns zuerst einen symbolischen Link libblas.so.3
erstellen in unserem Arbeitspfad zur OpenBLAS-Bibliothek libopenblas.so
, erweitern Sie dann den Standardwert LD_LIBRARY_PATH
mit unserem Arbeitspfad (und export 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
Lassen Sie uns nun noch einmal den Ladevorgang der Bibliothek überprüfen:
~/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]
Groß! Wir haben R. erfolgreich betrogen.
3. Experimentieren Sie mit OpenBLAS
~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R
GFLOPs = 8.77
Jetzt funktioniert alles wie erwartet!
4. Deaktivieren Sie LD_LIBRARY_PATH
(sicherheitshalber)
Es empfiehlt sich, LD_LIBRARY_PATH
zu deaktivieren nach Gebrauch.
~/Desktop/dgemm$ unset LD_LIBRARY_PATH
********************
Lösung 2:
********************
Hier bieten wir eine andere Lösung an, indem wir die Umgebungsvariable LD_PRELOAD
ausnutzen in unserer Lösung 1 erwähnt . Die Verwendung von LD_PRELOAD
ist "brutaler", da es das Laden einer bestimmten Bibliothek in das Programm vor jedem anderen Programm erzwingt, sogar vor der C-Bibliothek libc.so
! Dies wird häufig für dringendes Patchen in der Linux-Entwicklung verwendet.
Wie in Teil 2 des ursprünglichen Beitrags gezeigt , die gemeinsame BLAS-Bibliothek libopenblas.so
hat SONAME libopenblas.so.0
. Ein SONAME ist ein interner Name, den der dynamische Bibliothekslader zur Laufzeit suchen würde, also müssen wir einen symbolischen Link zu libopenblas.so
erstellen mit diesem SONAME :
~/Desktop/dgemm$ ln -sf libopenblas.so libopenblas.so.0
dann exportieren wir es:
~/Desktop/dgemm$ export LD_PRELOAD=$(pwd)/libopenblas.so.0
Beachten Sie, dass ein vollständiger Pfad bis libopenblas.so.0
muss in LD_PRELOAD
eingespeist werden für einen erfolgreichen Ladevorgang, auch wenn libopenblas.so.0
ist unter $(pwd)
.
Jetzt starten wir Rscript
und prüfen Sie, was mit LD_DEBUG
passiert :
~/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]
Im Vergleich zu dem, was wir in Lösung 1 gesehen haben indem wir R mit unserer eigenen Version von libblas.so.3
betrügen , das können wir sehen
libopenblas.so.0
wird zuerst geladen, also zuerst vonRscript
gefunden;- nach
libopenblas.so.0
gefunden wird,Rscript
fährt fort,libblas.so.3
zu suchen und zu laden . Dies wird jedoch durch das "Wer zuerst kommt, mahlt zuerst" nicht beeinflusst Regel, erklärt in der ursprünglichen Antwort.
Gut, alles funktioniert, also testen wir unsere mmperf.c
Programm:
~/Desktop/dgemm$ Rscript --default-packages=base --vanilla mmperf.R
GFLOPs = 9.62
Das Ergebnis 9,62 ist größer als 8,77, das wir in der früheren Lösung nur zufällig gesehen haben. Als Test für die Verwendung von OpenBLAS führen wir das Experiment nicht oft durch, um ein genaueres Ergebnis zu erzielen.
Dann setzen wir wie üblich die Umgebungsvariable am Ende zurück:
~/Desktop/dgemm$ unset LD_PRELOAD