Quel est le plus rapide pour supprimer la première ligne du fichier… sed ou tail?

14

Dans cette réponse ( comment supprimer la première ligne d'un fichier avec sed? ), Il existe deux façons de supprimer le premier enregistrement d'un fichier:

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

** ---------------- OU ----------------**

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

Personnellement, je pense que l' tailoption est cosmétiquement plus agréable et plus lisible, mais probablement parce que je suis séduit.

Quelle méthode est la plus rapide?

WinEunuuchs2Unix
la source
5
Pas une réponse, mais une considération possible est qu'il sedest plus portable: "+2" pour tailfonctionne bien sur Ubuntu, qui utilise GNU tail, mais ne fonctionnera pas sur BSD tail.
John N
@JohnN merci de partager le tailmanque de compatibilité multiplateforme.
WinEunuuchs2Unix
3
@John N "+2" pour la queue fonctionne très bien sur mai Mac exécutant Sierra qui prétend utiliser la commande de queue BSD
Nick Sillito
Urgh, vous avez tout à fait raison - je viens de le relancer et cette fois, j'ai vérifié l'entrée. Ce que j'aurais dû faire la première fois. C'est POSIX aussi. / s'en va, embarrassé.
John N
2
@JohnN Vous ne vous trompez pas complètement. Dans le passé, UNIX ne fournissait pas l' -noption et utilisait la syntaxe tail +2 $file. Voir freebsd.org/cgi/… Il est possible que vous pensiez à cela plutôt qu'à l'un des BSD modernes.
DVD

Réponses:

28

Performances de sedvs tailpour supprimer la première ligne d'un fichier

TL; DR

  • sed est très puissant et polyvalent, mais c'est ce qui le rend lent, en particulier pour les fichiers volumineux avec de nombreuses lignes.

  • tail fait juste une chose simple, mais celle-ci, elle le fait bien et rapidement, même pour des fichiers plus gros avec de nombreuses lignes.

Pour les fichiers de petite et moyenne taille, sedet tailfonctionnent de manière similaire rapidement (ou lentement, selon vos attentes). Cependant, pour les fichiers d'entrée plus volumineux (plusieurs Mo), la différence de performances augmente considérablement (un ordre de grandeur pour les fichiers de l'ordre de centaines de Mo), avec des tailperformances nettement supérieures sed.

Expérience

Préparations générales:

Nos commandes à analyser sont:

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

Notez que je redirige la sortie à /dev/nullchaque fois pour éliminer la sortie du terminal ou les écritures de fichier comme goulot d'étranglement des performances.

Configurons un disque RAM pour éliminer les E / S disque comme goulot d'étranglement potentiel. J'ai personnellement un tmpfsmonté à /tmpdonc j'ai simplement placé mon testfilelà pour cette expérience.

Ensuite, je crée une fois un fichier de test aléatoire contenant un nombre spécifié de lignes $numoflinesavec une longueur de ligne aléatoire et des données aléatoires à l'aide de cette commande (notez que ce n'est certainement pas optimal, cela devient vraiment lent pour environ> 2 millions de lignes, mais peu importe, ce n'est pas le chose que nous analysons):

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

Oh, au fait. mon ordinateur portable de test exécute Ubuntu 16.04, 64 bits sur un processeur Intel i5-6200U. Juste pour comparaison.

Timing de gros fichiers:

Mise en place d'un énorme testfile:

L'exécution de la commande ci-dessus avec numoflines=10000000produit un fichier aléatoire contenant 10 millions de lignes, occupant un peu plus de 600 Mo - c'est assez énorme, mais commençons par cela, car nous pouvons:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

Effectuez la course chronométrée avec notre énorme testfile:

Maintenant, faisons d'abord une seule exécution chronométrée avec les deux commandes pour estimer avec quelle ampleur nous travaillons.

$ 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

On voit déjà un résultat vraiment clair pour les gros fichiers, tailc'est une amplitude plus rapide que sed. Mais juste pour le plaisir et pour être sûr qu'il n'y a pas d'effets secondaires aléatoires qui font une grande différence, faisons-le 100 fois:

$ 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 conclusion reste la même, sedest inefficace pour supprimer la première ligne d'un gros fichier, taildoit y être utilisée.

Et oui, je sais que les constructions de boucles de Bash sont lentes, mais nous ne faisons que relativement peu d'itérations ici et le temps qu'une boucle simple prend n'est pas significatif par rapport aux sed/ tailruntimes de toute façon.

