Вам потрібно щось швидше, ніж "wc -l"


12

Для дійсно великого файлу, як 1 Гб, wc -lвідбувається повільно. Чи є у нас швидший спосіб обчислити кількість нових рядків для певного файлу?


25
Купувати швидші диски? Зважаючи на те, що кожен байт вхідного сигналу повинен перевірятись на його 0x0Aінсес, введення / виведення без сумніву є вузьким місцем.
триг

2
Якщо ви підозрюєте, wcщо у вас занадто багато накладних витрат, ви можете спробувати реалізувати свої власні foreach byte in file: if byte == '\n': linecount++. Якщо впроваджено в C або ассемблері, я не думаю, що воно стане швидше, за винятком можливо, в просторі ядра на RTOS з найвищим пріоритетом (або навіть використовувати переривання для цього - ви просто не можете нічого більше робити з системою. .. добре, я відволікаюсь ;-))
Мерфі

3
І просто, щоб отримати відчуття масштабу, я зробив швидку time wc -l some_movie.aviна незахований файл, в результаті чого 5172672 some_movie.avi -- real 0m57.768s -- user 0m0.255s -- sys 0m0.863s. Що в основному підтверджує право @thrig, введення / виведення погіршує вашу ефективність у цьому випадку.
Мерфі

10
Найкращий спосіб показати, що це дисковий вузол IO, зробіть 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 

і шестигранний редактор.


Я був здивований результатом mmap TBH. Раніше я думав, що створення карти швидше, ніж читання / запис, але потім я побачив деякі орієнтири Linux, які показали навпаки. Схоже, це дуже вірно в цьому випадку.
PSkocik

4
mmap отримає набагато кращі результати на Linux, оскільки в цей час буде відображено величезні сторінки, а помилки TLB - це sloooowwwwwww.
jthill

Можливо, буде користь для читання різних частин файлу в окремих потоках (наприклад, із forциклом OpenMP ), щоб досягти певного прогресу, коли один потік зупиняється в очікуванні введення. Але з іншого боку, це може перешкоджати плануванню вводу / виводу, тому все, що я можу порекомендувати, - це спробувати його і виміряти!
Toby Speight

read()Версія може отримати вигоду з читання,.
Бармар

1
@TobySpeight Так, багатопотоковість може прискорити його. Також дивлячись сканування двох байтів одночасно через 2 ^ 16 таблиці пошуку, забезпечив досить гарну швидкість, коли я останній раз грав з ним.
PSkocik

18

Ви можете покращити рішення, запропоноване @pskocik, зменшивши кількість дзвінків до read. Є багато дзвінків, щоб прочитати BUFSIZфрагменти з файлу 1Gb. Звичайний підхід до цього полягає в збільшенні розміру буфера:

  • просто для розваги, спробуйте збільшити розмір буфера в 10 разів. Або 100. На моєму Debian 7 BUFSIZце 8192. З оригінальною програмою це 120 тисяч операцій читання. Можливо, ви можете дозволити собі вхідний буфер 1 Мб, щоб зменшити його в 100 разів.
  • для більш оптимального підходу програми можуть виділити буфер розміром з файлом, що вимагає однієї операції зчитування. Це працює досить добре для "невеликих" файлів (хоча деякі читачі мають більше 1 Гбіт на своїй машині).
  • нарешті, ви зможете поекспериментувати зі вбудованим в пам'ять входом / виводом, який обробляє розподіл як таке.

Коли ви оцінюєте різні підходи, ви можете мати на увазі, що деякі системи (наприклад, Linux) використовують більшу частину невикористаної пам'яті вашого комп'ютера як кеш диска. Нещодавно (майже 20 років тому, що згадується в мерзенних FAQ ), мене здивували несподівано хороші результати (не дуже хорошого) алгоритму підкачки, який я розробив для обробки умов з низькою пам'яттю в текстовому редакторі. Мені було пояснено, що вона працює швидко, тому що програма працює з буферів пам'яті, які використовуються для читання файлу, і лише в тому випадку, якщо файл буде перечитаний або записаний, буде різниця у швидкості.

Це ж стосується mmap(в іншому випадку, все ще в моєму списку справ, щоб включити до FAQ, розробник повідомив про дуже хороші результати в сценарії, коли кеш-диск був фактичною причиною вдосконалення). Розробка тестів вимагає часу та турботи, щоб проаналізувати причини хорошої (або поганої) ефективності.

Подальше читання:


2
Ви завищуєте вплив розмірів буфера вище певного порогу. Зазвичай збільшення розміру буфера понад 4 КБ-ish не дуже допомагає, і насправді може бути згубним, оскільки може виштовхнути буфер з кешу L1. На моїй машині тестування з ddвикористанням буферів 1 Мб проходить повільніше, ніж 8 КБ. Значення за замовчуванням 8 кб для wc насправді вибрано досить добре, воно буде близьким до оптимального для великого кола систем.
marcelm
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.