Який швидше видалити перший рядок у файлі ... sed або tail?


14

У цій відповіді ( Як я можу видалити перший рядок файлу з sed? ) Є два способи видалення першої записи з файлу:

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

** ---------------- АБО ---------------- **

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

Особисто я вважаю, що цей tailваріант косметично приємніший і легший для читання, але, мабуть, тому, що мені непросто.

Який метод найшвидший?


5
Не відповідь, але можливе враження, що sedце портативніше: "+2" для tailвідмінної роботи на Ubuntu, який використовує GNU tail, але не працює на BSD tail.
— Джон N

@JohnN дякую за те, що поділився tailвідсутністю сумісності між платформами.
— WinEunuuchs2Unix

3
@John N "+2" для хвоста працює чудово на травні Mac під управлінням Sierra, який претендує на використання хвостової команди BSD
— Нік Сілліто

Урх, ти абсолютно прав - я щойно перезапустив його і цього разу перевірив вхід. Що я мав би зробити перший раз. Це також POSIX. / відшаровується, бентежить.
— Джон N

2
@JohnN Ви не зовсім помиляєтесь. Раніше UNIX не забезпечував цей -nпараметр і використовував синтаксис tail +2 $file. Дивіться freebsd.org/cgi/… Можливо, ви думали про це, а не про один із сучасних BSD.
— hvd

Відповіді:


28

Продуктивність sedvs. tailдля видалення першого рядка файлу

TL; DR

  • sed дуже потужний і універсальний, але саме це робить його повільним, особливо для великих файлів з багатьма рядками.

  • tail робить лише одну просту річ, але ця вона робить добре і швидко, навіть для великих файлів з багатьма рядками.

Для файлів малого та середнього розміру sedта tailвиконуються однаково швидко (або повільно, залежно від ваших очікувань). Однак для більших вхідних файлів (декілька МБ) різниця в продуктивності значно зростає (порядок розміру для файлів у межах сотень Мбайт), з tailчітко перевершуючими показниками sed.

Експеримент

Загальна підготовка:

Наші команди для аналізу:

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

Зауважте, що я /dev/nullкожного разу пересилаю висновки, щоб усунути вихідний термінал або запис файлів як вузьке місце.

Давайте встановимо диск RAM для усунення дискового вводу / виводу як потенційного вузького місця. У мене особисто є tmpfsучасник, /tmpтому я просто розмістив testfileтам свій експеримент.

Тоді я один раз створюю випадковий тестовий файл, що містить задану кількість рядків $numoflinesіз випадковою довжиною рядка та випадковими даними, використовуючи цю команду (зауважте, що це точно не оптимально, він стає дійсно повільним для приблизно> 2М рядків, але кого це цікавить, це не що ми аналізуємо):

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

Ой, дощо. мій тестовий ноутбук працює на Ubuntu 16.04, 64 біт на процесорі Intel i5-6200U. Просто для порівняння.

Час великих файлів:

Налаштування величезного testfile:

Запустити команду вище із numoflines=10000000створеним випадковим файлом, що містить 10М рядків, займає трохи більше 600 Мб - це досить величезно, але почнемо з нього, тому що ми можемо:

$ wc -l testfile 
10000000 testfile

$ du -h testfile 
611M    testfile

$ head -n 3 testfile 
qOWrzWppWJxx0e59o2uuvkrfjQbzos8Z0RWcCQPMGFPueRKqoy1mpgjHcSgtsRXLrZ8S4CU8w6O6pxkKa3JbJD7QNyiHb4o95TSKkdTBYs8uUOCRKPu6BbvG
NklpTCRzUgZK
O/lcQwmJXl1CGr5vQAbpM7TRNkx6XusYrO

Виконайте приурочений пробіг із нашими величезними testfile:

Тепер давайте зробимо лише одночасний запуск з обома командами спочатку, щоб оцінити, з якою величиною ми працюємо.

$ 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

Ми вже бачимо дійсно зрозумілий результат для великих файлів, tailце на швидкість швидше, ніж sed. Але просто для розваги і щоб бути впевненим, що випадкові побічні ефекти не мають великої різниці, давайте зробимо це 100 разів:

$ 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

