python vs bc nel valutare 6 ^ 6 ^ 6


29

Sto valutando l'espressione 6^6^6usando pythone bcseparatamente.

Il contenuto del file Python è print 6**6**6. Quando eseguo time python test.py, ottengo l'output come

real        0m0.067s
user        0m0.050s
sys         0m0.011s

E poi, ho eseguito il comando time echo 6^6^6 | bcche mi ha dato il seguente output

real        0m0.205s
user        0m0.197s
sys         0m0.005s

Da questi risultati è chiaro che il tempo di sistema impiegato da python e bc era rispettivamente di 11ms e 5ms. Il comando bc ha superato python a livello di tempo sys ma quando si tratta di utente e tempo reale python era quasi 4 volte più veloce di bc . Cosa potrebbe essere andato lì. Non ho dato alcuna priorità ai processi in quanto tali. Sto cercando di capire questa situazione.


Quindi vuoi dire che il componente sys dà solo il tempo necessario ai carichi e il runtime verrà dato nel componente utente dell'output?
Ganessh,

Non ne sono davvero sicuro, ecco perché ho pubblicato un commento. È solo una supposizione.
terdon

7
echo | bccomporta il lancio di una subshell a causa della pipe - ecco da dove probabilmente è arrivato un po 'di tempo extra dell'utente. Per rendere questo un test equo, lo script python dovrebbe leggere da stdin in modo da poterlo fare time echo 6**6**6 | whatever.py.
Riccioli d'oro,

1
Preferirei inserire la riga di comando in una sceneggiatura e temporizzare l'esecuzione. Oppure usa echo 6^6^6 | time bc.
Daniel Kullmann,

1
Nota a margine: in Python l' 6**6**6espressione viene effettivamente calcolata in fase di compilazione . Tuttavia, poiché stai avviando il file direttamente invece di importarlo da un modulo, questo non dovrebbe importare. Per vedere la differenza inserita 10**12345678in un a.pyfile e provare a importarlo dall'interprete interattivo. Quindi chiudere l'interprete, riavviarlo e importarlo di anuovo. La prima volta dovrebbe impiegare un notevole lasso di tempo (perché python sta compilando il modulo), mentre la seconda volta carica il .pyc, che dovrebbe essere istantaneo,
Bakuriu,

Risposte:


25

Python importa un gran numero di file all'avvio:

% python -c 'import sys; print len(sys.modules)'
39

Ognuno di questi richiede un numero ancora maggiore di tentativi di apertura di un file Python, poiché esistono molti modi per definire un modulo:

% python -vv -c 'pass'
# installing zipimport hook
import zipimport # builtin
# installed zipimport hook
# trying site.so
# trying sitemodule.so
# trying site.py
# trying site.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/sitemodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.py
import site # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site.pyc
# trying os.so
# trying osmodule.so
# trying os.py
# trying os.pyc
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/osmodule.so
# trying /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
# /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc matches /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.py
import os # precompiled from /System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/os.pyc
    ...

Ogni "tentativo", ad eccezione di quelli integrati, richiede un livello di sistema / chiamate di sistema e ogni "importazione" sembra innescare circa 8 messaggi "di prova". (Esistono modi per ridurlo utilizzando zipimport e ogni percorso in PYTHONPATH potrebbe richiedere un'altra chiamata.)

Ciò significa che ci sono quasi 200 chiamate al sistema stat prima che Python si avvii sulla mia macchina e "time" lo assegni a "sys" piuttosto che a "user", perché il programma utente è in attesa sul sistema di fare qualcosa.

In confronto, e come ha detto Terdon, "bc" non ha un costo di avvio così elevato. Osservando l'output di dtruss (ho un Mac; "strace" per un sistema operativo basato su Linux), vedo che bc non effettua chiamate open () o stat () proprie, tranne per il caricamento di alcuni condivisi le librerie sono l'inizio, cosa che ovviamente fa anche Python. Inoltre, Python ha più file da leggere, prima che sia pronto per elaborare qualsiasi cosa.

L'attesa per il disco è lenta.

Puoi avere un'idea del costo di avvio di Python facendo:

time python -c pass

Sulla mia macchina sono 0,032 secondi, mentre 'stampa 6 ** 6 ** 6' è 0,072 secondi, quindi il costo di avvio è 1/2 del tempo complessivo e il calcolo + conversione in decimale è l'altra metà. Mentre:

time echo 1 | bc

impiega 0,005s e "6 ^ 6 ^ 6" richiede 0,184s, quindi l'esponenziazione di bc è oltre 4x più lenta di quella di Python anche se è 7x più veloce per iniziare.


4
Hai un po 'sepolto il piombo lì. Potresti voler spostare il bit di fine in alto.
Riking

Solo per interesse sulla mia macchina: time python -c 'pass' 0m0.025s, time python -c 'print 6 6 6' 0m0.087s ma time python -c 'x = 6 6 6' 0m0.028s Quindi la maggior parte di il tempo sta producendo un numero elevato.
Steve Barnes,

Sì, la conversione in base 10 richiede un tempo quadratico nel numero di cifre. Come caso estremo, prova a stampare uno dei numeri primi Mersenne più grandi. È molto veloce da calcolare, ma richiede molto tempo per stampare nella base 10.
Andrew Dalke,

11

Ho trovato una bella risposta su SO spiegando i diversi campi:

  • Reale è l'ora dell'orologio da parete - tempo dall'inizio alla fine della chiamata. Questo è tutto il tempo trascorso, compresi gli intervalli di tempo utilizzati da altri processi e il tempo trascorso dal processo bloccato (ad esempio se è in attesa del completamento dell'I / O).

  • L'utente è la quantità di tempo della CPU trascorso nel codice in modalità utente (al di fuori del kernel) all'interno del processo. Questo è solo il tempo CPU effettivo utilizzato nell'esecuzione del processo. Altri processi e tempi di blocco bloccati non contano ai fini di questa cifra.

  • Sys è la quantità di tempo CPU trascorso nel kernel all'interno del processo. Ciò significa eseguire il tempo della CPU impiegato nelle chiamate di sistema all'interno del kernel, al contrario del codice della libreria, che è ancora in esecuzione nello spazio utente. Come "utente", questo è solo il tempo CPU utilizzato dal processo. Vedi sotto per una breve descrizione della modalità kernel (anche conosciuta come modalità 'supervisore') e il meccanismo di chiamata del sistema.

