GNU/Linux >> Linux Esercitazione >  >> Linux

Senza accesso root, esegui R con BLAS ottimizzato quando è collegato con BLAS di riferimento

perché la mia strada non funziona

Innanzitutto, le librerie condivise su UNIX sono progettate per imitare il modo in cui funzionano le librerie di archivio (le librerie di archivio esistevano prima). In particolare ciò significa che se hai libfoo.so e libbar.so , entrambi definiscono il simbolo foo , quindi la libreria che viene caricata per prima è quella che vince:tutti i riferimenti a foo da qualsiasi punto all'interno del programma (incluso da libbar.so ) si legherà a libfoo.so s definizione di foo .

Questo imita ciò che accadrebbe se collegassi il tuo programma a libfoo.a e libbar.a , dove entrambe le librerie di archivio definivano lo stesso simbolo foo . Maggiori informazioni sul collegamento all'archivio qui.

Dovrebbe essere chiaro dall'alto che se libblas.so.3 e libopenblas.so.0 definiscono lo stesso set di simboli (cosa che fanno ), e se libblas.so.3 viene caricato prima nel processo, poi le routine da libopenblas.so.0 mai essere chiamato.

In secondo luogo, l'hai deciso correttamente da R si collega direttamente a libR.so , e da libR.so si collega direttamente a libblas.so.3 , è garantito che libopenblas.so.0 perderà la battaglia.

Tuttavia, erroneamente deciso che Rscript è meglio, ma non è:Rscript è un minuscolo binario (11K sul mio sistema; confronta con 2.4MB per libR.so ), e approssimativamente tutto ciò che fa è exec di R . Questo è banale da vedere in strace uscita:

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

Ciò significa che quando il tuo script inizia l'esecuzione, libblas.so.3 è stato caricato e libopenblas.so.0 che verrà caricato come dipendenza di mmperf.so non veramente essere utilizzato per qualsiasi cosa.

è possibile farlo funzionare

Probabilmente. Mi vengono in mente due possibili soluzioni:

  1. Fai finta che libopenblas.so.0 in realtà è libblas.so.3
  2. Ricostruisci l'intero R pacchetto contro libopenblas.so .

Per #1, devi ln -s libopenblas.so.0 libblas.so.3 , allora assicurati che la tua copia di libblas.so.3 si trova prima di quello di sistema, impostando LD_LIBRARY_PATH in modo appropriato.

Questo sembra funzionare per me:

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

Nota come ho ricevuto un errore (il mio "pretend" libblas.so.3 non definisce i simboli che ci si aspetta da esso, poiché in realtà è una copia di libc.so.6 ).

Puoi anche confermare quale versione di libblas.so.3 viene caricato in questo modo:

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

Per il n. 2, hai detto:

Non ho accesso root sulle macchine che voglio testare, quindi il collegamento effettivo a OpenBLAS è impossibile.

ma questo sembra essere un argomento fasullo:se riesci a compilare libopenblas , sicuramente puoi anche creare la tua versione di R .

Aggiornamento:

All'inizio hai detto che libblas.so.3 e libopenblas.so.0 definiscono lo stesso simbolo, cosa significa? Hanno SONAME diversi, non è sufficiente per distinguerli dal sistema?

I simboli e il SONAME non avere niente a che fare l'uno con l'altro.

Puoi vedere i simboli nell'output di readelf -Ws libblas.so.3 e readelf -Ws libopenblas.so.0 . Simboli relativi a BLAS , come cgemv_ , apparirà in entrambe le librerie.

La tua confusione su SONAME forse viene da Windows. Il DLL I programmi su Windows sono progettati in modo completamente diverso. In particolare, quando FOO.DLL importa il simbolo bar da BAR.DLL , entrambi il nome del simbolo (bar ) e il DLL da cui quel simbolo è stato importato (BAR.DLL ) sono registrati nel FOO.DLL s import table.

Questo rende facile avere R importa cgemv_ da BLAS.DLL , mentre MMPERF.DLL importa lo stesso simbolo da OPENBLAS.DLL .

Tuttavia, ciò rende difficile l'interposizione delle librerie e funziona in modo completamente diverso dal modo in cui funzionano le librerie di archivio (anche su Windows).

Le opinioni divergono su quale design sia complessivamente migliore, ma è probabile che nessuno dei due sistemi cambi mai modello.

