y-matsui::weblog

電子楽器、音楽、コンピュータ、プログラミング、雑感。面倒くさいオヤジの独り言

処理の方法で

プログラムネタ。
先日、人が作ったのを引き継いだプログラムのうち、DBから値を取り出しながら、テキストデータとしてエクスポートする処理を眺めていた。
数十万件件以上ものデータをDBから取り出し、テキストに書き出すテストを行っていたのだが、メモリ消費が激しく、700MB使っていたと思えば、次の瞬間には100MB程度に落ちたりする。
何かがおかしい。2時間待ったら結果が出た。
「メモリオーバーフローの例外」
くそ!
この処理、DBから取り出したレコードを、一度、構造体に格納し、各フィールド(メンバ)の値を変換したり、評価したりしてから、テキストファイルに書き出すのだが、構造体のサイズを動的に割り当ててるのが、初めて見るやり方だ。←redim preserveってやつ。配列にしか使えないということであったが、構造体でも使えるみたい。

当初思ったのは
「DBに問い合わせたら件数が分かるのに、なんで、1件取り出すごとに、構造体のサイズを拡張するの?」ってこと。
最初に問い合わせた件数と同じサイズの構造体を定義すれば良いのに。
数十万回ものループの度に、(ループの中で)redim preserveって、尋常じゃない気がしたので、ベテランプログラマに聞いて見たら、「良くやる方法だよ」っていう話。
それから、メモリオーバフローの件を、別の凄腕プログラマに聞いたら、「単純に構造体が大きすぎるんじゃないですか?(数十万件のレコードをメモリ上に持つって・・・って感じのニュアンス)」
「うーーーん。そうなのかなぁ」と自分。
試しに、ループの中で、動的に構造体の要素を拡張(多分、内部ではコピーして、破棄してるんじゃないかなぁ)するのを止めて、頭でカウントしてガバっと取得するように変更してみた。
2時間かかった挙句に、エラーを吐き出していたダメダメプログラムが、10分で結果をきちんと返す優秀なプログラムに変換された。
「だよなぁ」
自分の感覚が間違っていないことが証明されて、ちょっとすっきりしたのである。
「きっと、前任プログラマ氏は、別の処理の中でも、同じようなやり方をしているに違いない」と睨み、見てみると、テキストを1行づつ読みながら、DB登録する、先のとは逆の処理の部分でも、redim preserveを使っていた。テキストを読み込む処理では(DBカウントとは異なり)最後まで読んでみないと件数が分からないので、1行読むごとに増やしていく処理は、理にかなっているように思えた。
しかし、まずはやってみよう!。ということで処理を書き直した。
テキストをまず最終行まで空読みして、行数をカウント。構造体のサイズをここで決定する。
・・と同時に、件数が多い場合には、ユーザに「まじで処理を続行するん?」と確認を入れる。
ユーザが確認画面でOKとおっしゃったら、構造体をガバっと確保して、処理を走らせる。
見事なパフォーマンスで動作した。
もともとのプログラムも、件数が多い場合(ループ回数が多い場合)にメモリ破壊を起こしていたのだが、このバグが直った。
やっぱり、ループの中では不要なことをしない方が良いことが良く分かった。