Quindi, nel tuo esempio specifico, la versione di Python è più veloce in termini di tempo effettivo necessario per il completamento. Tuttavia, l'approccio python trascorre più tempo nello spazio del kernel, effettuando chiamate alle funzioni del kernel. Il bccomando non trascorre sostanzialmente tempo nello spazio del kernel e tutto il tempo è trascorso nello spazio utente, presumibilmente eseguendo bccodice interno .

Questo non fa alcuna differenza per te, l'unica informazione che ti interessa davvero è realquale sia il tempo reale trascorso tra l'avvio del comando e il suo output.

Dovresti anche essere consapevole che queste minuscole differenze non sono stabili, dipenderanno anche dal carico del tuo sistema e cambieranno ogni volta che esegui il comando:

$ for i in {1..10}; do ( time python test.py > /dev/null ) 2>&1; done | grep user
user    0m0.056s
user    0m0.052s
user    0m0.052s
user    0m0.052s
user    0m0.060s
user    0m0.052s
user    0m0.052s
user    0m0.056s
user    0m0.048s
user    0m0.056s

$ for i in {1..10}; do ( time echo 6^6^6 | bc > /dev/null ) 2>&1; done | grep user
user    0m0.188s
user    0m0.188s
user    0m0.176s
user    0m0.176s
user    0m0.172s
user    0m0.176s
user    0m0.180s
user    0m0.172s
user    0m0.172s
user    0m0.172s

10

Lo spiegherò da un'altra prospettiva.

Per essere onesti, bcha un vantaggio poiché non deve leggere nulla dal disco e ha solo bisogno del suo BLOB / binari mentre Python deve importare una serie di moduli + leggere un file. Quindi il tuo test potrebbe essere parziale bc. Per testarlo effettivamente dovresti usare bc -q filedove filecontiene:

6^6^6
quit

Cambiando solo quello ha modificato il tempo di utilizzo echo:

bc  0.33s user 0.00s system 80% cpu 0.414 total

Per utilizzare il file:

bc -q some  0.33s user 0.00s system 86% cpu 0.385 total

(dovrai usare il metodo di terdon per notare differenze maggiori, ma almeno sappiamo che lo sono)

Ora, dal punto di vista di Python, Python deve leggere dal disco, compilare ed eseguire ogni volta il file, oltre a caricare i moduli come punti Andrew , il che rende i tempi di esecuzione più lenti. Se compili il codice byte dello script Python noterai che per eseguire il codice è necessario un tempo totale inferiore del 50%:

python some.py > /dev/null  0.25s user 0.01s system 63% cpu 0.413 total

compilato:

./some.pyc  0.22s user 0.00s system 77% cpu 0.282 total

Come puoi vedere, ci sono diversi fattori che possono influenzare l'esecuzione del tempo tra diversi strumenti.


3

Ho avuto il vantaggio di leggere le altre risposte. Per cominciare le persone come me dovrebbero sapere il motivo per cui abbiamo a che fare con un numero così grande qui è che entrambi Pythone bcfacciamo l' espansione esponenziale associativa giusta , il che significa che non 6^36stiamo valutando, ma piuttosto 6^46656che è considerevolmente più grande. 1

