ハッシュを高速に処理する - Perl

ハッシュに大量のデータを挿入する際、プログラムの動作を出来る限り早く(高速に)、処理を軽くしたい。実は一行付け足すだけで、かなり違ってくる技がある。

keysは本来ハッシュのキーを取り出す際に利用するのだが、これから登録するハッシュのキーの数をあらかじめ宣言する機能を持つ。これを行うことでPerl内部の処理が1ステップ減り、大量のデータを追加する際、高速な動作が期待できる。

サンプル

;#
;#ハッシュの処理速度を上げる
;#

my %hash = ();

#-- ここで個数を指定する --#
keys(%hash) = 30000;           #この場合は3万件

open(DAT, 'ken_all.txt') or die($!);
while(<DAT>){
    chomp;
    my ( $post_cd, $address ) = split(/\t/);
    $hash{$post_cd} = $address;
}
close(DAT);

検証

原理はシンプル。 例外もあるが、原則としてPerlはハッシュに値が追加されるたびにメモリの確保を行っている。この作業を毎回行うよりも、冒頭で一括して行う方が速いため、動作が軽くなるのである。本当に軽くなるか、実際に実験を行ってみた。

ここでは1,000件、5,000件、30,000件のテストデータを用意した。郵政公社が公開している郵便番号のデータを加工し、ランダムに抽出した物である。

○1,000件
Benchmark: timing 1000 iterations of NON_SET, SET...
   NON_SET:  4 wallclock secs ( 3.99 usr +  0.00 sys =  3.99 CPU) @ 250.63/s (n=1000)
       SET:  3 wallclock secs ( 3.60 usr +  0.01 sys =  3.61 CPU) @ 277.01/s (n=1000)

○5,000件
Benchmark: timing 1000 iterations of NON_SET, SET...
   NON_SET: 20 wallclock secs (19.87 usr +  0.12 sys = 19.99 CPU) @ 50.03/s (n=1000)
       SET: 18 wallclock secs (18.30 usr +  0.08 sys = 18.38 CPU) @ 54.41/s (n=1000)

○30,000件
Benchmark: timing 1000 iterations of NON_SET, SET...
   NON_SET: 121 wallclock secs (119.55 usr +  0.93 sys = 120.48 CPU) @  8.30/s (n=1000)
       SET: 111 wallclock secs (110.51 usr +  0.58 sys = 111.09 CPU) @  9.00/s (n=1000)

実行結果を見て分かる通り、1000件などデータ件数が少ないうちは大差ないが、数万件レベルになってくると如実な違いが見て取れる。

ベンチマーク測定用コード

#!/usr/bin/perl

;#
;#ハッシュのベンチマーク
;#

use Benchmark;

#--------------------------------------------#
#ベンチマーク
#--------------------------------------------#
timethese(
    1000, {
          'SET'     => 'set1();'
        , 'NON_SET' => 'set2();'

    }
);

#--------------------------------------------#
#都度メモリ確保
#--------------------------------------------#
sub set1{
    my %hash = ();

    open(DAT, 'ken_all.txt') or die($!);
    while(<DAT>){
        chomp;
        my ( $post_cd, $address ) = split(/\t/);
        $hash{$post_cd} = $address;
    }
    close(DAT);

}

#--------------------------------------------#
#冒頭で一括してメモリ確保
#--------------------------------------------#
sub set2{
    my %hash = ();

    keys(%hash) = 30000;

    open(DAT, 'ken_all.txt') or die($!);
    while(<DAT>){
        chomp;
        my ( $post_cd, $address ) = split(/\t/);
        $hash{$post_cd} = $address;
    }
    close(DAT);
}