2006年11月08日 04:30 [Edit]

perl - In-Memory File

camel

Perl 5.8以降では、このような場合にin-memory fileが使えます。

【続】やはり Perl はメモリ喰いな言語。データ型の内部構造 :: Drk7jp
DB上の全レコードをいったん perl 側の配列に格納して、その結果を返す。ってコードなのですが、当然ながらレコード数が多くなればメモリを食うのは当たり前なのですが、以前の記事の内容を完全に忘却してました。ここには落とし穴があるのです。

使い方は、簡単です。

my @array = (0x21..0x7e);
my $memfile;

open my $wfh, '>', \$memfile or die $!;
print $wfh chr($_), "\n" for (@array);
close $wfh;

open my $rfh, '<', \$memfile or die $!;
print while(<$rfh>);
close $wfh;

もしarrayの内容にsequentialにしかアクセスしないのであれば、この方法で劇的にメモリー消費量を減らせます。

# 上のcodeにappend
use Devel::Size::Report qw/report_size/;
print report_size \@array;
print report_size \$memfile;
Size report v0.10 for 'ARRAY(0x181832c)':
  Array ref 1948 bytes (overhead: 444 bytes, 22.79%)
    Scalar 16 bytes
    Scalar 16 bytes
  ....
Total: 1948 bytes in 95 elements
Size report v0.10 for 'SCALAR(0x1801b1c)':
  Scalar Ref 228 bytes (overhead: 16 bytes, 7.02%)
    Scalar 212 bytes
Total: 228 bytes in 2 elements

もちろん、このやり方でもspeedとのtrade-offはあります。

#!/usr/local/bin/perl
use strict;
use warnings;
use Benchmark qw/timethese cmpthese/;

cmpthese(timethese(
        0, {
            array => sub {
                my @array = ();
                push @array, $_ for ( 0 .. 0xff );
                for ( 0 .. 0xff ) {
                    $array[$_] != $_ and die;
                }
            },
            memfile => sub {
                my $memfile = '';
                open my $wfh, '>', \$memfile or die;
                print $wfh $_, "\n" for ( 0 .. 0xff );
                close $wfh;
                open my $rfh, '<', \$memfile or die;
                for ( 0 .. 0xff ) {
                    my $line = <$rfh>;
                    $_ == $line or die;
                }
                close $rfh;
              }
          }
));
__END__
MacBook Pro 2.0GHz での結果:
Benchmark: running array, memfile for at least 3 CPU seconds...
     array:  4 wallclock secs ( 3.13 usr +  0.01 sys =  3.14 CPU) @ 7003.82/s (n=21992)
   memfile:  4 wallclock secs ( 3.18 usr +  0.01 sys =  3.19 CPU) @ 2334.80/s (n=7448)
          Rate memfile   array
memfile 2335/s      --    -67%
array   7004/s    200%      --

速度は1/3になりますが、メモリー効率は6倍。悪い取引じゃないと思います。ましてやswapなどのことを考えれば。

以上は一行一レコードの場合ですが、固定長の場合はさらにefficientに出来ます。例えば、レコードがすべてUnsigned Longであることがわかっていれば、

open my $wfh, '>', \$memfile or die $!;
print $wfh pack("N", $_) for (@array);
close $wfh;

open my $rfh, '<', \$memfile or die $!;
print unpack("N", $_), "\n" while(read($rfh, $_, 4));
close $wfh;

のように出来ます。

これを使ってWrapper Classを作ったりTie::Array::Whateverモジュールを作るのもTrivialでしょう。

最後に、これを可能にしてくれた今は亡きNick Ing-Simmonsに感謝しつつ本entryをsubmitします。

Dan the Just Another PerlIO Hacker


この記事へのトラックバックURL

この記事へのコメント
k.daibaさん、
このin-memory file、まだまだ用途が未開拓です。私自身こういう利用法ははじめておもいついたぐらいです。
私自身 perldoc -f open の説明以外の説明を見たことがないですし....
Dan the Just Another File Hacker
Posted by at 2006年11月08日 12:10
in memory fileについて,BuiltinFunction のopen(perldoc -f open)で書いてあるもの以外で説明が出ているところがあったら教えてください.固定長の場合の話とか見当たらなかったので,他にのTipsがあるのか興味あります.

Posted by k.daiba at 2006年11月08日 11:25