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

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

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

** ---------------- ИЛИ ЖЕ ----------------**

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

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

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

6 ответов

Решение

Производительность sed против 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 со случайной длиной строки и случайными данными, использующими эту команду (обратите внимание, что она определенно не оптимальна, она становится действительно медленной для>2M строк, но кого это волнует, это не то, что мы анализируем):

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/tail время выполнения в любом случае.

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

Настройка небольшого 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

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

Вот еще один вариант, используя только встроенные команды 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, и я видел значительные различия во времени обоих, хотя они все находятся на одном уровне.

Попытка в моей системе и префикс каждой команды с 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 КБ. Файл был прочитан с жесткого диска.

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

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

Есть несколько вещей, которые вы можете иметь в виду при выборе команды:

  • Какова ваша цель? sed потоковый редактор для преобразования текста. tail для вывода определенных строк текста. Если вы хотите разобраться со строками и только распечатать их, используйте tail, Если вы хотите редактировать текст, используйте sed,
  • tail имеет гораздо более простой синтаксис, чем sedтак что используйте то, что вы можете прочитать сами, а что могут читать другие.

Другим важным фактором является объем данных, которые вы обрабатываете. Небольшие файлы не дадут вам никакой разницы в производительности. Картина становится интересной, когда вы имеете дело с большими файлами. С 2 ГБ BIGFILE.txt мы можем видеть, что 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

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

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

Другие вопросы по тегам