Насколько быстрым является Perl-оператор smartmatch при поиске скаляра в массиве? - PullRequest
18 голосов
/ 17 октября 2010

Я хочу многократно искать значения в массиве, который не изменяется.

До сих пор я делал это следующим образом: я помещаю значения в хеш (поэтому у меня есть массив и хеш с практически одинаковым содержимым), и я ищу в хэше, используя exists.

Мне не нравится иметь две разные переменные (массив и хеш), которые обе хранят одну и ту же вещь; тем не менее, хэш гораздо быстрее для поиска.

Я обнаружил, что в Perl 5.10 есть оператор ~~ (smartmatch). Насколько это эффективно при поиске скаляра в массиве?

Ответы [ 3 ]

39 голосов
/ 18 октября 2010

Если вы хотите найти один скаляр в массиве, вы можете использовать подпрограмму List :: Util * first.Он останавливается, как только узнает ответ.Я не ожидаю, что это будет быстрее, чем поиск хеша , если у вас уже есть хеш , но если вы подумаете о создании хеша и наличии его в памяти, вам будет удобнее просто выполнить поискмассив, который у вас уже есть.

Что касается умений оператора Smart-Match, если вы хотите увидеть, насколько он умный, протестируйте его.:)

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

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

Вот эталонный тест:

#!perl
use 5.12.2;
use strict;
use warnings;

use Benchmark qw(cmpthese);

my @hits = qw(A B C);
my @base = qw(one two three four five six) x ( $ARGV[0] || 1 );

my @at_end       = ( @base, @hits );
my @at_beginning = ( @hits, @base );

my @in_middle = @base;
splice @in_middle, int( @in_middle / 2 ), 0, @hits;

my @random = @base;
foreach my $item ( @hits ) {
    my $index = int rand @random;
    splice @random, $index, 0, $item;
    }

sub count {
    my( $hits, $candidates ) = @_;

    my $count;
    foreach ( @$hits ) { when( $candidates ) { $count++ } }
    $count;
    }

cmpthese(-5, {
    hits_beginning => sub { my $count = count( \@hits, \@at_beginning ) },
    hits_end       => sub { my $count = count( \@hits, \@at_end ) },
    hits_middle    => sub { my $count = count( \@hits, \@in_middle ) },
    hits_random    => sub { my $count = count( \@hits, \@random ) },
    control        => sub { my $count = count( [], [] ) },
  }
);

Вот как это делали разные части.Обратите внимание, что это логарифмический график на обеих осях, поэтому наклоны линий врезания не так близки, как они выглядят:

Smart match speed

Итак, это выглядит как умное совпадениеОператор немного умен, но это не очень помогает, потому что вам все равно придется сканировать весь массив.Вы, вероятно, не знаете заранее, где вы найдете свои элементы.Я ожидаю, что хеш будет работать так же, как в умном сопоставлении в лучшем случае, даже если вам придется отдать ему немного памяти.


Хорошо, умное совпадение, будучи умным раз два, отлично, нореальный вопрос «Должен ли я использовать это?».Альтернатива - поиск по хешу, и меня беспокоит, что я не рассмотрел этот случай.

Как и в случае с любым другим тестом, я начинаю думать о том, какими могут быть результаты, прежде чем я их на самом деле протестирую.Я ожидаю, что если у меня уже есть хеш, поиск значения будет молниеносным.Этот случай не проблема.Меня больше интересует случай, когда у меня еще нет хеша.Как быстро я могу сделать хеш и найти ключ?Я ожидаю, что это не так хорошо, но все же лучше, чем наихудшее умное совпадение?

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

  • У вас есть один массив, который вы будете искать повторно
  • Вы всегда получаете новый массив, который вам нужно искать только один раз
  • Вы получаете очень большие массивы, но имеете ограниченную память

Теперь, помня об этом, я добавляю к своей предыдущей программе:

my %old_hash = map {$_,1} @in_middle; 

cmpthese(-5, {
    ...,
    new_hash       => sub { 
        my %h = map {$_,1} @in_middle; 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $h{$_} }
        $count;
        },
    old_hash       => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ if exists $old_hash{$_} }
        $count;
        },
    control_hash   => sub { 
        my $count = 0;
        foreach ( @hits ) { $count++ }
        $count;
        },
    }
);

Вот сюжет.Цвета немного сложно различить.Самая нижняя строка - это случай, когда вам нужно создать хеш в любое время, когда вы захотите найти его.Это довольно бедно.Две верхние (зеленые) строки - это элемент управления для хэша (на самом деле никакого хэша там нет) и поиска существующего хеша.Это лог / сюжет журнала;эти два случая быстрее, чем даже умный контроль соответствия (который просто вызывает подпрограмму).

Smart match v. hash

Есть еще несколько вещей, на которые стоит обратить внимание. Строки для «случайного» случая немного отличаются. Это понятно, потому что каждый бенчмарк (то есть один раз на масштабирование массива) случайным образом размещает элементы попадания в массиве-кандидате. Некоторые прогоны помещают их немного раньше, а некоторые чуть позже, но, поскольку я создаю массив @random только один раз за прогон всей программы, они немного перемещаются. Это означает, что неровности на линии незначительны. Если я попробую все позиции и усредню, я ожидаю, что эта «случайная» линия будет такой же, как и «средняя» линия.

Теперь, глядя на эти результаты, я бы сказал, что в худшем случае умное сопоставление выполняется намного быстрее, чем в худшем. В этом есть смысл. Чтобы создать хеш, я должен посетить каждый элемент массива и также сделать хеш, который много копирует. С умным совпадением копирование невозможно.

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

10 голосов
/ 17 октября 2010

Быстро для небольшого числа потенциальных совпадений, но не быстрее, чем хеш.Хеши - действительно правильный инструмент для тестирования членства в наборе.Поскольку доступ к хешу - O (log n), а smartmatch в массиве - все еще O (n) линейное сканирование (хотя и короткое замыкание, в отличие от grep), с большим количеством значений в разрешенных совпадениях, smartmatch становится относительно хуже.

Код теста (соответствует 3 значениям):

#!perl
use 5.12.0;
use Benchmark qw(cmpthese);

my @hits = qw(one two three);
my @candidates = qw(one two three four five six); # 50% hit rate
my %hash;
@hash{@hits} = ();

sub count_hits_hash {
  my $count = 0;
  for (@_) {
    $count++ if exists $hash{$_};
  }
  $count;
}

sub count_hits_smartmatch {
  my $count = 0;
  for (@_) {
    $count++ when @hits;
  }
  $count;
}

say count_hits_hash(@candidates);
say count_hits_smartmatch(@candidates);

cmpthese(-5, {
    hash => sub { count_hits_hash((@candidates) x 1000) },
    smartmatch => sub { count_hits_smartmatch((@candidates) x 1000) },
  }
);

Результаты теста:

             Rate smartmatch       hash
smartmatch  404/s         --       -65%
hash       1144/s       183%         --
8 голосов
/ 17 октября 2010

«Умный» в «умном совпадении» не относится к поиску.Речь идет о том, чтобы делать правильные вещи в нужное время в зависимости от контекста.

Вопрос о том, быстрее ли циклически перебирать массив или индекс в хэш, - это то, что вам нужно для сравнения, но в целомдолжен быть довольно маленький массив для быстрого просмотра, чем индексирование в хеш.

...