Ci sono modi per UNIX di emulare l'associazione di simboli in stile Windows:vedi RTLD_DEEPBIND nella pagina man di dlopen. Attenzione:questi sono carichi di pericoli, potrebbero confondere gli esperti UNIX, non sono ampiamente utilizzati e potrebbero avere bug di implementazione.

Aggiornamento 2:

vuoi dire che compilo R e lo installo nella mia home directory?

Sì.

Quindi quando voglio invocarlo, devo dare esplicitamente il percorso alla mia versione di programma eseguibile, altrimenti potrebbe essere invocato quello sul sistema? Oppure posso inserire questo percorso nella prima posizione della variabile d'ambiente $PATH per imbrogliare il sistema?

In entrambi i casi funziona.


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

Soluzione 1:

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

Grazie a Employed Russian, il mio problema è finalmente risolto. L'indagine richiede competenze importanti nel debug e patch del sistema Linux , e credo che questa sia una grande risorsa che ho imparato. Qui vorrei pubblicare una soluzione, oltre a correggere diversi punti nel mio post originale.

1 Informazioni sull'invocazione di R

Nel mio post originale, ho menzionato che ci sono due modi per avviare R, tramite R o Rscript . Tuttavia, ho erroneamente esagerato la loro differenza. Esaminiamo ora il loro processo di avvio, tramite un'importante funzione di debug di Linux strace (vedi man strace ). In realtà accadono molte cose interessanti dopo che abbiamo digitato un comando nella shell e possiamo usare

strace -e trace=process [command]

per tracciare tutte le chiamate di sistema che coinvolgono la gestione dei processi. Di conseguenza possiamo osservare le fasi di fork, attesa ed esecuzione di un processo. Anche se non indicato nella pagina di manuale, @Employed Russian mostra che è possibile specificare solo una sottoclasse di process , ad esempio, execve per le fasi di esecuzione.

Per R abbiamo

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

mentre per Rscript abbiamo

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

Abbiamo utilizzato anche time per misurare il tempo di avvio. Nota che

  1. Rscript è circa 5,5 volte più veloce di R . Uno dei motivi è che R caricherà 6 pacchetti predefiniti all'avvio, mentre Rscript carica solo un base pacchetto per controllo:--default-packages=base . Ma è ancora molto più veloce anche senza questa impostazione.
  2. Alla fine entrambi i processi di avvio vengono indirizzati a $(R RHOME)/bin/exec/R , e nel mio post originale, ho già sfruttato readelf -d per mostrare che questo eseguibile caricherà libR.so , che sono collegati con libblas.so.3 . Secondo la spiegazione di @Employed Russian, la libreria BLAS caricata per prima vincerà, quindi non è possibile che il mio metodo originale funzioni.
  3. Per eseguire correttamente strace , abbiamo utilizzato incredibile file /dev/null come file di input e file di output quando necessario. Ad esempio, Rscript richiede un file di input, mentre R esige entrambi. Forniamo il dispositivo nullo a entrambi per far funzionare il comando senza intoppi e l'output pulito. Il dispositivo null è un file fisicamente esistente, ma funziona in modo sorprendente. Quando si legge da esso, non contiene nulla; mentre ci scrive, scarta tutto.

2. Imbroglia R

Ora dal libblas.so verrà caricato comunque, l'unica cosa che possiamo fare è fornire la nostra versione di questa libreria. Come ho detto nel post originale, se abbiamo accesso root, questo è davvero facile, usando update-alternatives --config libblas.so.3 , in modo che il sistema Linux ci aiuti a completare questo passaggio. Ma @Employed Russian offre un modo fantastico per imbrogliare il sistema senza accesso root:controlliamo come R trova la libreria BLAS all'avvio e assicuriamoci di alimentare la nostra versione prima che venga trovata l'impostazione predefinita del sistema! Per monitorare come le librerie condivise vengono trovate e caricate, usa la variabile di ambiente LD_DEBUG .

Ci sono un certo numero di variabili d'ambiente Linux con il prefisso LD_ , come documentato in man ld.so . Queste variabili possono essere assegnate prima di un eseguibile, in modo da poter modificare la funzionalità di esecuzione di un programma. Alcune variabili utili includono:

  • LD_LIBRARY_PATH per impostare il percorso di ricerca della libreria in fase di esecuzione;
  • LD_DEBUG per la ricerca delle tracce e il caricamento delle librerie condivise;
  • LD_TRACE_LOADED_OBJECTS per visualizzare tutta la libreria caricata da un programma (si comporta in modo simile a ldd );
  • LD_PRELOAD per forzare l'inserimento di una libreria in un programma all'inizio, prima che tutte le altre librerie vengano cercate;
  • LD_PROFILE e LD_PROFILE_OUTPUT per profilarne uno libreria condivisa specificata. Utente R che ha letto la sezione 3.4.1.1 sprof di scrivere le estensioni R dovrebbe ricordare che questo è usato per profilare il codice compilato dall'interno di R.

