GNU/Linux >> LINUX-Kenntnisse >  >> Linux

Führen Sie ohne Root-Zugriff R mit optimiertem BLAS aus, wenn es mit Referenz-BLAS verknüpft ist

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:

  1. Stellen Sie sich vor, dass libopenblas.so.0 ist eigentlich libblas.so.3
  2. Gesamtes R neu erstellen Paket gegen libopenblas.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

verwenden
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

  1. Rscript ist etwa 5,5 mal schneller als R . Ein Grund ist, dass R lädt 6 Standardpakete beim Start, während Rscript lädt nur einen base Paket nach Kontrolle:--default-packages=base . Aber auch ohne diese Einstellung ist es immer noch viel schneller.
  2. Am Ende werden beide Startvorgänge zu $(R RHOME)/bin/exec/R geleitet , und in meinem ursprünglichen Beitrag habe ich bereits readelf -d ausgenutzt um zu zeigen, dass diese ausführbare Datei libR.so lädt , die mit libblas.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.
  3. 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ährend R 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 wie ldd );
  • 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 und LD_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 von Rscript 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

Linux
  1. Wie führe ich einen Befehl aus, der Umleitung oder Piping mit Sudo beinhaltet?

  2. Werden beim Ausführen zu einem Runlevel vorherige Runlevel ausgeführt?

  3. Wenn ich CPAN in Linux Ubuntu verwende, sollte ich es mit sudo / als root oder als meinen Standardbenutzer ausführen

  4. Führen Sie ifconfig ohne sudo aus

  5. Wie installiere ich .deb lokal ohne apt-get, dpkg oder Root-Zugriff?

So fügen Sie Repositories zu Red Hat Linux mit und ohne Proxy hinzu

HOWTO:Führen Sie Linux auf Android ohne Root aus

An Ports kleiner als 1024 ohne Root-Zugriff binden

Gewähren Sie dem Linux-Root-Benutzer mysql-Root-Zugriff ohne Passwort

Timeout für Systemctl-Befehle bei Ausführung als Root

Konfigurieren des Systemd-Dienstes für die Ausführung mit Root-Zugriff