Самый быстрый и эффективный способ получить количество записей (строк) в сжатом gzip файле

16

Я пытаюсь подсчитать количество записей в 7,6 ГБ gzip-файле. Я нашел несколько подходов, используя 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сжатие по существу работает, заменяя обычные длинные строки более короткими. Таким образом, вам нужно заботиться только о длинных строках в словаре, которые содержат \n, и подсчитывать (взвешенные) их появления. Например, из-за английских правил, .\nэто обычная 16-битная строка.
MSalters
19

Используйте unpigz.

Ответ Kusalananda является правильным, вам будет нужно распаковывать , что весь файл для проверки его содержимого. /bin/gunzipделает это так быстро, как может, на одном ядре. Pigz - это параллельная реализация, gzipкоторая может использовать несколько ядер.

К сожалению, декомпрессия сам нормальных GZIP файлов не может быть распараллеливание, но pigzделает предложение улучшенную версию gunzip, unpigz, что делает соответствующую работу , такие как чтение, запись и контрольную сумму в отдельном потоке. В некоторых быстрых тестах unpigzэто почти в два раза быстрее, чем gunzipна моей базовой машине i5.

Установите pigzс вашим любимым менеджером пакетов и используйте unpigzвместо gunzipили unpigz -cвместо zcat. Итак, ваша команда становится:

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

Все это предполагает, что узким местом является процессор, а не диск, конечно.

marcelm
источник
4
Моя pigzсправочная страница утверждает, что распаковка не может быть распараллелена, по крайней мере, без специально подготовленных потоков дефляции для этой цели. В результате pigz использует один поток (основной поток) для распаковки, но создаст три других потока для чтения, записи и проверки вычислений, что может ускорить распаковку при некоторых обстоятельствах . Тем не менее, как и вы, я нахожу, что это как минимум вдвое быстрее, чем gzip, если не из-за параллелизма
Стефан
@ StéphaneChazelas Хороший вопрос! Это объясняет слегка разочаровывающее ускорение декомпрессии. Я отредактировал свой пост, чтобы лучше отражать эту информацию.
marcelm
5

Проблема со всеми конвейерами в том, что вы по сути дела удваиваете работу. Независимо от того, насколько быстро выполняется декомпрессия, данные все равно необходимо перенести в другой процесс.

Perl имеет PerlIO :: gzip, который позволяет вам читать gzip-потоки напрямую. Следовательно, он может предложить преимущество, даже если его скорость распаковки может не соответствовать скорости 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]': $!";

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

$ time -p ./gzlc.pl spy.gz 
1154737
настоящий 4.49
пользователь 4.47
система 0,01

против

$ time -p unpigz -c spy.gz | туалет
1154737
реальный 3,68
пользователь 4.10
система 1,46

и

$ time -p zcat spy.gz | туалет
1154737
настоящий 6.41
пользователь 6.08
система 0,86

Понятно, что использование здесь unpigz -c file.gz | wc -lявляется победителем в плане скорости. И эта простая командная строка наверняка превосходит написание программы, пусть даже короткой.

Синан Юнюр
источник
1
Я думаю, вы сильно переоцениваете ресурсы, необходимые для перемещения данных между двумя процессами, по сравнению с расчетами декомпрессии. Попробуйте
сравнить
2
@ SinanÜnür В моей системе Linux x86_64 (также на старом оборудовании) gzip | wcтакая же скорость, как у вашего сценария perl. И pigz | wcв два раза быстрее. gzipработает с той же скоростью, независимо от того, записываю ли я вывод в / dev / null или pipe в wcто, во что я верю, так это то, что «библиотека gzip», используемая perl, работает быстрее, чем инструмент командной строки gzip. Возможно, есть еще одна специфическая проблема Mac / Darwin с трубами. Удивительно, что эта версия на Perl вообще конкурентоспособна.
rudimeier
1
На моей установке x86_64 Linux, кажется, работает лучше, чем zcatи хуже, чем unpigz. Я поражен тем, насколько быстрее конвейер в системе Linux по сравнению с Mac. Я не ожидал, что, хотя я должен был, как я однажды заметил, одна и та же программа работала быстрее на виртуальной Linux-машине с ограниченными процессорами на том же Mac, чем на голом железе.
Синан Юнур,
1
Это интересно; в моей системе (Debian 8.8 amd64, quad core 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 секунды. GNU zcatвыполняется вдвое дольше zcat compressed.gz > /dev/null. Это на Mavericks, старый процессор Core 2 Duo, 16 ГБ оперативной памяти, Crucial MX100 SSD.
Синан Юнур
4

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

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

Так что ИМХО теоретически возможно придумать решение более эффективное, чем unpigz или zgrep. Это, как говорится, конечно, не практично (если кто-то уже не сделал это).

IAmBarry
источник
7
Основная проблема с этой идеей состоит в том, что символы Хаффмана, используемые DEFLATE, соответствуют битовым последовательностям после сжатия LZ77, поэтому в несжатом файле может не быть простой взаимосвязи между ними и символами U + 000A. Например, возможно, один символ Хаффмана означает последние пять битов "." за ними следуют первые три бита «\ n», а другой символ означает последние пять бит «\ n», за которыми следуют все восемь бит «Т».
Звол
@zwol Нет, часть LZ77 алгоритма Deflate сжимает последовательности байтов, а не битовые последовательности. en.wikipedia.org/wiki/DEFLATE#Duplicate_string_elidity
Ross Ridge,
1
@RossRidge Да, я этого не знал, но я не думаю, что это лишает законной силы то, что я сказал. В Хаффмане символы могут, как мне кажется основано на следующем абзаце этой ссылки, каждое расширение к переменному числу бит, они не должны производить целый ряд байт.
Звол
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

Как видите, большинство ответов пытается оптимизировать то, что может: количество переключений контекста и межпроцессный ввод-вывод. Причина в том, что это единственное, что вы можете легко оптимизировать здесь.

Теперь проблема заключается в том, что потребность в ресурсах практически не зависит от потребности в декомпрессии. Вот почему оптимизация не сделает ничего быстрее.

Там, где это могло бы быть действительно ускорено, это был бы модифицированный алгоритм un-gzip (то есть декомпрессии), который исключал бы фактическое получение распакованного потока данных; скорее он только вычисляет количество новых строк в распакованном потоке из сжатого . Это было бы сложно, это потребовало бы глубокого знания алгоритма gzip (некоторая комбинация алгоритмов сжатия LZW и Хаффмана ). Вполне вероятно, что алгоритм не позволяет существенно оптимизировать время декомпрессии с молнией, нам нужно только знать количество строк новой строки. Даже если бы это было возможно, по сути, должна была быть разработана новая библиотека для распаковки gzip (она не существует, пока не узнают).

Реальный ответ на ваш вопрос таков: нет, вы не можете сделать это значительно быстрее.

Может быть, вы могли бы использовать распакованную распаковку gzip, если она существует. Он может использовать несколько процессорных ядер для распаковки. Если он не существует, он может быть относительно легко разработан.

Для xz существует параллельный компрессор (pxz).

Петер - Восстановить Монику
источник