Что быстрее удалить первую строку в файле ... sed или tail?

14

В этом ответе ( Как я могу удалить первую строку файла с помощью sed? ) Есть два способа удалить первую запись в файле:

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

** ---------------- ИЛИ ----------------**

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

Лично я думаю, что этот tailвариант косметически более приятен и более читабелен, но, вероятно, потому, что мне бросают вызов.

Какой метод самый быстрый?

WinEunuuchs2Unix
источник
5
Не ответ, но возможное соображение заключается в том, что sedон более переносим: «+2» tailотлично работает на Ubuntu, который использует GNU tail, но не работает на BSD tail.
Джон Н
@JohnN спасибо, что поделились tailотсутствием кроссплатформенной совместимости.
WinEunuuchs2Unix
3
@John N "+2" для tail отлично работает на майском Mac, работающем под управлением Sierra, которая утверждает, что использует команду BSD tail
Ник Силлито,
Ух, ты совершенно прав - я только что запустил его и на этот раз проверил ввод. Что я должен был сделать в первый раз. Это тоже POSIX. / срывается, смущенный.
Джон Н
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получением случайного файла, содержащего 10M строк, занимающих чуть более 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следует использовать там.

И да, я знаю, что циклические конструкции Bash медленны, но мы делаем здесь лишь относительно немного итераций, и время, которое занимает простой цикл, не так существенно по сравнению с sed/ tailruntime.

Сроки небольших файлов:

Настройка небольшая testfile:

Теперь для полноты давайте рассмотрим более распространенный случай, когда у вас есть небольшой входной файл в диапазоне кБ. Давайте создадим случайный входной файл 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

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

Byte Commander
источник
+1 за ответ спасибо Я отредактировал исходный вопрос (извините), основываясь на комментарии Серга, который awkтоже может это сделать. Мой оригинальный вопрос был основан на ссылке, которую я нашел в первую очередь. После всей вашей тяжелой работы, пожалуйста, посоветуйте, если мне следует выбрать awkкандидатуру решения и вернуть фокус только на первоначальный объем проекта sedи tail.
WinEunuuchs2Unix
Что это за система? На моем Mac (так что инструменты BSD) тестирование / usr / share / dict / words дает мне 0,09 секунды для sed и 0,19 секунды для tail (и 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 за ответ спасибо. Это очень интересное решение, и мне нравятся скобки и чтение справа налево через иерархический порядок в bash. (не уверен, правильно ли я сформулировал). Можно ли обновить ваш ответ размером входного файла и результатами теста времени, если это достаточно просто сделать?
WinEunuuchs2Unix
@ WinEunuuchs2Unix Времена добавлены, хотя они не очень надежны, как на ВМ. У меня нет удобной установки Ubuntu прямо сейчас.
Цифровая травма
Я не думаю, что VM против Bare Metal имеет значение, когда вы все равно сравниваете VM с VM. Спасибо за подтверждение времени. Я бы, наверное, пошел, tailно все же думаю, что readвариант очень крутой.
WinEunuuchs2Unix
4

Попробовав в своей системе и добавив к каждой команде префикс, timeя получил следующие результаты:

СЭД:

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 один пользователь показал, что tail в 10 раз быстрее (2,31 секунды), чем sed (21,86 секунды), используя RAMDisk с файлом 61 МБ. Я отредактировал ваш ответ, чтобы применить блоки кода, но вы можете отредактировать его также в соответствии с размером файла, который вы использовали.
WinEunuuchs2Unix
@Serg Абсолютно справедливо, что это всего лишь анекдотичный ответ, и, возможно, вы получите разные результаты с разными конфигурациями оборудования, разными тестовыми файлами и т. Д.
Ник Силлито,
2
Файл, не находящийся в кэше, при использовании sedможет сыграть решающую роль в этом результате, именно в этом порядке вы его проверили.
Minix
что за система? Как я прокомментировал еще один пост здесь, мой Mac sedбыл примерно в два раза быстрее.
Кевин
1

Нет объективного способа сказать, что лучше, потому что sedи tailэто не единственные вещи, которые запускаются в системе во время выполнения программы. Множество факторов, таких как дисковый ввод-вывод, сетевой ввод-вывод, прерывания процессора для процессов с более высоким приоритетом, влияют на скорость выполнения вашей программы.

Они оба написаны на C, так что это не языковая проблема, а скорее экологическая. Например, у меня есть 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 секунд. Это не что большое пока на самом деле, безусловно , не в диапазоне ГБ.
Byte Commander
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не работает, похоже, что он не достаточно умен, чтобы сделать дополнительную загрузку файла

StevenWernerCS
источник
0

Другие ответы хорошо показывают, что лучше создать новый файл с отсутствующей первой строкой. Если вы хотите отредактировать файл, а не создавать новый, я уверен, edчто это будет быстрее, потому что он вообще не должен создавать новый файл. Но вы должны искать, как удалить строку, edпотому что я использовал ее только один раз.

akostadinov
источник