Висновок залишається таким же, sedнеефективний для видалення першого рядка великого файлу, його tailслід використовувати там.

І так, я знаю, що конструктивні петлі Баша повільні, але ми тут робимо лише мало ітерацій, і час, який звичайний цикл займає, не є істотним порівняно з sed/ час tailвиконання.

Визначення часу:

Налаштування малого testfile:

Тепер для повноти розглянемо більш поширений випадок, коли у вас є невеликий вхідний файл у діапазоні kB. Давайте створимо випадковий вхідний файл за допомогою такого numoflines=100вигляду:

$ wc -l testfile 
100 testfile

$ du -h testfile 
8,0K    testfile

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

Виконайте приурочений пробіг із нашими маленькими testfile:

Оскільки ми можемо очікувати, що час роботи таких невеликих файлів буде в межах декількох мілісекунд від досвіду, давайте відразу зробимо 1000 ітерацій:

$ 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

Як бачите, таймінги досить схожі, тлумачити чи дивуватися не так вже й багато. Для невеликих файлів обидва інструменти однаково добре підходять.


+1 за відповідь дякую. Я редагував оригінальне запитання (вибачте) на основі коментаря Серга, який також awkможе це зробити. Моє оригінальне запитання ґрунтувалося на посиланні, яке я знайшов в першу чергу. Після всієї вашої наполегливої ​​роботи, будь ласка, порадьте, чи варто мені зняти awkкандидата на рішення та повернути увагу до початкового обсягу проекту лише sedта tail.
— WinEunuuchs2Unix

Яка це система? На моєму mac (тому інструменти BSD) тестування на / usr / share / dict / words дає мені 0,09 секунди для sed і 0,19s для хвоста (і awk 'NR > 1', що цікаво).
— Кевін

5

Ось ще одна альтернатива, використовуючи лише bash вбудовані та cat:

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

$fileперенаправляється в { }групу команд. readПросто читає і скидає перший рядок. Потім решта потоку проходить трубопроводом, catякий записує його у файл призначення.

На моєму Ubuntu 16.04 продуктивність цього та tailрішення дуже схожі. Я створив великий тестовий файл із seq:

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

tail рішення:

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

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

cat/ брекет-рішення:

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

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

Наразі у мене є лише зручна програма Ubuntu VM, і я помітив суттєві зміни в термінах обох, хоча вони всі в одному і тому ж бальному парку.


1
+1 за відповідь, дякую. Це дуже цікаве рішення, і я люблю дужки і праворуч на читання через ієрархічний наказ Баша. (не впевнений, чи правильно я це сформулював). Чи можливо оновити свою відповідь розміром вхідного файлу та результатами порівняння часу, якщо це досить просто?
— WinEunuuchs2Unix

@ WinEunuuchs2Unix додано терміни, хоча вони не надто надійні, оскільки це на VM. Зараз у мене немає зручності установки металевого Ubuntu.
— Цифрова травма

Я не думаю, що VM проти Bare Metal має значення, коли ти порівняєш VM з VM. Дякую за підтвердження часу. Я б, мабуть, пішов, tailале все ще думаю, що readваріант дуже класний.
— WinEunuuchs2Unix

4

Спробувавшись у своїй системі та встановивши префікс кожної команди, timeя отримав такі результати:

sed:

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

і хвіст:

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

що дозволяє припустити, що в моїй системі принаймні AMD FX 8250 під керуванням Ubuntu 16.04 хвіст значно швидший. Тестовий файл мав 10 000 рядків розміром 540 к. Файл читався з жорсткого диска.


+1 за відповідь дякую. В окремому тесті в AU Chatroom один користувач показав, що хвіст в 10 разів швидше (2,31 секунди), ніж sed (21,86 секунди), використовуючи RAMDisk з файлом 61 Мб. Я відредагував вашу відповідь, щоб застосувати блоки коду, але ви, можливо, захочете її відредагувати відповідно до використовуваного розміру файлу.
— WinEunuuchs2Unix

@Serg Абсолютно справедливо, що це лише анекдотична відповідь, і, можливо, ви отримаєте різні результати з різною конфігурацією апаратних засобів, різними тестовими файлами тощо
— Nick Sillito

