Используя встроенные средства командной строки Perl или Linux, как быстро отобразить одно целое число в другое? - PullRequest
2 голосов
/ 14 октября 2011

У меня есть сопоставление текстового файла из двух целых чисел, разделенных запятыми:

123,456
789,555
...

Это 120Megs ... так что это очень длинный файл.

Я продолжаю искать первый столбец и возвращаю второй, например, искать 789 --returns -> 555, и мне нужно сделать это БЫСТРО, используя обычные встроенные Linux-модули.

Я делаю это прямо сейчас, и на это требуется несколько секунд.

Если бы у меня была база данных, я мог бы ее проиндексировать. Я думаю, мне нужен индексированный текстовый файл!

Вот что я делаю сейчас:

my $lineFound=`awk -F, '/$COLUMN1/ { print $2 }' ../MyBigMappingFile.csv`;

Есть ли какой-нибудь простой способ справиться с улучшением производительности?

Ответы [ 6 ]

3 голосов
/ 14 октября 2011

Хэш-предложения - это естественный способ, которым опытный Perler сделает это, но в этом случае он может быть неоптимальным.Он сканирует весь файл и создает большую плоскую структуру данных за линейное время.Более грубые методы могут закорачивать с наихудшим линейным временем, обычно на практике реже.

Сначала я создал большой файл сопоставления:

my $LEN = shift;
for (1 .. $LEN) {
    my $rnd = int rand( 999 );
    print "$_,$rnd\n";
}

С передачей $LEN в командной строке как10000000, файл вышел на 113МБ.Затем я протестировал три реализации.Первый - это метод поиска по хешу.Второе выкидывает файл и сканирует его с помощью регулярного выражения.Третий читает построчно и останавливается, когда совпадает.Полная реализация:

#!/usr/bin/perl
use strict;
use warnings;
use Benchmark qw{timethese};

my $FILE  = shift;
my $COUNT = 100;
my $ENTRY = 40;


slurp(); # Initial file slurp, to get it into the hard drive cache

timethese( $COUNT, {
    'hash'       => sub { hash_lookup( $ENTRY ) },
    'scalar'     => sub { scalar_lookup( $ENTRY ) },
    'linebyline' => sub { line_lookup( $ENTRY ) },
});


sub slurp
{
    open( my $fh, '<', $FILE ) or die "Can't open $FILE: $!\n";
    undef $/;
    my $s = <$fh>;
    close $fh;
    return $s;
}

sub hash_lookup
{
    my ($entry) = @_;
    my %data;

    open( my $fh, '<', $FILE ) or die "Can't open $FILE: $!\n";
    while( <$fh> ) {
        my ($name, $val) = split /,/;
        $data{$name} = $val;
    }
    close $fh;

    return $data{$entry};
}

sub scalar_lookup
{
    my ($entry) = @_;
    my $data = slurp();
    my ($val) = $data =~ /\A $entry , (\d+) \z/x;
    return $val;
}

sub line_lookup
{
    my ($entry) = @_;
    my $found;

    open( my $fh, '<', $FILE ) or die "Can't open $FILE: $!\n";
    while( <$fh> ) {
        my ($name, $val) = split /,/;
        if( $name == $entry ) {
            $found = $val;
            last;
        }
    }
    close $fh;

    return $found;
}

Результаты в моей системе:

Benchmark: timing 100 iterations of hash, linebyline, scalar...
      hash: 47 wallclock secs (18.86 usr + 27.88 sys = 46.74 CPU) @  2.14/s (n=100)
linebyline: 47 wallclock secs (18.86 usr + 27.80 sys = 46.66 CPU) @  2.14/s (n=100)
    scalar: 42 wallclock secs (16.80 usr + 24.37 sys = 41.17 CPU) @  2.43/s (n=100)

(Обратите внимание, что я запускаю это с SSD, поэтому ввод / вывод очень быстрый и, возможно, делает это начальнымslurp() ненужно. YMMV.)

