¿Cuál es más rápido para eliminar la primera línea del archivo ... sed o tail?

14

En esta respuesta ( ¿Cómo puedo eliminar la primera línea de un archivo con sed? ) Hay dos formas de eliminar el primer registro en un archivo:

sed '1d' $file >> headerless.txt

** ---------------- O ---------------- **

tail -n +2 $file >> headerless.txt

Personalmente, creo que la tailopción es cosméticamente más agradable y más legible, pero probablemente porque estoy desafiada.

¿Qué método es el más rápido?

WinEunuuchs2Unix
fuente
55
No es una respuesta, pero una posible consideración es que sedes más portátil: "+2" tailfunciona bien en Ubuntu, que usa GNU tail, pero no funcionará en BSD tail.
John N
@JohnN gracias por compartir la tailfalta de compatibilidad multiplataforma.
WinEunuuchs2Unix
3
@John N "+2" para la cola funciona bien en mayo Mac ejecutando Sierra que dice utilizar el comando BSD tail
Nick Sillito
Urgh, tienes toda la razón: acabo de volver a ejecutarlo y esta vez revisé la entrada. Lo que debería haber hecho la primera vez. También es POSIX. Me escabullo avergonzado.
John N
2
@ John No estás completamente equivocado. En el pasado, UNIX no proporcionaba la -nopción y usaba la sintaxis tail +2 $file. Ver freebsd.org/cgi/… Es posible que estuvieras pensando en eso en lugar de uno de los BSD modernos.
hvd

Respuestas:

28

Rendimiento de sedvs. tailpara eliminar la primera línea de un archivo

TL; DR

  • sed es muy potente y versátil, pero esto es lo que lo hace lento, especialmente para archivos grandes con muchas líneas.

  • tail hace solo una cosa simple, pero esa lo hace bien y rápido, incluso para archivos más grandes con muchas líneas.

Para archivos pequeños y medianos, sedy tailtienen un rendimiento similar rápido (o lento, según sus expectativas). Sin embargo, para archivos de entrada más grandes (varios MB), la diferencia de rendimiento crece significativamente (un orden de magnitud para archivos en el rango de cientos de MB), con un tailrendimiento claramente superior sed.

Experimentar

Preparaciones generales:

Nuestros comandos para analizar son:

sed '1d' testfile > /dev/null
tail -n +2 testfile > /dev/null

Tenga en cuenta que estoy canalizando la salida /dev/nullcada vez para eliminar la salida del terminal o las escrituras de archivos como cuello de botella de rendimiento.

Configuremos un disco RAM para eliminar la E / S del disco como posible cuello de botella. Personalmente tengo un tmpfsmontado en, /tmpasí que simplemente coloqué mi testfileallí para este experimento.

Luego, una vez estoy creando un archivo de prueba aleatorio que contiene una cantidad específica de líneas $numoflinescon longitud de línea aleatoria y datos aleatorios usando este comando (tenga en cuenta que definitivamente no es óptimo, se vuelve realmente lento para aproximadamente> 2M líneas, pero a quién le importa, no es el lo que estamos analizando):

cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n "$numoflines" > testfile

Oh, por cierto. mi computadora portátil de prueba ejecuta Ubuntu 16.04, 64 bits en una CPU Intel i5-6200U. Solo para comparar.

Tiempo de archivos grandes:

Configurar un gran testfile:

Ejecutar el comando anterior numoflines=10000000produjo un archivo aleatorio que contiene 10 millones de líneas, ocupando un poco más de 600 MB; es bastante grande, pero comencemos con él, porque podemos:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

Realice la ejecución cronometrada con nuestro enorme testfile:

Ahora hagamos una sola ejecución temporizada con ambos comandos primero para estimar con qué magnitudes estamos trabajando.

$ time sed '1d' testfile > /dev/null
real    0m2.104s
user    0m1.944s
sys     0m0.156s