2
Файл, який не знаходиться в кеш-пам'яті, при використанні sedможе відігравати фактор цього результату, саме такий порядок ви перевірили.
— Minix

яка система? Коли я коментував інший пост тут, на моєму mac sedбуло приблизно вдвічі швидше.
— Кевін

1

Там немає об'єктивного способу сказати, що краще, тому sedі tailне єдині речі , які працюють в системі під час виконання програми. Дуже багато факторів, таких як диск вводу / виводу, мережевий введення / виведення, переривання процесора для процесів з більш високим пріоритетом - всі вони впливають на швидкість роботи вашої програми.

Обидва вони написані на мові С, тож це не мовне питання, а більше екологічне. Наприклад, у мене є SSD, і в моїй системі це займе час у мікросекундах, але для одного файлу на жорсткому диску знадобиться більше часу, оскільки жорсткі диски значно повільніші. Тож апаратне забезпечення також відіграє роль у цьому.

Ви можете пам’ятати про кілька речей, які слід пам’ятати, яку команду вибрати:

  • Яке ваше призначення? sedє редактором потоку для перетворення тексту. tailпризначений для виведення конкретних рядків тексту. Якщо ви хочете мати справу з рядками і лише роздруковувати їх, використовуйте tail. Якщо ви хочете відредагувати текст, використовуйте sed.
  • tailмає набагато простіший синтаксис, ніж те sed, тому використовуйте те, що ви можете прочитати самостійно, і те, що можуть прочитати інші.

Ще одним важливим фактором є кількість даних, які ви обробляєте. Невеликі файли не дають вам різниці в продуктивності. Зображення стає цікавим, коли ви маєте справу з великими файлами. З BIGFILE.txt розміром 2 ГБ ми бачимо, що sedсистемні виклики мають набагато більше, ніж tailі працюють значно повільніше.

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

+1 за відповідь дякую. Але я не впевнений, що цей коментар допомагає мені вирішити, яку команду я повинен використовувати ....
— WinEunuuchs2Unix

@ WinEunuuchs2Unix Ну, ви запитали, яка команда краще, тому я відповідаю саме на це питання. Яку команду вибрати, залежить від вас. Якщо ти вмієш читати tailкраще sed- використовуй це. Я особисто хотів би використовувати pythonабо , awkа не sedтому , що він може отримати комплекс. Крім того, якщо вас турбує продуктивність, давайте поглянемо на реальність - тут ви бачите результати в мікросекундах. Ви не відчуєте різниці, якщо це не чудовий величезний файл у діапазоні гігабайт, який ви намагаєтесь прочитати
— Сергій Колодяжний

Ой, я також вдячний awkвідповіді:) ... Моє запитання грунтувалося на ще одному запитанні AU (за посиланням), і там вони ніколи не згадували awk. Я згоден, різниця в часі номінальна для невеликих файлів. Я просто намагався виробити деякі хороші звички.
— WinEunuuchs2Unix

1
@ WinEunuuchs2Unix Звичайно, тут: awk 'NR!=1' input_file.txt . Це дає мені однаковий результат, приблизно 150 мілісекунд, однакове число і для, tailі для sed. Але по-азіатськи я використовую SSD, тому я б сказав, що це жорсткий диск і процесор, а не команда.
— Сергій Колодяжний

1
@Serg, навіть маючи лише 60 МБ файл, що містить 1М рядків, 1000 працює sedза тривалістю більше 3 хвилин, тоді як tailпотрібно лише близько 20 секунд. Це не що велике поки на самому ділі, безумовно , не в діапазоні ГБ.
— Байт-командир

1

Верхня відповідь не враховувала диск > /dev/null

якщо у вас великий файл і ви не хочете створювати тимчасовий дублікат на своєму диску, спробуйте 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

Редагувати: якщо файл більше, ніж наявна пам'ять vim -c, не працює, схоже, його недостатньо розумно, щоб зробити додаткове завантаження файлу


0

Інші відповіді добре показують, що краще створити новий файл, у якому відсутній перший рядок. Якщо ви хочете відредагувати файл на відміну від створення нового файлу, я edб ставлю ставку швидше, оскільки він взагалі не повинен створювати новий файл. Але ви повинні шукати, як видалити рядок, edтому що я використовував її лише один раз.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.