普段プログラムを組むときには可読性や保守性が大事なポイントになりますが、それと同じぐらい速度やメモリ効率なんかも大事です。
今回は少ないメモリでもなんとかできるような工夫をちょっとかいていこうかなと思います。
といいますか、普段からところどころやってはいたんですが、あまり意識せずにいたので、今回非常にお世話になることになりました。
自分がやっている開発はPerl何ですけど、多分どの言語でも同じかと思います。
基本的な考え方
テキストデータやデータベースなどある程度まとまった量の大きいデータを読み込むとき、それを一気に読み込むとメモリを圧迫し、PCに負担をかけます。
そこでデータを一気に読み込むのではなく、一定量ずつよみこめば負担は小さくなります。
例えば外部データに10万行のデータがあってここからプログラムを取り出すとき、一気に10万行とりこむのではなく、1000行ずつ頭から順番に取り出したほうが負担は少ないです。時間は遅くなります。
いってみると荷物の運搬のようなもので一度に大量の荷物を運ぶのではなく、小分けにして運べば時間はかかるかもしれませんが、少ない人数でも作業が可能ですね。
これが大体のイメージです。
プログラムA
1 2 3 |
$sth = $self->dbh->prepare($sql); $sth->execute(); $data = $sth->fetchall_arrayref({}); |
プログラムB
1 2 3 4 5 6 |
my $sth = $self->dbh->prepare($sql); $sth->execute(); my @arr=(); while (my $hash_ref = $sth->fetchrow_hashref) { push( @arr, $hash_ref ); } |
プログラムAが一気に取り込んだもの、プログラムBが少しずつ取り込んだものになります。
プログラムを習い始めのころ、最初はBのコードを書く機会が多いと思いますが、こういった背景があったのだということを知っておいたほうが良いでしょう。
Perl/DBIを使ってMySQLでSELECT文を実行する
似たような考え方としてテキストファイルから一定量のデータを取り出すといったことも同じ意図です。
プログラムA
1 2 3 4 |
my $io = IO::File->new($tmpfile, "r") or confess "Error: $tmpfile $!\n"; my @lines = $io->getlines(); $io->close(); my $res = $io->close(); |
プログラムB
1 2 3 4 5 6 7 |
my $io = IO::File->new($tmpfile, "r") or confess "Error: $tmpfile $!\n"; my $buf; my $large_file; while( my $res = $io->read($buf,4096)){ $large_file .= $buf; } |
一般的にはテキストファイルでも先ほどのデータベースと同じように、1行ずつとりだすケースが多いと思います。その場合はいいのですが、厄介なのは改行がなく、大量の文字列が保存されている場合などです(jsonの文字列など)。
その場合、データ量で少しずつ取り込むしかないので上記のプログラムのように、バイト数を指定してあげて対処をします。
PHPでもfgetsという関数でバイト数を指定できます。昔はこんな機能がどこで使うのだろう・・・と思っていたのですが、こういったところで活用していくわけですね。
今回の問題と似たことをあつかっているのがこちらの記事です。