$ time tail -n +2 testfile > /dev/null
real    0m0.181s
user    0m0.044s
sys     0m0.132s

Ya vemos un resultado realmente claro para archivos grandes, tailes una magnitud más rápido que sed. Pero solo por diversión y para estar seguros de que no hay efectos secundarios aleatorios que hagan una gran diferencia, hagámoslo 100 veces:

$ time for i in {1..100}; do sed '1d' testfile > /dev/null; done
real    3m36.756s
user    3m19.756s
sys     0m15.792s

$ time for i in {1..100}; do tail -n +2 testfile > /dev/null; done
real    0m14.573s
user    0m1.876s
sys     0m12.420s

La conclusión sigue siendo la misma, sedes ineficiente para eliminar la primera línea de un archivo grande, taildebe usarse allí.

Y sí, sé que las construcciones de bucle de Bash son lentas, pero solo estamos haciendo relativamente pocas iteraciones aquí y el tiempo que toma un bucle simple no es significativo en comparación con los tiempos de ejecución sed/ de tailtodos modos.

Sincronización de archivos pequeños:

Configurar un pequeño testfile:

Ahora para completar, veamos el caso más común de que tiene un pequeño archivo de entrada en el rango de kB. Creemos un archivo de entrada aleatorio con numoflines=100este aspecto:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

$ head -n 3 testfile 
tYMWxhi7GqV0DjWd
pemd0y3NgfBK4G4ho/
aItY/8crld2tZvsU5ly

Realice la ejecución cronometrada con nuestro pequeño testfile:

Como podemos esperar que los tiempos para archivos tan pequeños estén en el rango de unos pocos milisegundos de la experiencia, hagamos 1000 iteraciones de inmediato:

$ time for i in {1..1000}; do sed '1d' testfile > /dev/null; done
real    0m7.811s
user    0m0.412s
sys     0m7.020s

$ time for i in {1..1000}; do tail -n +2 testfile > /dev/null; done
real    0m7.485s
user    0m0.292s
sys     0m6.020s

Como puede ver, los tiempos son bastante similares, no hay mucho para interpretar o preguntarse. Para archivos pequeños, ambas herramientas son igualmente adecuadas.

Byte Commander
fuente
+1 por responder gracias. Edité la pregunta original (lo siento) en base al comentario de Serg que también awkpuede hacer esto. Mi pregunta original se basó en el enlace que encontré en primer lugar. Después de todo su arduo trabajo, por favor avise si debo eliminar awkcomo candidato a solución y volver a enfocar el alcance del proyecto original de solo sedy tail.
WinEunuuchs2Unix
¿Qué sistema es este? En mi Mac (herramientas BSD), las pruebas en / usr / share / dict / words me dan 0.09s para sed y 0.19s para tail (y awk 'NR > 1', curiosamente).
Kevin
5

Aquí hay otra alternativa, usando solo bash builtins y cat :

{ read ; cat > headerless.txt; } < $file

$file se redirige a la { } agrupación de comandos. El readsimplemente lee y descarta la primera línea. Luego se canaliza el resto de la secuencia a la catque se escribe en el archivo de destino.

En mi Ubuntu 16.04, el rendimiento de esto y la tailsolución son muy similares. Creé un archivo de prueba grande conseq :

$ seq 100000000 > 100M.txt
$ ls -l 100M.txt 
-rw-rw-r-- 1 ubuntu ubuntu 888888898 Dec 20 17:04 100M.txt
$

tail solución:

$ time tail -n +2 100M.txt > headerless.txt

real    0m1.469s
user    0m0.052s
sys 0m0.784s
$ 

cat/ solución de llave:

$ time { read ; cat > headerless.txt; } < 100M.txt 

real    0m1.877s
user    0m0.000s
sys 0m0.736s
$ 

Sin embargo, solo tengo una máquina virtual Ubuntu en este momento, y vi una variación significativa en los tiempos de ambos, aunque todos están en el mismo estadio.

