Самый быстрый CSV-парсер в Perl
Я создаю подпрограмму, которая:
(1) Разбирает CSV файл,
(2) И проверяет, имеют ли все строки в этом файле ожидаемое количество столбцов. Он кричит, если количество столбцов недействительно.
Когда число строк колеблется от тысяч до миллионов, , как вы думаете, это самый эффективный способ сделать это?
Сейчас я пытаюсь выполнить эти реализации.
(1) Базовый парсер файлов
open my $in_fh, '<', $file or
croak "Cannot open '$file': $OS_ERROR";
my $row_no = 0;
while ( my $row = <$in_fh> ) {
my @values = split (q{,}, $row);
++$row_no;
if ( scalar @values < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
close $in_fh
or croak "Cannot close '$file': $OS_ERROR";
(2) Использование Text:: CSV_XS (bind_columns и csv- > getline)
my $csv = Text::CSV_XS->new () or
croak "Cannot use CSV: " . Text::CSV_XS->error_diag();
open my $in_fh, '<', $file or
croak "Cannot open '$file': $OS_ERROR";
my $row_no = 1;
my @cols = @{$csv->getline($in_fh)};
my $row = {};
$csv->bind_columns (\@{$row}{@cols});
while ($csv->getline ($in_fh)) {
++$row_no;
if ( scalar keys %$row < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
$csv->eof or $csv->error_diag();
close $in_fh or
croak "Cannot close '$file': $OS_ERROR";
(3) Использование Text:: CSV_XS (csv- > parse)
my $csv = Text::CSV_XS->new() or
croak "Cannot use CSV: " . Text::CSV_XS->error_diag();
open my $in_fh, '<', $file or
croak "Cannot open '$file': $OS_ERROR";
my $row_no = 0;
while ( <$in_fh> ) {
$csv->parse($_);
++$row_no;
if ( scalar $csv->fields < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
$csv->eof or $csv->error_diag();
close $in_fh or
croak "Cannot close '$file': $OS_ERROR";
(4) Использование Parse:: CSV
use Parse::CSV;
my $simple = Parse::CSV->new(
file => $file
);
my $row_no = 0;
while ( my $array_ref = $simple->fetch ) {
++$row_no;
if ( scalar @$array_ref < $min_cols_no ) {
croak "Invalid file format. File '$file' does not have '$min_cols_no' columns in line '$row_no'.";
}
}
Я сравнивал их с помощью модуля Benchmark.
use Benchmark qw(timeit timestr timediff :hireswallclock);
И это числа (в секундах), которые я получил:
1000 строк файла:
Реализация 1: 0,0016
Реализация 2: 0,0025
Реализация 3: 0.0050
Реализация 4: 0,0097
10 000 строк файла:
Реализация 1: 0.0204
Реализация 2: 0.0244
Реализация 3: 0,0523
Реализация 4: 0.1050
150000 строк файла:
Реализация 1:1.8697
Реализация 2: 3.1913
Реализация 3: 7.8475
Реализация 4: 15.6274
Учитывая эти числа, я бы сделал вывод, что простой парсер является самым быстрым, но из того, что я читал из разных источников, Text:: CSV_XS должен быть самым быстрым.
Кто-нибудь просветит меня по этому поводу? Что-то не так с тем, как я использовал модули? Большое спасибо за вашу помощь!
Ответы
Ответ 1
Обратите внимание, что ваша версия Text::CSV_XS
делает больше, чем ваша простая версия парсера. Он разбивает строку, помещает ее в память и делает вашу хэш-точку для полей.
У него также может быть другая логика под капотом, например, разрешение экранированных разделителей (я не знаю, поскольку я не использовал его). Кроме того, при использовании модуля всегда требуется небольшое количество накладных расходов: вызовы функций, передача параметров взад и вперед и, возможно, общий код, который на самом деле не применяется в вашем случае (например, проверка ошибок для вещей, заботиться).
Обычно преимущества использования модуля значительно перевешивают затраты. Вы получаете больше функций, более надежный код и т.д. Но это может быть неверно с небольшой, очень простой задачей. Если все, что вам нужно сделать, это проверить количество столбцов, использование модуля может быть излишним. Вы могли бы сделать свою собственную реализацию еще быстрее, просто подсчитав количество столбцов и не потрудившись разделить на все:
/(?:,[^,]*){$min_cols_no-1}/ or croak "Did not find minimum number of columns";
Если вы сделаете реальную обработку в дополнение к этому этапу проверки, использование модуля, вероятно, будет полезным.
Ответ 2
Существуют файлы CSV
header1,header2,header3
value1,value2,value3
а затем есть файлы CSV.
header1,"This, as they say, is header2","And header3
even contains a newline!"
value1,"value2, 2nd in a series of 3 values",value3
Text::CSV
, и его ilk были тщательно разработаны и протестированы для решения второго рода. Если вы уверены, что ваш ввод и всегда будет соответствовать простой спецификации CSV, то очень вероятно, что вы можете построить парсер, который будет превосходить Text::CSV
.
Ответ 3
Все модули синтаксического анализа CSV делают то же самое: открытие файла и анализ CSV каким-то образом, как и в вашем базовом подразделении. Они просто несут намного больше накладных расходов, потому что изнутри они делают намного больше, чем вам нужно (проверьте правильность формата CSV, пройдите вокруг структур объектов и т.д.). Это делает их более медленными, чем ваш базовый подход, в разной степени.
Вы сами оценили подходы; не является ли результат очевидным? Если мне не нужна расширенная функциональность модулей CSV, я бы сам проанализировал CSV файл.
(я не знаю, можно ли ускорить их, улучшив использование модулей)
Ответ 4
Просто для удовольствия, я проверил regexp для этого... и он работает!;) Если у вас достаточно барана, вы можете прочитать весь файл сразу, а затем использовать регулярное выражение:
my $blob = 'a;s;d
q;w;e
r;t;y
u;i;o
p;z;x
c;;b
n;m;f
g;h;j
k;l;';
say $blob =~ /^ ([^;]*;){2}[^;]* (\n (([^;]*;){2}[^;]*)+ \n ([^;]*;){2}[^;]*)? $/x ? 'ok' : 'bu';
Но это не включает экранирование разделителя, цитирование и т.д. - просто проверьте указанное количество разделителей:)