Найшвидший і найефективніший спосіб отримати кількість записів (рядків) у файлі, стисненому gzip


16

Я намагаюся зробити кількість записів у файлі gzip 7,6 ГБ. Я знайшов кілька підходів за допомогою zcatкоманди.

$ zcat T.csv.gz | wc -l
423668947

Це працює, але це вимагає занадто багато часу (більше 10 хвилин, щоб отримати кількість рахунків). Я спробував ще кілька підходів, як

$ sed -n '$=' T.csv.gz
28173811
$ perl -lne 'END { print $. }' < T.csv.gz
28173811
$ awk 'END {print NR}' T.csv.gz
28173811

Усі три ці команди виконуються досить швидко, але дають неправильний підрахунок 28173811.

Як я можу виконати кількість записів за мінімальний час?


5
Чому потрібно рахувати кількість записів? Якщо ви намагаєтеся порахувати їх, перш ніж обробити, це означає, що вам доведеться двічі розпакувати файл.
Ендрю Генле

3
Детальніше про те, чому ви робите це було б корисно. Якщо це щось триває - тобто ви регулярно стискаєте купу файлів, а пізніше потрібно знати кількість записів - чому б не вважати їх такими, що вони стиснуті, і вставляти число у ім’я файлу?
jamesqf

3
Читання файлу 9,7 ГБ з механічного диска по своїй суті повільніше. Зберігайте файл на SSD і дивіться, наскільки швидше працює gunzip / zcat. Але як говорить @jamesqf, зберігайте рядок у назві файлу або у файлі в tgz, і витягнути цей файл буде набагато швидше.
ChuckCottrill

2
Є вагомі теоретичні причини, чому ви не можете уникнути цієї роботи. Формат стиснення, що дозволяє визначати корисну властивість даних ", не розпаковуючи її", за визначенням майже не такий хороший формат стиснення, як це міг би бути :)
hobbs

Відповіді:


28

В sed, perlі awkкоманди , які ви згадуєте можуть бути правильними, але вони все читали стиснутий дані та рахують символи нового рядка. Ці символи нового рядка не мають нічого спільного з символами нового рядка в нестиснених даних.

Для підрахунку кількості рядків у нестиснених даних немає ніякого способу їх розпакування. Ваш підхід з zcat- це правильний підхід, і оскільки дані настільки великі, він буде час , щоб розпакувати його.

Більшість утиліт, що займаються gzipстисненням та декомпресією, швидше за все, використовуватимуть ті самі загальні бібліотечні процедури. Єдиним способом прискорити це було б знайти реалізацію zlibпроцедур, які якихось швидші за типовими, і відновити, наприклад, zcatвикористовувати їх.


11
Це було б нетривіально вправою програмування, але здійсненним. Вся справа в тому, щоб не відбудовуватися zcat. Значна частина роботи Росії zcat- це генерування фактичного випуску продукції. Але якщо ви рахуєте лише \nсимволи, це не обов’язково. gzipстиснення по суті працює, замінюючи загальні довгі рядки на більш короткі рядки. Тому вам потрібно дбати лише про довгі рядки в словнику, які містять a \n, і порахувати (зважене) виникнення цих. Наприклад, англійські правила .\n- це загальна рядок 16 біт.
MSalters

19

Використовуйте unpigz.

Відповідь Кусалананда є правильною, вам потрібно буде зняти цей файл, щоб сканувати його вміст. /bin/gunzipробить це якомога швидше, на одному ядрі. Pigz - це паралельна реалізація, gzipяка може використовувати декілька ядер.

До жаль, декомпресія сам нормальних GZIP файлів не може бути розпаралелювання, але pigzробить пропозицію поліпшену версію gunzip, unpigz, що робить відповідну роботу , такі як читання, запис і контрольну суму в окремому потоці. У деяких швидких орієнтирах, unpigzце майже вдвічі швидше, ніж gunzipна моїй основній машині i5.

Встановіть pigzразом із вашим улюбленим менеджером пакунків і використовуйте unpigzзамість цього gunzipабо unpigz -cзамість нього zcat. Отже ваша команда стає:

$ unpigz -c T.csv.gz | wc -l

Все це передбачає, що вузьким місцем є саме процесор, а не диск, звичайно.