Trauma digital
fuente
1
+1 por respuesta gracias. Esa es una solución muy interesante y me encantan las llaves y la lectura de derecha a izquierda a través del orden jerárquico de bash. (No estoy seguro si lo redacté correctamente). ¿Es posible actualizar su respuesta con el tamaño del archivo de entrada y el tiempo de los resultados de referencia si es lo suficientemente fácil de hacer?
WinEunuuchs2Unix
@ WinEunuuchs2Unix Timings agregados, aunque no son muy confiables ya que esto está en una VM. No tengo una instalación de Ubuntu de metal desnudo a mano en este momento.
Trauma digital
No creo que VM vs Bare Metal sea importante cuando comparas VM con VM de todos modos. Gracias por la prueba de tiempo. Probablemente iría tailpero todavía creo que la readopción es muy buena.
WinEunuuchs2Unix
4

Probar en mi sistema y anteponer cada comando con time , obtuve los siguientes resultados:

sed:

real    0m0.129s
user    0m0.012s
sys     0m0.000s

y cola:

real    0m0.003s
user    0m0.000s
sys     0m0.000s

lo que sugiere que, en mi sistema al menos AMD FX 8250 con Ubuntu 16.04, la cola es significativamente más rápida. El archivo de prueba tenía 10,000 líneas con un tamaño de 540k. El archivo fue leído desde un disco duro.

Nick Sillito
fuente
+1 por responder gracias. En una prueba separada en AU Chatroom, un usuario mostró que la cola es 10 veces más rápida (2,31 segundos) que sed (21,86 segundos) usando un disco RAM con disco de 61 MB. Edité su respuesta para aplicar bloques de código, pero es posible que también desee editarla con el tamaño de archivo que utilizó.
WinEunuuchs2Unix
@Serg absolutamente justo que esto es sólo una respuesta anecdótica, y potencialmente se podría obtener resultados diferentes con diferentes configuraciones de hardware, archivos de diferentes pruebas, etc.
Nick Sillito
2
El archivo no está en caché, cuando se usa sedpodría jugar un factor en este resultado, ese es el orden en el que los probó.
Minix
que tipo de sistema Como comenté en otra publicación aquí, en mi Mac sedfue aproximadamente el doble de rápido.
Kevin
1

No hay una forma objetiva de decir cuál es mejor, porque sedy tailno son las únicas cosas que se ejecutan en un sistema durante la ejecución del programa. Muchos factores, como la E / S de disco, la E / S de red, las interrupciones de la CPU para procesos de mayor prioridad, influyen en la rapidez con que se ejecutará su programa.

Ambos están escritos en C, por lo que este no es un problema de lenguaje, sino más bien ambiental. Por ejemplo, tengo SSD y en mi sistema esto llevará tiempo en microsegundos, pero para el mismo archivo en el disco duro tomará más tiempo porque los HDD son significativamente más lentos. Entonces el hardware también juega un papel en esto.

Hay algunas cosas que es posible que desee tener en cuenta al considerar qué comando elegir:

  • Cual es tu propósito ? sedes editor de flujo para transformar texto. tailes para generar líneas específicas de texto. Si desea lidiar con líneas y solo imprimirlas, usetail . Si desea editar el texto, use sed.
  • tailtiene una sintaxis mucho más simple que sed, así que use lo que pueda leer usted mismo y lo que otros puedan leer.

Otro factor importante es la cantidad de datos que está procesando. Los archivos pequeños no le darán ninguna diferencia de rendimiento. La imagen se pone interesante cuando se trata de archivos grandes. Con un BIGFILE.txt de 2 GB, podemos ver que sedtiene muchas más llamadas al sistema que tail, y funciona considerablemente más lento.

