Нужно что-то быстрее, чем «wc -l»

12

Для действительно большого файла, такого как 1 ГБ, wc -lбывает медленно. У нас есть более быстрый способ подсчета количества новых строк для конкретного файла?

прости
источник
25
Купить диски быстрее? Учитывая, что каждый байт входных данных должен проверяться на предмет его 0x0Aотсутствия, ввод / вывод, несомненно, является узким местом.
thrig
2
Если вы подозреваете, wcчто у вас слишком много накладных расходов, вы можете попробовать реализовать свои собственные foreach byte in file: if byte == '\n': linecount++. Реализованный в C или ассемблере, я не думаю, что он станет быстрее, за исключением, возможно, пространства ядра в ОСРВ с наивысшим приоритетом (или даже для этого использует прерывание - вы просто не можете ничего сделать с системой). .. хорошо, я отвлекся ;-))
Мерфи
3
И просто для того, чтобы почувствовать масштаб, я быстро просмотрел time wc -l some_movie.aviнекэшированный файл, в результате чего 5172672 some_movie.avi -- real 0m57.768s -- user 0m0.255s -- sys 0m0.863s. Что в основном доказывает правильность @thrig, I / O разрушает вашу производительность в этом случае.
Мерфи
10
Лучший способ показать, что это узкое место дискового ввода-вывода, выполнить time wc -l some_large_file_smaller_than_cacheдважды подряд и посмотреть, как быстро выполняется вторая операция, а затем time wc -l some_large_file_larger_than_cacheпосмотреть, как не меняется время между запусками. Для файла размером ~ 280 МБ время идет от 1,7 до 0,2 секунд, но для файла объемом 2 ГБ - 14 секунд оба раза.
EightBitTony
1
Как медленно, слишком медленно для вас? Что /usr/bin/time wc -l <file>говорит? Какое у вас оборудование? Это быстрее, если вы запускаете команду несколько раз? Нам действительно нужно больше информации;)
marcelm

Ответы:

21

Вы можете попробовать написать на C:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(){
  char buf[BUFSIZ];
  int nread;
  size_t nfound=0;
  while((nread=read(0, buf, BUFSIZ))>0){
    char const* p;
    for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}
  }
  if(nread<0) { perror("Error"); return 1; }
  printf("%lu\n", nfound);
  return 0;
}

Сохранить, например, wcl.cскомпилировать, например, с помощью gcc wcl.c -O2 -o wclи запустить с

<yourFile ./wcl

Это находит переводы строк в 1 Гб файла в моей системе примерно за 370 мс (повторные запуски ). (Увеличение размеров буфера немного увеличивает время, которое следует ожидать - BUFSIZ должен быть близок к оптимальному). Это очень сравнимо с ~ 380 мс, которые я получаю wc -l.

Mmaping дает мне лучшее время около 280 мс , но оно, конечно, имеет ограничение, ограниченное реальными файлами (без FIFOS, без ввода с терминала и т. Д.):

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
int main(){
  struct stat sbuf;
  if(fstat(0, &sbuf)<0){ perror("Can't stat stdin"); return 1; }

  char* buf = mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, 0/*stdin*/, 0/*offset*/);
  if(buf == MAP_FAILED){ perror("Mmap error"); return 1; } 

  size_t nread = sbuf.st_size, nfound=0;
  char const* p;
  for(p=buf; p=memchr(p,'\n',nread-(p-buf)); nfound++,p++) {;}

  printf("%lu\n", nfound);
  return 0;
}

Я создал свой тестовый файл с:

 $ dd if=/dev/zero of=file bs=1M count=1042 

и добавил несколько тестовых новых строк с:

 $ echo >> 1GB 

и шестнадцатеричный редактор.

PSkocik
источник
Я был удивлен результатом mmap TBH. Раньше я думал, что mmaping быстрее, чем чтение / запись, но потом я увидел некоторые тесты Linux, которые показали обратное. Похоже, это очень верно в этом случае.
PSkocik
4
mmap собирается добиться гораздо лучших результатов в Linux, потому что в настоящее время он будет отображаться на огромных страницах, а промахи TLB - это sloooowwwwwww.
до
Может быть некоторое преимущество при чтении разных частей файла в отдельных потоках (например, с помощью forцикла OpenMP ), так что некоторый прогресс может быть достигнут, пока один поток останавливается в ожидании ввода. Но, с другой стороны, это может затруднить планирование ввода / вывода, поэтому все, что я могу порекомендовать, это попробовать и измерить!
Тоби Спейт
read()Версия может извлечь выгоду из упреждающего чтения.
Бармар
1
@TobySpeight Да, многопоточность может ускорить его. Кроме того, просмотр двух байтов за раз с помощью таблиц поиска 2 ^ 16 обеспечил довольно хорошую скорость в прошлый раз, когда я играл с ним.
PSkocik
18

Вы можете улучшить решение, предлагаемое @pskocik, уменьшив количество звонков до read. Существует много вызовов для чтения BUFSIZфрагментов из файла объемом 1 ГБ. Обычный подход к этому заключается в увеличении размера буфера:

  • просто ради интереса попробуйте увеличить размер буфера в 10 или 100 раз. На моем Debian 7 BUFSIZэто 8192. С оригинальной программой это 120 тысяч операций чтения. Вы, вероятно, можете позволить себе входной буфер 1 Мб, чтобы уменьшить его в 100 раз.
  • для более оптимального подхода приложения могут выделить буфер размером с файл, для чего требуется одна операция чтения. Это работает достаточно хорошо для «маленьких» файлов (хотя некоторые читатели имеют более 1 ГБ на своей машине).
  • наконец, вы можете поэкспериментировать с отображаемым в память вводом / выводом, который обрабатывает распределение как таковое.

При тестировании различных подходов вы можете иметь в виду, что некоторые системы (например, Linux) используют большую часть неиспользуемой памяти вашей машины в качестве дискового кэша. Некоторое время назад (почти 20 лет назад, упомянутое в подлых FAQ ) я был озадачен неожиданно хорошими результатами (не очень хорошего) алгоритма подкачки, который я разработал для обработки условий нехватки памяти в текстовом редакторе. Мне объяснили, что он работает быстро, потому что программа работает из буферов памяти, используемых для чтения файла, и что только если файл будет перечитан или записан, будет разница в скорости.

То же самое относится и к mmap(в другом случае, все еще в моем списке дел для включения в FAQ, разработчик сообщил о очень хороших результатах в сценарии, где дисковый кеш был реальной причиной улучшения). Разработка эталонных тестов требует времени и усилий, чтобы проанализировать причины хорошей (или плохой) производительности.

Дальнейшее чтение:

Томас Дики
источник
2
Вы переоцениваете влияние размеров буфера выше определенного порога. Как правило, увеличение размера буфера выше 4 КБ мало помогает, и на самом деле может иметь пагубные последствия, поскольку может вытолкнуть буфер из кэша L1. На моей машине тестирование с ddиспользованием буферов 1 МБ медленнее, чем 8 КБ. Значение по умолчанию для wc 8 КБ выбрано довольно хорошо, оно будет близко к оптимальному для широкого диапазона систем.
marcelm