Интересно, что реализация hash так же быстро, как и linebyline, что я не ожидал.При использовании slurping scalar может оказаться быстрее на традиционном жестком диске.

Однако самым быстрым на данный момент является простой вызов grep:

$ time grep '^40,' int_map.txt 
40,795

real    0m0.508s
user    0m0.374s
sys     0m0.046

Perl мог легко прочитать этот вывод и разделить запятую практически за любое время.

Редактировать: Не берите в голову grep.Я неправильно понял цифры.

3 голосов
/ 14 октября 2011

120 мег не такой большой. Предполагая, что у вас есть по крайней мере 512 МБ оперативной памяти, вы можете легко прочитать весь файл в хеш, а затем выполнить все ваши поиски в этом направлении.

1 голос
/ 14 октября 2011

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

Если при каждом вызове скрипта выполняется много поисков, я бы порекомендовал разобрать файл в хеш (или массив, если диапазон ключей достаточно узок).

Если файл меняется каждый день, создание новой базы данных SQLite может или не может стоить вашего времени.

Если при каждом вызове сценария требуется поискать только один ключ, и если файл данных часто меняется, вы можете получить улучшение, если весь файл закачивается в скаляр (минимизирует накладные расходы памяти и сопоставляет шаблон с этим разбора каждой строки).

#!/usr/bin/env perl

use warnings; use strict;

die "Need key\n" unless @ARGV;

my $lookup_file = 'lookup.txt';
my ($key) = @ARGV;

my $re = qr/^$key,([0-9]+)$/m;

open my $input, '<', $lookup_file
    or die "Cannot open '$lookup_file': $!";

my $buffer = do { local $/; <$input> };

close $input;

if (my ($val) = ($buffer =~ $re)) {
    print "$key => $val\n";
}
else {
    print "$key not found\n";
}

На моем старом медленном ноутбуке с ключом в конце файла:

C:\Temp> dir lookup.txt
...
2011/10/14  10:05 AM       135,436,073 lookup.txt

C:\Temp> tail lookup.txt
4522701,5840
5439981,16075
7367284,649
8417130,14090
438297,20820
3567548,23410
2014461,10795
9640262,21171
5345399,31041

C:\Temp> timethis lookup.pl 5345399

5345399 => 31041

TimeThis :  Elapsed Time :  00:00:03.343
1 голос
/ 14 октября 2011

use:

sed -n "/^$COLUMN1/{s/.*,//p;q}" file

Это оптимизирует ваш код тремя способами: 1) Нет необходимости разбивать каждую строку на две на «,».2) Вы останавливаете обработку файла после первого попадания.3) sed работает быстрее, чем awk.

Это должно составлять более половины вашего времени поиска.

HTH Chris

0 голосов
/ 15 октября 2011

Поможет ли это, если вы скопируете файл в /dev/shm и используете / awk / sed / perl / grep / ack / любой другой запрос для сопоставления?

не говорите мне, что вы работаете на 128 МБ оперативной памяти. :)

0 голосов
/ 14 октября 2011

В этом примере файл загружается в хеш (на моей системе это занимает около 20 с на 120 Мб).Последующие поиски тогда почти мгновенные.Это предполагает, что каждое число в левом столбце уникально.Если это не так, то вам нужно вставить числа справа с тем же номером слева в массив или что-то в этом роде.

use strict;
use warnings;

my ($csv) = @ARGV;
my $start=time;
open(my $fh, $csv) or die("$csv: $!");
$|=1;
print("loading $csv... ");
my %numHash;
my $p=0;
while(<$fh>) { $p+=length; my($k,$v)=split(/,/); $numHash{$k}=$v }
print("\nprocessed $p bytes in ",time()-$start, " seconds\n");
while(1) { print("\nEnter number: "); chomp(my $i=<STDIN>); print($numHash{$i}) }

Пример использования и вывод:

$ ./lookup.pl MyBigMappingFile.csv 
loading MyBigMappingFile.csv... 
processed 125829128 bytes in 19 seconds

Enter number: 123
322

Enter number: 456
93

Enter number: 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...