bash-4.3$ du -sh BIGFILE.txt 
2.0G    BIGFILE.txt
bash-4.3$ strace -c  sed '1d' ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 59.38    0.079781           0    517051           read
 40.62    0.054570           0    517042           write
  0.00    0.000000           0        10         1 open
  0.00    0.000000           0        11           close
  0.00    0.000000           0        10           fstat
  0.00    0.000000           0        19           mmap
  0.00    0.000000           0        12           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         2           rt_sigaction
  0.00    0.000000           0         1           rt_sigprocmask
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         7         7 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           getrlimit
  0.00    0.000000           0         2         2 statfs
  0.00    0.000000           0         1           arch_prctl
  0.00    0.000000           0         1           set_tid_address
  0.00    0.000000           0         1           set_robust_list
------ ----------- ----------- --------- --------- ----------------
100.00    0.134351               1034177        11 total
bash-4.3$ strace -c  tail  -n +2 ./BIGFILE.txt  > /dev/null
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 62.30    0.148821           0    517042           write
 37.70    0.090044           0    258525           read
  0.00    0.000000           0         9         3 open
  0.00    0.000000           0         8           close
  0.00    0.000000           0         7           fstat
  0.00    0.000000           0        10           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         1         1 ioctl
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.238865                775615         7 total
Sergiy Kolodyazhnyy
fuente
+1 por responder gracias. Pero no estoy seguro de que este comentario me esté ayudando a decidir qué comando debo usar ...
WinEunuuchs2Unix
@ WinEunuuchs2Unix Bueno, usted preguntó qué comando es mejor, por lo que estoy respondiendo exactamente esa pregunta. Qué comando elegir depende de usted. Si puedes leer tailmejor que sed, úsalo. Yo personalmente usaría pythono awkmás bien sedporque puede volverse complejo. Además, si le preocupa el rendimiento, seamos sinceros: aquí está viendo resultados en microsegundos. No sentirás la diferencia a menos que sea un archivo enorme en el rango de gigabytes que estás tratando de leer
Sergiy Kolodyazhnyy
Oh, agradecería una awkrespuesta también:) ... Mi pregunta se basó en otra AU Q&A (en el enlace) y allí nunca mencionaron awk. Estoy de acuerdo en que la diferencia horaria es nominal en archivos pequeños. Solo estaba tratando de desarrollar algunos buenos hábitos.
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix Claro, aquí está: awk 'NR!=1' input_file.txt . Me da el mismo resultado, alrededor de 150 milisegundos, el mismo número para ambos taily sed. Pero antes, estoy usando SSD, así que diría que lo que importa es el disco duro y la CPU, no el comando.
Sergiy Kolodyazhnyy
1
@Serg incluso con solo un archivo de 60 MB que contiene 1 millón de líneas, 1000 ejecuciones sedtoman más de 3 minutos, mientras que tailsolo necesitan alrededor de 20 segundos. Eso no es que no era grande, sin embargo, en realidad, sin duda en el rango GB.
Byte Commander
1

La respuesta principal no tuvo en cuenta el disco haciendo > /dev/null

si tiene un archivo grande y no desea crear un duplicado temporal en su disco, intente vim -c

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time sed -i '1d' testfile

real    0m59.053s
user    0m9.625s
sys     0m48.952s

$ cat /dev/urandom | base64 -w0 | tr 'n' '\n'| head -n 10000000 > testfile
$ time vim -e -s testfile -c ':1d' -c ':wq'

real    0m8.259s
user    0m3.640s
sys     0m3.093s

Editar: si el archivo es más grande que la memoria disponible vim -cno funciona, parece que no es lo suficientemente inteligente como para hacer una carga incremental del archivo

StevenWernerCS
fuente
0

Otras respuestas muestran bien qué es mejor crear un nuevo archivo sin la primera línea. Sin embargo, si desea editar un archivo en lugar de crear un nuevo archivo, apuesto a edque sería más rápido porque no debería crear un nuevo archivo en absoluto. Pero tienes que buscar cómo eliminar una línea edporque la usé solo una vez.

akostadinov
fuente