L'uso di LD_DEBUG può essere visto da:

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

Qui siamo particolarmente interessati all'utilizzo di LD_DEBUG=libs . Ad esempio,

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

mostra vari tentativi che il programma R ha tentato di individuare e caricare libblas.so.3 . Quindi, se potessimo fornire la nostra versione di libblas.so.3 , e assicurati che R lo trovi per primo, quindi il problema è risolto.

Per prima cosa creiamo un link simbolico libblas.so.3 nel nostro percorso di lavoro verso la libreria OpenBLAS libopenblas.so , quindi espandi LD_LIBRARY_PATH predefinito con il nostro percorso lavorativo (ed export it):

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

Ora ricontrolliamo il processo di caricamento della libreria:

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

Grande! Abbiamo imbrogliato con successo R.

3. Sperimenta con OpenBLAS

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

Ora tutto funziona come previsto!

4. Annulla LD_LIBRARY_PATH (per sicurezza)

È buona norma annullare l'impostazione di LD_LIBRARY_PATH dopo l'uso.

~/Desktop/dgemm$ unset LD_LIBRARY_PATH

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

Soluzione 2:

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

Qui offriamo un'altra soluzione, sfruttando la variabile d'ambiente LD_PRELOAD menzionato nella nostra soluzione 1 . L'uso di LD_PRELOAD è più "brutale", poiché forza il caricamento di una data libreria nel programma prima di qualsiasi altro programma, anche prima della libreria C libc.so ! Viene spesso utilizzato per patch urgenti nello sviluppo di Linux.

Come mostrato nella parte 2 del post originale , la libreria BLAS condivisa libopenblas.so ha SONAME libopenblas.so.0 . Un SONAME è un nome interno che il caricatore dinamico della libreria cercherà in fase di esecuzione, quindi dobbiamo creare un collegamento simbolico a libopenblas.so con questo SONAME :

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

quindi lo esportiamo:

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

Tieni presente che un percorso completo a libopenblas.so.0 deve essere inviato a LD_PRELOAD per un caricamento riuscito, anche se libopenblas.so.0 è sotto $(pwd) .

Ora lanciamo Rscript e controlla cosa succede per 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]

Confronto con ciò che abbiamo visto nella soluzione 1 ingannando R con la nostra versione di libblas.so.3 , possiamo vederlo

  • libopenblas.so.0 viene caricato per primo, quindi trovato per primo da Rscript;
  • dopo libopenblas.so.0 viene trovato, Rscript continua a cercare e caricare libblas.so.3 . Tuttavia, questo non avrà alcun effetto dal "primo arrivato, primo servito" regola, spiegata nella risposta originale.

Bene, tutto funziona, quindi testiamo il nostro mmperf.c programma:

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

Il risultato 9.62 è maggiore di 8.77 che abbiamo visto nella soluzione precedente solo per caso. Come test per l'utilizzo di OpenBLAS, non eseguiamo l'esperimento molte volte per ottenere risultati più precisi.

Quindi, come al solito, annulliamo l'impostazione della variabile d'ambiente alla fine:

~/Desktop/dgemm$ unset LD_PRELOAD

Linux
  1. Come eseguire un comando senza proprietà di root?

  2. quando uso CPAN in Linux Ubuntu dovrei eseguirlo usando sudo / come root o come utente predefinito

  3. Installare zsh senza accesso root?

  4. Come posso creare un utente con accesso in sola lettura a tutti i file? (ovvero root senza permessi di scrittura)

  5. Come configurare ssh senza password con chiavi RSA

Come aggiungere repository a Red Hat Linux con e senza proxy

HOWTO:eseguire Linux su Android senza root

Esegui ifconfig senza sudo

Come installare localmente .deb senza apt-get, dpkg o accesso root?

Consenti all'utente root di Linux l'accesso root mysql senza password

Linux:amministratori di sistema produttivi senza root (protezione della proprietà intellettuale)?