Perché coreutils è più lento di Python?


20

Ho scritto il seguente script per testare la velocità della funzionalità di ordinamento di Python:

from sys import stdin, stdout
lines = list(stdin)
lines.sort()
stdout.writelines(lines)

Ho quindi confrontato questo con il sortcomando coreutils su un file contenente 10 milioni di righe:

$ time python sort.py <numbers.txt >s1.txt
real    0m16.707s
user    0m16.288s
sys     0m0.420s

$ time sort <numbers.txt >s2.txt 
real    0m45.141s
user    2m28.304s
sys     0m0.380s

Il comando integrato utilizzava tutte e quattro le CPU (Python ne utilizzava solo una) ma impiegava circa 3 volte più tempo per l'esecuzione! Cosa dà?

Sto usando Ubuntu 12.04.5 (32 bit), Python 2.7.3 e sort8.13


@goldilocks Sì, guarda l'utente in tempo reale.
Augurar

Huh - hai ragione. Apparentemente è stato parallelizzato in coreutils 8.6.
riccioli d'oro

Puoi usare --buffer-sizeper specificare che sortusa tutta la memoria fisica disponibile e vedere se questo aiuta?
iruvar,

@ 1_CR Provato con buffer da 1 GB, nessun cambiamento significativo nei tempi. Ne ha usato solo circa 6 GB, quindi aumentare ulteriormente la dimensione del buffer non sarebbe di aiuto.
agosto

Risposte:


22

Il commento di Izkata ha rivelato la risposta: confronti specifici del luogo. Il sortcomando utilizza le impostazioni locali indicate dall'ambiente, mentre Python utilizza per impostazione predefinita un confronto dell'ordine dei byte. Il confronto delle stringhe UTF-8 è più difficile rispetto al confronto delle stringhe di byte.

$ time (LC_ALL=C sort <numbers.txt >s2.txt)
real    0m5.485s
user    0m14.028s
sys     0m0.404s

Che ne dici di quello.


E come si confrontano per le stringhe UTF-8?
Gilles 'SO- smetti di essere malvagio' il

@Gilles Modifica dello script Python da utilizzare locale.strxfrmper l'ordinamento, lo script ha richiesto ~ 32 secondi, ancora più veloce di sortma molto meno.
Augurar

3
Python 2.7.3 sta facendo un confronto di byte, ma Python3 farebbe un confronto di parole unicode. Python3.3 è circa due volte più lento di Python2.7 per questo "test". Il sovraccarico di impacchettare i byte grezzi nella rappresentazione Unicode è persino maggiore dei già significativi oggetti di impacchettamento che Python 2.7.3 deve fare.
Anthon,

2
Ho trovato lo stesso rallentamento con cute anche altri. Su diverse macchine ora ho export LC_ALL=Cdentro .bashrc. Ma attenzione: questo essenzialmente si interrompe wc(tranne wc -l), solo per citare un esempio. I "byte cattivi" non vengono contati affatto ...
Walter Tross,

1
Questo problema di prestazioni si verifica anche con grep: puoi ottenere un sostanziale miglioramento delle prestazioni quando esegui il grepping di file di grandi dimensioni disabilitando UTF-8, specialmente quando lo faigrep -i
Adrian Pronk,

7

Si tratta più di un'analisi aggiuntiva che di una risposta effettiva, ma sembra variare a seconda dei dati ordinati. Innanzitutto, una lettura di base:

$ printf "%s\n" {1..1000000} > numbers.txt

$ time python sort.py <numbers.txt >s1.txt
real    0m0.521s
user    0m0.216s
sys     0m0.100s

$ time sort <numbers.txt >s2.txt
real    0m3.708s
user    0m4.908s
sys     0m0.156s

OK, Python è molto più veloce. Tuttavia, puoi rendere i coreutil sortpiù veloci dicendogli di ordinare numericamente:

$ time sort <numbers.txt >s2.txt 
real    0m3.743s
user    0m4.964s
sys     0m0.148s

$ time sort -n <numbers.txt >s2.txt 
real    0m0.733s
user    0m0.836s
sys     0m0.100s

È molto più veloce, ma Python vince ancora con un ampio margine. Ora, riproviamo ma con un elenco non ordinato di numeri 1M:

$ sort -R numbers.txt > randomized.txt

$ time sort -n <randomized.txt >s2.txt 
real    0m1.493s
user    0m1.920s
sys     0m0.116s

$ time python sort.py <randomized.txt >s1.txt
real    0m2.652s
user    0m1.988s
sys     0m0.064s

