Самый быстрый 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';

Но это не включает экранирование разделителя, цитирование и т.д. - просто проверьте указанное количество разделителей:)