Timing de petits fichiers:

Mise en place d'un petit testfile:

Maintenant, pour être complet, regardons le cas le plus courant où vous avez un petit fichier d'entrée dans la plage de Ko. Créons un fichier d'entrée aléatoire avec numoflines=100, ressemblant à ceci:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

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

Effectuez la course chronométrée avec notre petit testfile:

Comme nous pouvons nous attendre à ce que le timing de ces petits fichiers soit de l'ordre de quelques millisecondes par expérience, faisons tout de suite 1000 itérations:

$ 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

Comme vous pouvez le voir, les horaires sont assez similaires, il n'y a pas grand-chose à interpréter ou à s'interroger. Pour les petits fichiers, les deux outils sont tout aussi bien adaptés.

Byte Commander
la source
+1 pour avoir répondu merci. J'ai édité la question d'origine (désolé) sur la base des commentaires de Serg qui awkpeut aussi le faire. Ma question d'origine était basée sur le lien que j'ai trouvé en premier lieu. Après tout votre travail acharné, veuillez indiquer si je dois supprimer en awktant que solution candidate et revenir à la portée du projet d'origine de seulement sedet tail.
WinEunuuchs2Unix
De quel système s'agit-il? Sur mon mac (donc les outils BSD), le test sur / usr / share / dict / words me donne 0,09s pour sed et 0,19s pour tail (et awk 'NR > 1', fait intéressant).
Kevin
5

Voici une autre alternative, en utilisant uniquement les commandes bash et cat:

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

$fileest redirigé dans le { }groupe de commandes. Le readlit simplement et rejette la première ligne. Le reste du flux est ensuite dirigé vers catlequel l'écrit dans le fichier de destination.

Sur mon Ubuntu 16.04, les performances et la tailsolution sont très similaires. J'ai créé un fichier de test volumineux avec seq:

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

tail Solution:

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

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

cat/ brace solution:

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

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

Je n'ai cependant qu'une machine virtuelle Ubuntu à portée de main en ce moment, et j'ai vu des variations significatives dans le timing des deux, bien qu'elles soient toutes dans le même stade.

Traumatisme numérique
la source
1
+1 pour la réponse merci. C'est une solution très intéressante et j'adore les accolades et la lecture de droite à gauche via l'ordre de hiérarchie de bash. (Je ne sais pas si j'ai formulé cela correctement). Est-il possible de mettre à jour votre réponse avec la taille du fichier d'entrée et les résultats du benchmark de synchronisation si cela est assez facile à faire?
WinEunuuchs2Unix
@ WinEunuuchs2Unix Timings ajouté, bien qu'ils ne soient pas très fiables car c'est sur une VM. Je n'ai pas d'installation d'Ubuntu bare-metal à portée de main en ce moment.
Digital Trauma
Je ne pense pas que VM vs Bare Metal importe quand vous comparez VM à VM de toute façon. Merci pour la preuve du timing. J'irais probablement avec tailmais je pense toujours que l' readoption est très cool.
WinEunuuchs2Unix
4

En essayant mon système et en préfixant chaque commande avec, timej'ai obtenu les résultats suivants:

sed:

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

et la queue:

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

ce qui suggère que, sur mon système au moins AMD FX 8250 exécutant Ubuntu 16.04, la queue est nettement plus rapide. Le fichier de test avait 10 000 lignes avec une taille de 540 Ko. Le fichier a été lu sur un disque dur.

Nick Sillito
la source
+1 pour avoir répondu merci. Dans un test distinct dans AU Chatroom, un utilisateur a montré que la queue était 10 fois plus rapide (2,31 secondes) que sed (21,86 secondes) en utilisant un RAMDisk avec un fichier de 61 Mo. J'ai modifié votre réponse pour appliquer des blocs de code, mais vous voudrez peut-être aussi la modifier avec la taille de fichier que vous avez utilisée.
WinEunuuchs2Unix
@Serg Absolument juste que ce n'est qu'une réponse anecdotique, et potentiellement vous obtiendriez des résultats différents avec différentes configurations matérielles, différents fichiers de test, etc.
Nick Sillito
2
Le fichier n'étant pas dans le cache, lors de l'utilisation sedpeut jouer un rôle dans ce résultat, c'est l'ordre dans lequel vous les avez testés.
Minix
quelle sorte de système? Comme je l'ai commenté sur un autre post ici, sur mon mac sedétait environ deux fois plus rapide.
Kevin
1

