¿Por qué los coreutils son más lentos que Python?


20

Escribí el siguiente script para probar la velocidad de la funcionalidad de clasificación de Python:

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

Luego comparé esto con el sortcomando coreutils en un archivo que contiene 10 millones de líneas:

$ 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

El comando incorporado usó las cuatro CPU (Python solo usó una) pero tardó aproximadamente 3 veces más en ejecutarse. ¿Lo que da?

Estoy usando Ubuntu 12.04.5 (32 bits), Python 2.7.3 y sort8.13


@goldilocks Sí, mire al usuario en tiempo real.
augurar

Huh, tienes razón. Aparentemente fue paralelizado en coreutils 8.6.
Ricitos

¿Se puede usar --buffer-sizepara especificar que sortuse toda la memoria física disponible y ver si eso ayuda?
iruvar

@ 1_CR Probado con 1 GB de búfer, sin cambios significativos en el tiempo. Solo usó alrededor de .6 GB de eso, por lo que aumentar aún más el tamaño del búfer no ayudaría.
augurar

Respuestas:


22

El comentario de Izkata reveló la respuesta: comparaciones específicas de la localidad. El sortcomando usa la configuración regional indicada por el entorno, mientras que Python tiene por defecto una comparación de orden de bytes. Comparar cadenas UTF-8 es más difícil que comparar cadenas de bytes.

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

Qué hay sobre eso.


¿Y cómo se comparan para las cuerdas UTF-8?
Gilles 'SO- deja de ser malvado'

@Gilles Modificando el script de Python para usar locale.strxfrmpara ordenar, el script tardó ~ 32 segundos, aún más rápido sortpero mucho menos.
augurar el

3
Python 2.7.3 está haciendo una comparación de bytes, pero Python3 estaría haciendo una comparación de palabras unicode. Python3.3 es aproximadamente el doble de lento que Python2.7 para esta "prueba". La sobrecarga de empaquetar los bytes sin procesar en la representación Unicode es incluso mayor que los objetos de empaque ya significativos que Python 2.7.3 tiene que hacer.
Anthon

2
Encontré la misma desaceleración con cut, y otros también. En varias máquinas que ahora tengo export LC_ALL=Cen .bashrc. Pero cuidado: esto esencialmente se rompe wc(excepto wc -l), solo por nombrar un ejemplo. Los "bytes incorrectos" no se cuentan en absoluto ...
Walter Tross

1
Este problema de rendimiento también se produce con grep: puede obtener una mejora sustancial en el rendimiento al agrupar archivos de gran tamaño deshabilitando UTF-8, especialmente cuando se hacegrep -i
Adrian Pronk

7

Esto es más un análisis adicional que una respuesta real, pero parece variar según los datos que se ordenan. Primero, una lectura 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 es mucho más rápido. Sin embargo, puede hacer que los coreutils sean sortmás rápidos diciéndole que ordene numéricamente:

$ 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

Eso es mucho más rápido, pero Python aún gana por un amplio margen. Ahora, intentemos de nuevo pero con una lista no ordenada de números 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

Coreutils sort -nes más rápido para datos numéricos sin clasificar (aunque es posible que pueda modificar el cmpparámetro de ordenación de Python para hacerlo más rápido). Coreutils sortsigue siendo significativamente más lento sin la -nbandera. Entonces, ¿qué pasa con los caracteres aleatorios, no con los números puros?

$ 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 aún supera a los coreutils pero por un margen mucho menor que el que muestra en su pregunta. Sorprendentemente, aún es más rápido cuando se observan datos alfabéticos puros:

$ 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

También es importante tener en cuenta que los dos no producen la misma salida ordenada:

$ 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

Por extraño que parezca, la --buffer-sizeopción no parecía hacer mucha (o ninguna) diferencia en mis pruebas. En conclusión, presumiblemente debido a los diferentes algoritmos mencionados en la respuesta de goldilock, python sortparece ser más rápido en la mayoría de los casos, pero GNU numérico losort supera en números no ordenados 1 .


El OP probablemente ha encontrado la causa raíz, pero en aras de la exhaustividad, aquí hay una comparación final:

$ 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 Alguien con más python-fu del que debería intentar probar los ajustes list.sort()para ver que se puede lograr la misma velocidad especificando el método de clasificación.


55
Python sort tiene una ventaja de velocidad adicional, basada en su última muestra: orden numérico ASCII. sortparece estar haciendo un poco de trabajo extra para las comparaciones en mayúsculas / minúsculas.
Izkata

@Izkata ¡Eso es! Vea mi respuesta a continuación.
augurar

1
En realidad, Python tiene bastante sobrecarga creando sus cadenas internas a partir de la stdinentrada sin formato. La conversión de los números de ( lines = map(int, list(stdin))) y de vuelta ( stdout.writelines(map(str,lines))) hace que toda la clasificación ir más lento, hasta de 0.234s real para 0.720s en mi máquina.
Anthon

6

Ambas implementaciones están en C, por lo que hay igualdad de condiciones allí. Coreutils sort aparentemente usa el algoritmo mergesort . Mergesort realiza un número fijo de comparaciones que aumenta logarítmicamente al tamaño de entrada, es decir, O grande (n log n).

La clasificación de Python utiliza una combinación híbrida / inserción híbrida única, timsort , que realizará un número variable de comparaciones con el mejor de los casos de O (n), presumiblemente, en una lista ya ordenada, pero generalmente es logarítmica (lógicamente, usted no puede ser mejor que logarítmico para el caso general al ordenar).

Dados dos tipos logarítmicos diferentes, uno podría tener una ventaja sobre el otro en algún conjunto de datos en particular. Un tipo de fusión tradicional no varía, por lo que realizará el mismo independientemente de los datos, pero, por ejemplo, la clasificación rápida (también logarítmica), que varía, funcionará mejor en algunos datos pero peor en otros.

Sin sortembargo, un factor de tres (o más de 3, ya que está en paralelo) es bastante, lo que me hace preguntarme si no hay alguna contingencia aquí, como sortcambiar a disco (la -Topción parece implicar que sí lo hace). Sin embargo, su bajo tiempo de sistema vs. usuario implica que este no es el problema.


Buen punto de que ambas implementaciones están escritas en C. Estoy seguro de que si implementara un algoritmo de clasificación en Python, sería mucho, mucho más lento.
augurar

Por cierto, el archivo consta de valores flotantes generados aleatoriamente entre 0 y 1, por lo que no debería haber demasiada estructura para explotar.
augurar
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.