4
Моя pigzсторінка man говорить про те, що Decompression не може бути паралельним, принаймні, без спеціально підготовлених для цього потоків спуску. Як результат, pigz використовує один потік (основний потік) для декомпресії, але створить три інші потоки для читання, запису та перевірки обчислення, які можуть прискорити декомпресію за деяких обставин . І все-таки, як ви, я вважаю, що принаймні вдвічі швидше, ніж gzipякщо не через паралелізм
Стефан Шазелас

@ StéphaneChazelas Добрий момент! Це пояснює м'яко невтішну швидкість декомпресії. Я відредагував своє повідомлення, щоб краще відобразити цю інформацію.
marcelm

5

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

Perl має PerlIO :: gzip, який дозволяє читати gzipped потоки безпосередньо. Тому він може запропонувати перевагу, навіть якщо його швидкість декомпресії може не відповідати швидкості unpigz:

#!/usr/bin/env perl

use strict;
use warnings;

use autouse Carp => 'croak';
use PerlIO::gzip;

@ARGV or croak "Need filename\n";

open my $in, '<:gzip', $ARGV[0]
    or croak "Failed to open '$ARGV[0]': $!";

1 while <$in>;

print "$.\n";

close $in or croak "Failed to close '$ARGV[0]': $!";

Я спробував це з компресованим файлом у 13 Мб gzip (розпаковується до 1,4 ГБ) на старому MacBook Pro 2010 року з 16 ГБ оперативної пам’яті та старому ThinkPad T400 з 8 ГБ оперативної пам’яті з файлом, який уже знаходиться в кеші. На Mac сценарій Perl був значно швидшим, ніж використання конвеєрів (5 секунд проти 22 секунд), але в ArchLinux він втратив на розблокування:

$ time -p ./gzlc.pl spy.gz 
1154737
реальна 4,59
користувач 4.47
sys 0,01

проти

$ time -p unpigz -c spy.gz | wc -l
1154737
реально 3,68
користувач 4.10
сис 1,46

і

$ time -p zcat spy.gz | wc -l
1154737
справжній 6,41
користувач 6.08
sys 0,86

Зрозуміло, що використання тут unpigz -c file.gz | wc -lє переможцем і в швидкості. І цей простий командний рядок, безумовно, перемагає написання програми, хоч і короткої.


1
Я думаю, ви значно завищуєте ресурси, необхідні для переміщення даних між двома процесами, порівняно з декомпресійними обчисленнями. Спробуйте порівняння різних підходів;)
marcelm

2
@ SinanÜnür У моїй x86_64 система Linux (також старе обладнання) gzip | wcмає таку ж швидкість, що і у вашого сценарію Perl. І pigz | wcвдвічі швидше. gzipпрацює з однаковою швидкістю, незалежно від того, якщо я записую висновок в / dev / null або pipe в wcте, що я вважаю, що "бібліотека gzip", що використовується perl, швидша, ніж інструмент командного рядка gzip. Можливо, є ще одна специфічна для Mac / Darwin проблема з трубами. Досі дивно, що ця версія perl взагалі є конкурентоспроможною.
rudimeier

1
На моїй установці Linux x86_64, здається, це краще zcatі гірше, ніж unpigz. Я вражений тим, наскільки швидше конвеєр в системі Linux порівняно з Mac. Я не сподівався, що, хоч і мав би, як колись спостерігав, що та сама програма працювала швидше на обмеженому процесором Linux VM на тому ж Mac, ніж на голому металі.
Sinan Ünür

1
Це цікаво; у моїй системі (Debian 8.8 amd64, чотирьохядерний i5), скрипт perl трохи повільніше ... 109M .gz файл декомпресії до 1,1 г тексту, послідовно займає 5,4 секунди zcat | wc -lта 5,5 сек для вашого сценарію perl. Чесно кажучи, я вражений тим, що люди тут повідомляють, особливо між Linux та MacOS X!
marcelm

Я не знаю, чи можу я узагальнити те, що я бачу на своєму Mac, щось дивне відбувається. З декомпресованим файлом 1,4 ГБ wc -lпотрібно 2,5 секунди. gzcat compressed.gz > /dev/nullзаймає 2,7 секунди. Однак трубопровід займає 22 секунди. Якщо я спробую GNU wc, на декомпресований файл потрібно лише півсекунди, але на конвеєрі 22 секунди. zcatВиконання GNU займає вдвічі більше часу zcat compressed.gz > /dev/null. Це на Mavericks, старому процесорі Core 2 Duo, 16 Гб оперативної пам’яті, вирішальному MX100 SSD.
Sinan Ünür

4

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