Il n'y a pas de moyen objectif de dire ce qui est mieux, parce que sedet tailne sont pas les seules choses qui fonctionnent sur un système lors de l' exécution du programme. De nombreux facteurs tels que les E / S disque, les E / S réseau, les interruptions CPU pour les processus de priorité plus élevée - tous ces facteurs influencent la vitesse d'exécution de votre programme.

Les deux sont écrits en C, donc ce n'est pas un problème de langue, mais plutôt un problème environnemental. Par exemple, j'ai un SSD et sur mon système, cela prendra du temps en microsecondes, mais pour le même fichier sur le disque dur, cela prendra plus de temps car les disques durs sont beaucoup plus lents. Le matériel joue donc également un rôle.

Il y a quelques choses que vous voudrez peut-être garder à l'esprit lorsque vous envisagerez la commande à choisir:

  • Quel est ton but ? sedest un éditeur de flux pour transformer du texte. tailsert à produire des lignes de texte spécifiques. Si vous souhaitez traiter des lignes et les imprimer uniquement, utilisez tail. Si vous souhaitez modifier le texte, utilisez sed.
  • taila une syntaxe beaucoup plus simple que sed, alors utilisez ce que vous pouvez lire vous-même et ce que les autres peuvent lire.

Un autre facteur important est la quantité de données que vous traitez. Les petits fichiers ne vous donneront aucune différence de performances. L'image devient intéressante lorsque vous traitez de gros fichiers. Avec un BIGFILE.txt de 2 Go, nous pouvons voir qu'il seda beaucoup plus d'appels système tailet s'exécute considérablement plus lentement.

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
la source
+1 pour avoir répondu merci. Mais je ne suis pas sûr que ce commentaire m'aide à décider quelle commande je dois utiliser ....
WinEunuuchs2Unix
@ WinEunuuchs2Unix Eh bien, vous avez demandé quelle commande est la meilleure, donc je réponds exactement à cette question. La commande à choisir dépend de vous. Si vous pouvez lire tailmieux que sed- utilisez cela. Personnellement , j'utiliser pythonou awkplutôt que sedparce qu'il peut devenir complexe. De plus, si vous êtes préoccupé par les performances, regardons la réalité - vous voyez des résultats en microsecondes ici. Vous ne sentirez pas de différence à moins qu'il s'agisse d'un énorme fichier de plage de gigaoctets que vous essayez de lire
Sergiy Kolodyazhnyy
Oh, j'apprécierais aussi une awkréponse:) ... Ma question était basée sur un autre AU Q&A (dans le lien) et là, ils n'ont jamais mentionné awk. Je suis d'accord que le décalage horaire est nominal sur les petits fichiers. J'essayais juste de développer de bonnes habitudes.
WinEunuuchs2Unix
1
@ WinEunuuchs2Unix Bien sûr, la voici: awk 'NR!=1' input_file.txt . Cela me donne également le même résultat, environ 150 millisecondes, même nombre pour tailet sed. Mais agian, j'utilise SSD, donc je dirais que c'est le disque dur et le CPU qui importent, pas la commande.
Sergiy Kolodyazhnyy
1
@Serg, même avec seulement un fichier de 60 Mo contenant 1 million de lignes, 1000 exécutions sedprennent plus de 3 minutes, alors qu'il tailne faut que 20 secondes environ. Ce n'est pas si gros mais en fait, certainement pas dans la gamme GB.
Byte Commander
1

La meilleure réponse n'a pas pris en compte le disque > /dev/null

si vous avez un gros fichier et que vous ne voulez pas créer de doublon temporaire sur votre disque, essayez 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

Modifier: si le fichier est plus grand que la mémoire disponible vim -cne fonctionne pas, il ne semble pas assez intelligent pour effectuer un chargement incrémentiel du fichier

StevenWernerCS
la source
0

D'autres réponses montrent bien ce qui est préférable de créer un nouveau fichier avec la première ligne manquante. Si vous souhaitez modifier un fichier plutôt que de créer un nouveau fichier, je parie que ce edserait plus rapide car il ne devrait pas du tout créer un nouveau fichier. Mais vous devez rechercher comment supprimer une ligne avec edcar je ne l'ai utilisée qu'une seule fois.

akostadinov
la source