Il coreutils sort -nè più veloce per i dati numerici non ordinati (anche se potresti essere in grado di modificare il cmpparametro dell'ordinamento python per renderlo più veloce). Coreutils sortè ancora significativamente più lento senza la -nbandiera. E i personaggi casuali, non i numeri puri?

$ tr -dc 'A-Za-z0-9' </dev/urandom | head -c1000000 | 
    sed 's/./&\n/g' > random.txt

$ time sort  <random.txt >s2.txt 
real    0m2.487s
user    0m3.480s
sys     0m0.128s

$ time python sort.py  <random.txt >s2.txt 
real    0m1.314s
user    0m0.744s
sys     0m0.068s

Python batte ancora coreutils ma con un margine molto più piccolo di quello che mostri nella tua domanda. Sorprendentemente, è ancora più veloce se si osservano dati alfabetici puri:

$ tr -dc 'A-Za-z' </dev/urandom | head -c1000000 |
    sed 's/./&\n/g' > letters.txt

$ time sort   <letters.txt >s2.txt 
real    0m2.561s
user    0m3.684s
sys     0m0.100s

$ time python sort.py <letters.txt >s1.txt
real    0m1.297s
user    0m0.744s
sys     0m0.064s

È anche importante notare che i due non producono lo stesso output ordinato:

$ echo -e "A\nB\na\nb\n-" | sort -n
-
a
A
b
B

$ echo -e "A\nB\na\nb\n-" | python sort.py 
-
A
B
a
b

Stranamente, l' --buffer-sizeopzione non sembrava fare molta (o nessuna) differenza nei miei test. In conclusione, presumibilmente a causa dei diversi algoritmi menzionati nella risposta di goldilock, il pitone sortsembra essere più veloce nella maggior parte dei casi, ma GNU numericosort lo batte su numeri non ordinati 1 .


L'OP ha probabilmente trovato la causa principale, ma per completezza, ecco un confronto finale:

$ time LC_ALL=C sort   <letters.txt >s2.txt 
real    0m0.280s
user    0m0.512s
sys     0m0.084s


$ time LC_ALL=C python sort.py   <letters.txt >s2.txt 
real    0m0.493s
user    0m0.448s
sys     0m0.044s

1 Qualcuno con più python-fu di quanto dovrei provare a testare la modifica list.sort()per vedere la stessa velocità può essere raggiunto specificando il metodo di ordinamento.


5
L'ordinamento Python ha un ulteriore vantaggio di velocità, basato sul tuo ultimo campione: ordine numerico ASCII. sortsembra fare un po 'di lavoro extra per i confronti maiuscoli / minuscoli.
Izkata,

@Izkata Questo è tutto! Vedi la mia risposta qui sotto.
Augurar

1
In realtà Python ha un po 'di sovraccarico che crea le sue stringhe interne stdindall'input grezzo . Conversione coloro ai numeri ( lines = map(int, list(stdin))) e ritorno ( stdout.writelines(map(str,lines))) rende l'intero ordinamento andare più lento, fino dal 0.234s reale alla 0.720s sulla mia macchina.
Anthon,

6

Entrambe le implementazioni sono in C, quindi condizioni di parità. Coreutils sort a quanto pare utilizza il Mergesort algoritmo. Mergesort esegue un numero fisso di confronti che aumenta logaritmicamente alla dimensione dell'input, ovvero grande O (n log n).

L'ordinamento di Python utilizza un ordinamento di unione / inserimento ibrido univoco, timsort , che eseguirà un numero variabile di confronti con uno scenario del caso migliore di O (n) - presumibilmente, in un elenco già ordinato - ma è generalmente logaritmico (logicamente, tu non può andare meglio di logaritmico per il caso generale durante l'ordinamento).

Dati due diversi tipi logaritmici, uno potrebbe avere un vantaggio rispetto all'altro su un determinato set di dati. Un ordinamento di unione tradizionale non varia, quindi funzionerà allo stesso modo indipendentemente dai dati, ma ad esempio quicksort (anche logaritmico), che varia, funzionerà meglio su alcuni dati ma peggio su altri.

Un fattore tre (o più di 3, dato che sortè parallelizzato) è piuttosto un po ', il che mi fa chiedere se non ci sia alcuna contingenza qui, come lo sortscambio su disco (l' -Topzione sembrerebbe implicare che lo faccia). Tuttavia, il tuo tempo basso rispetto al tempo dell'utente implica che questo non è il problema.


È positivo che entrambe le implementazioni siano scritte in C. Sono sicuro che se avessi implementato un algoritmo di ordinamento in Python sarebbe molto, molto più lento.
agosto

A proposito, il file è costituito da valori float generati casualmente tra 0 e 1, quindi non dovrebbe esserci troppa struttura da sfruttare.
agosto
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.