gzip використовує стиснення DEFLATE. DEFLATE - це комбінація кодування LZ77 та Хаффмана. Можливо, є спосіб з'ясувати просто вузол символу Хаффмана для нового рядка та проігнорувати решту. Майже напевно є спосіб шукати нові рядки, закодовані за допомогою L277, зберігати кількість байтів і ігнорувати все інше.

Таким чином, IMHO теоретично можливо придумати рішення, більш ефективне, ніж unpigz або zgrep. Це, напевно, не є практичним (якщо хтось це вже не зробив).


7
Головною проблемою цієї ідеї є те, що символи Хаффмана, які використовуються DEFLATE, відповідають послідовності бітів після стиснення LZ77, тому у нестисненому файлі може бути не просте співвідношення між ними та символами U + 000A. Наприклад, можливо, один символ Хаффмана означає останні п'ять біт "". слідом за першими трьома бітами "\ n", а інший символ означає останні п'ять біт "\ n", а за ними всі вісім біт "T".
zwol

@zwol Ні, частина LZ77 алгоритму Deflate стискає послідовності байтів, а не бітові послідовності. en.wikipedia.org/wiki/DEFLATE#Duplicate_string_elimination
Росс Ридж

1
@RossRidge Хм, я цього не знав, але я не думаю, що це визнає недійсним те, що я сказав. У Хаффману символи можуть, як мені здається засноване на наступному абзаці цієї посилання, кожне розширення до змінного числа біт, вони не повинні виробляти цілий ряд байт.
zwol

1
@zwol Звичайно, вам потрібно шукати відповідні послідовності бітів коду Хаффмана в потоці бітів, але ця відповідь не пропонує іншого. Проблема з цією відповіддю полягає в тому, що визначити, які коди Хаффмана в кінцевому рахунку генерують чи більше символів нового рядка, не просто. Коди LZ77, які генерують нові рядки, постійно змінюються в міру переміщення розсувного вікна, а значить, і коди Хаффмана змінюються. Вам доведеться реалізувати весь алгоритм декомпресії, за винятком вихідної частини, а може бути і частини розсувного вікна, оскільки вас цікавлять лише нові рядки.
Росс Рідж

1

Це можна зробити за zgrepдопомогою -cпрапорця та $параметра.

У цьому випадку -c доручить команді вивести кількість збіжених рядків, а регулярний вираз $ збігається з кінцем рядка, щоб він відповідав кожному рядку або файлу.

zgrep -c $ T.csv.gz 

Як прокоментував @ StéphaneChazelas - zgrepце всього лише сценарій навколо zcatі grepі вона повинна забезпечувати однакову продуктивність з початковим пропозицієюzcat | wc -l


2
Привіт Яроне, дякую за відповідь, навіть zgrep займає стільки часу, скільки zcat мені потрібно знайти інший підхід, який я думаю
Рахул

8
zgrepце, як правило, скрипт, який викликає zcat(те саме gzip -dcq), щоб розпакувати дані та подати їх grep, тому допомогти не збирається.
Стефан Шазелас

1
@ StéphaneChazelas - дякую за коментар, оновіть мою відповідь, щоб відобразити це.
Ярон

0

Як бачимо, більшість відповідей намагається оптимізувати, що може: кількість контекстних комутаторів та міжобробних IO. Причина в тому, що це єдине, що ви можете легко оптимізувати тут.

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

Там, де це було б дійсно прискорене, це був би модифікований алгоритм un-gzip (тобто декомпресія), який виключає фактичне виробництво декомпресованого потоку даних; швидше, він обчислює лише кількість нових рядків у декомпресованому потоці від стисненого . Це було б важко, це вимагало б глибокого знання алгоритму gzip (деяка комбінація алгоритмів стиснення LZW і Хаффмана ). Цілком ймовірно, що алгоритм не дає можливості значно оптимізувати час декомпресії за допомогою освітлення, що нам потрібно лише знати кількість нових рядків. Навіть якщо це було б можливо, по суті, слід було б розробити нову бібліотеку декомпресії gzip (вона не існує, поки не буде відомо).

Реалістична відповідь на ваше запитання полягає в тому, що ні, ви не можете зробити це значно швидше.

Можливо, ви можете використати паралельну декомпресію gzip, якщо вона існує. Він може використовувати кілька ядер CPU для декомпресії. Якщо його не існує, його можна було б розвинути відносно легко.

Для xz існує паралельний компресор (pxz).

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