Usando le variazioni sui seguenti comandi, possiamo estrarre una media per un elemento specifico dell'output sia della timeparola riservata che del comando:

for i in {1..1000}; do (time echo 6^6^6 | bc > /dev/null) 2>&1; done | grep 'rea' | sed -e s/.*m// | awk '{sum += $1} END {print sum / NR}'

for i in {1..1000}; do (/usr/bin/time -v sh -c 'echo 6^6^6 | bc > /dev/null') 2>&1; done | grep 'Use' | sed -e s/.*:// | awk '{sum += $1} END {print sum / NR}'

È possibile percorrere un'altra strada e rimuovere completamente il file dal confronto. Inoltre, possiamo confrontare i tempi di bc con qualcosa di simile al dccomando, dato che storicamente il primo è un "processore front-end" con il secondo. I seguenti comandi sono stati cronometrati:

echo 6^6^6 | bc
echo 6 6 6 ^ ^ p | dc
echo print 6**6**6 | python2.7

Nota che il dccomando è associativo di sinistra per l'espiazione. 2

Abbiamo alcuni risultati con time(bash) per 1000 iterazioni (in secondi):

0.229678 real bc
0.228348 user bc
0.000569 sys bc
0.23306  real dc
0.231786 user dc
0.000395 sys dc
0.07 real python
0.065907 user python
0.003141 sys python

bce dcoffrire prestazioni comparabili in questo contesto.

3 risultati meno accurati dal comando /usr/bin/timeGNU time(la precisione della scala non è valida qui ma i risultati sono simili):

0.2224 user bc
0 sys bc
0.23 Elapsed bc
0.22998 user dc
0 sys dc
0.23 Elapsed dc
0.06008 user python
0 sys python
0.07 Elapsed python

Un vantaggio /usr/bin/timeè che offre l' -vopzione che fornisce molte più informazioni che potrebbero essere utili alla fine.

È anche possibile valutarlo internamente per così dire con il timeitmodulo Python:

python2.7 -m timeit -n 1000 -r 1 'print 6**6**6' | grep 'loops'
1000 loops, best of 1: 55.4 msec per loop

È un po 'più veloce di quello che abbiamo visto prima. Proviamo l'interprete stesso:

>>> import timeit
>>> import sys
>>> import os
>>> T = timeit.Timer("print 6**6**6")
>>> n = int(1000)
>>> f = open(os.devnull, 'w')
>>> sys.stdout = f
>>> t = t.timeit(n)
>>> sys.stdout = sys.__stdout__
>>> print t/n
0.0553743481636

Questo è il più veloce che abbia mai visto.


Se valutiamo una minore esponenziazione come 6^6, allora il comando time produce risultati sorprendenti - usando gli stessi forcomandi loop che abbiamo usato ora abbiamo:

0.001001 bc real
0.000304 user
0.000554 sys
0.014    python real i.e. 10x more than bc??
0.010432 user
0.002606 sys

Quindi con un numero intero più piccolo bcè improvvisamente molto più veloce ?? Dal riavvio del sistema alla seconda esecuzione non fa differenza. Allo stesso tempo, se usiamo timeitper Python, otteniamo:

python2.7 -m timeit -n 100000 -r 1 'print 6**6' | grep loops  
100000 loops, best of 1: 0.468 usec per loop

Questo è microsecondi , non millisecondi, quindi non corrisponde ai risultati molto più lenti usando il forloop. Forse sono necessari altri strumenti per testarlo ulteriormente e, come altri hanno spiegato, qui c'è molto di più di quello che si vede. Sembra che Python sia stato più veloce nello scenario della domanda, ma non è chiaro se si possano trarre conclusioni oltre quella ...


1. Inutile dire che va oltre lo scopo di qualcosa come l'espansione aritmetica dell'eco, per esempio echo $((6**6**6))- bashsembra essere anche associativo per quello, cioè 6^6^6 = 6^(6^6).

2. Confrontare con questo: 6 6 ^ 6 ^ p.

3. È possibile che il comando GNU time fornisca ulteriori informazioni quando eseguito su BSD UNIX (documento informativo sul tempo GNU): la maggior parte delle informazioni mostrate da 'time' derivano dalla chiamata di sistema 'wait3'. I numeri sono buoni solo come quelli restituiti da 'wait3'. Molti sistemi non misurano tutte le risorse su cui "tempo" può riferire; tali risorse sono riportate come zero. I sistemi che misurano la maggior parte o tutte le risorse si basano su 4.2 o 4.3BSD. Le versioni successive di BSD utilizzano un codice di gestione della memoria diverso che misura meno risorse. - Sui sistemi che non dispongono di una chiamata "wait3" che restituisce informazioni sullo stato, viene invece utilizzata la chiamata di sistema "times". Fornisce molte meno informazioni di "wait3", quindi in quei sistemi il "tempo" riporta la maggior parte delle risorse come zero.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.