Masteries

技術的なことや仕事に関することを書いていきます.

忘備録: List::Util#anyかhashか

Perlを書いている時, 「いずれかと一致するなら...」みたいな条件を書く時に, List::Utilのanyを使うことがある.

metacpan.org

例えば, @ids という変数に何かしらのIDの一覧があって, あるIDが$idとして渡ってきた時に, $id@idsに含まれているかどうか, みたいなのは, anyを使って次のように書ける:

if (any { $_ eq $id ) @ids) {
    ...
}

但し, このコードは@idsに含まれる要素が増えれば増えるほど処理時間が伸びるはず. なので, こういう場合はhashを使って判定する方が早そうに思える:

my $hash = { $_ => 1 } @ids;
if ($hash{$id}) {
    ...
}

...というのをBenchmarkで確認してみたので, 忘備録としてメモっておきます.

use strict;
use warnings;

use List::Util qw(any);
use Benchmark qw(:all);

my $data = [1..10];
my $first = $data->[0];
my $last = $data->[-1];

my %prepared_hash = (map { $_ => 1 } $data->@*);

timethese(100_000_000, {
    prepared_hash => sub {
        $prepared_hash{$last};
    },
    hash => sub {
        my %hash = (map { $_ => 1 } $data->@*);
        $hash{$last};
    },
    any_first => sub {
        any { $_ eq $first } $data->@*;
    },
    any_last => sub {
        any { $_ eq $last } $data->@*;
    },
});

prepared_hashはhashを事前に準備するパターン. hashは都度hashを生成するパターンで, any_firstany_lastは先頭/末尾との一致をanyで判定するパターン. anyを使うパターンでも, any_lastよりもany_firstの方が先に処理が終わる(1回目でanyの条件部が真になるので, そこで処理が打ち切られる)はず. 果たして結果は...

Benchmark: timing 100000000 iterations of any_first, any_last, hash, prepared_hash...
 any_first: 24 wallclock secs (24.48 usr +  0.14 sys = 24.62 CPU) @ 4061738.42/s (n=100000000)
  any_last: 44 wallclock secs (43.14 usr +  0.22 sys = 43.36 CPU) @ 2306273.06/s (n=100000000)
      hash: 112 wallclock secs (110.83 usr +  0.50 sys = 111.33 CPU) @ 898230.49/s (n=100000000)
prepared_hash:  3 wallclock secs ( 2.40 usr +  0.00 sys =  2.40 CPU) @ 41666666.67/s (n=100000000)

結果としては, hashを事前に準備するパターン > anyを使うパターン > hashを都度準備するパターン, となりました.

なので, @idsに該当する部分が不変で, 判定が何度も呼び出される場合は, anyを使わずに事前にhashを用意してあげた方が素早く処理できそうですね.

最近のPerlなら, こういう感じでstateを使ってあげる手もありそうです:

state %hash = map { $_ => 1 } @ids;
if ($hash{$id}) {
    ...
}