はじめて投稿させて頂きます。
WinXP、Delphi6でプログラムを作成しています。
Form上にボタンを配置してボタンが押された時に
for i:=0 to 768 do
SetLength(RDdata,768*768*50) ;
以上の処理を行うと以下の条件の時に処理中でFormが固まる現象が起きます。
1 実行ファイルを起動。
2 起動中にフォルダ操作を行う。(適当に)
3 ボタンを押して処理を開始する。
いろいろと調べたのですが、処理中にWM_ACTIVATEAPPメッセージが発生しているようです。この処理をWin2000で試したのですが、こっちでは上記のような現象が起きません。どなたか、なぜ固まるのかを教えていただけないでしょうか?よろしくお願いします。
これだけの情報ではなんとも答えようがありません。
RDdataがどういう型かもわからないですし。
実行ファイルを起動とは、デバッグ環境ではなく、ということでしょうか。
起動中にフォルダ操作を行うとは、mkdirやrndirするということでしょうか。それとも、エクスプローラでいろいろなフォルダを表示するだけでしょうか。
RDdataの型がbyte型だったと仮定しても、768*768*50=29491200byte。28.125MBのメモリを操作していることになります。
メモリ量はいくつでしょうか。増やせば解決しそうな気がします。
先ほどのコードが足りなかったので追加します。
{変更前}
for i:=0 to 768 do
SetLength(RDdata,768*768*50) ;
{変更後}
for i:=0 to 768 do
begin
SetLength(RDdata,768*768*50) ;
SetLength(RDdata,0) ;
end ;
よろしくお願いします。
早速の返信ありがとうございます。
RDdataの型はByteです。
デバッグ環境ではなくファイルから起動してください。
デバッグ環境では現象が起きないです。
フォルダ操作とは、エクスプローラでいろいろなフォルダを表示するだけです。
メモリは1Gですので問題はないと思います。
言葉や表現力に乏しいためご面倒をお掛けしますがよろしくお願いします。
デバッグ環境でも問題は発生します。
64回繰り返したところで、EOutOfMemory例外が発生しています。
# メモリは128MB
そちらの環境ではうまく動いているようなので、それがどうしてかわかりませんが。
ちゃんと、768回の確保・解放したことを確認していますよね?
128MBで64回ということは、単純計算で512回ほどでエラーになると思います。
# 実際には、もっと早くなると思います
例外処理はどうしていますか?
try
{ 上の処理 }
except
ShowMessage('エラー');
end;
こんな感じでやってみてください。
実行ファイルにすると(オプションにもよりますが)、重大な例外(アクセス違反など)をのぞいて、キャッチしない例外は無視されると思います。
にしのさんが書かれたような例外処理を加えてデバッグ環境で実行してみましたが、例外は発生しませんでした。
また、処理自体はデバッグ環境でも、そうでなくても最後まで処理されているのですが、デバッグ環境でない場合はFormが[応答なし]になってしまいます。
にしのさんの環境でEOutOfMemory例外が発生しているということはメモリが
うまく開放されてないということなんですかね?
今の環境はDelphi7,win2000なので、再現でいていないのかもしれません。
解放しないのは仕様のようですよ。
解放していたら今よりもっと遅いものになるでしょう。
もし、デフォルトのメモリマネージャが使えないようでしたら、SetMemoryManegerで再定義してやるのも手です。
>>解放しないのは仕様のようですよ。
>>解放していたら今よりもっと遅いものになるでしょう。
SetLength(RDdata,0)
と記述すれば、RDdataの領域は開放されてますけど?
>>もし、デフォルトのメモリマネージャが使えないようでしたら、>>SetMemoryManegerで再定義してやるのも手です
メモリマネージャはOSによって変わるものなのですか?
SetLength()ではなくHeapAllocなどで試したところ上記の現象が起りません。メモリマネージャが怪しいのはどういった理由からでしょうか?
メモリマネージャについての知識がない為ご教授ください。
にしのさんの環境(Delphi7,win2000)では、ボタンが押されると、
for文を64回繰り返したところで処理が止まるのでしょうか?
画面が固まってるだけで、処理は動いていませんか?
Delphiは、TMemoryManegerというメモリーマネージャがあります。
APIを直接使えば、OSのメモリーマネージャを直に使うことになるので、Delphiのメモリ管理に関しての制限は起きません。
こちらの環境では、64回の後に「メモリ不足です」というエラーダイアログが表示されます。
調べてみると、スコープを抜けた時点で解放されるそうです。
つまり、ループでSetLength(〜,0)にしても、すぐには解放されないようです。
明示的に
var
PRDdata
・・・
// SetLength(RDdata, 0);
PRDdata := @RDdata;
DynArrayClear(PRDdata, TypeInfo(byte));
としてやれば、最後まで確保・解放されました。
これでどうでしょうか。
>>PRDdata := @RDdata;
>>DynArrayClear(PRDdata, TypeInfo(byte));
開放処理を上記のように変更したら上記の現象は起きませんでした!!
ありがとうございます。
SetLength(*,0)としてもスコープを抜けるまで開放されないのですね。
知りませんでした。 勉強になります。
あともうひとつ、似たような現象が起きているのでご教授ねがえないでしょうか?
現在のFormにTImage(512*512)を追加して、サイズに見合うビットマップファイルを読み込み、以下の処理を行うと上記の現象がまた起きてしまいます。
for i:=0 to 768 do
begin
//SetLength(RDdata,768*768*50) ;
//SetLength(RDdata,0) ;
//PRDdata := @RDdata;
//DynArrayClear(PRDdata, TypeInfo(byte));
// 変更(fnameは適当に)
Form1.Image1.Picture.Bitmap.SaveToFile(fname);
end ;
実行する手順はSetLength()と同様です。
今日は所要がある為返信できませんが、よろしくお願いします。
>>SetLength(*,0)としてもスコープを抜けるまで開放されないのですね。
上記のような発言をしましたが、動的配列の初期化についていまいちよく理解できません。
デバッグ環境で実行すると、SetLength(*,0)でも開放されているように思います。上記の場合のスコープとは、RDdataがグローバル変数なので、宣言してあるユニットを抜けるまで開放されないという認識で宜しいのでしょうか?又、Win2000環境では上記の現象が起きないというのは、どういった理由からなのでしょうか?
TBitmapで同じような現象が起きる原因はよくわかりません。
ソースを追えばわかるかもしれませんが。
SetLengthしても、「確保した領域へのポインタを入れる変数」は空になっても、実際に確保した領域は開放されていないはずです。
たとえば、このようなコード。
SetLength(RDdata,768*768*50);
ShowMessage(IntToHex(Integer(RDdata), 8));
SetLength(RDdata,0);
ShowMessage(IntToHex(Integer(RDdata), 8));
SetLength(RDdata,768*768*50);
ShowMessage(IntToHex(Integer(RDdata), 8));
もし、解放しているのであれば、最初に確保した領域と、解放後に再確保した領域が同じでなければなりません。
こちらの環境では違うのですが、もしかして環境によって変わりますか?
もう1つ。
DynArrayClearも、確保した配列の情報を破棄するだけで、確保した領域はそのままのようです。
procedure TForm1.Button2Click(Sender: TObject);
var
PRDdata: Pointer;
begin
SetLength(RDdata,2000);
PRDdata := @RDdata;
DynArrayClear(PRDdata, TypeInfo(byte));
end;
procedure TForm1.Button3Click(Sender: TObject);
var
i: integer;
begin
for i := 0 to 1999 do RDdata[i] := 0;
ShowMessage(IntToStr(Length(RDdata)));
end;
Button2を押してから、Button3を押しても問題なく動きます。
問題なくは嘘でした。
解放後、再確保時にreallocするため、問題がないように見えるだけです。
OSにメモリを返していないので、一応動作するだけでした。
本題を忘れてました。
SetLength(〜,0);を行ったスコープを抜けた時点でクリアです。
にしのさん、お世話になります。
>>SetLengthしても、「確保した領域へのポインタを入れる変数」は空になっても、実際に確保した領域は開放されていないはずです。
タスクマネージャでメモリの使用状況を見て確認したのですが、SetLength(*,0)を行った直後に使用メモリが減っているので開放されていうると考えていたのですが、違うのでしょうか?
>>もし、解放しているのであれば、最初に確保した領域と、解放後に再確保した領域が同じでなければなりません。
こちらでも最初に確保した領域と再確保した領域は違いますけど、再確保時の領域が必ず、最初に確保した領域と同じになるのでしょうか?
>>DynArrayClearも、確保した配列の情報を破棄するだけで、確保した領域はそのままのようです。
DynArrayClear後も領域情報は変化しませんけど、破棄とはどういうことでしょうか?そもそもDynArrayClearはなにを行っているのでしょうか?調べ方が悪く、DynArrayClearに関する情報がわかりませんので教えていただけないでしょうか?
>>解放後、再確保時にreallocするため、問題がないように見えるだけです
どの部分の処理のことかわかりませんので教えてください。
>>解放後、再確保時にreallocするため、問題がないように見えるだけです。
メモリ使用量は、GetHeapStatusを使用して確認してみてください。
タスクマネージャを見ればわかると思いますが、単純なフォームを最小化・最大化などしても、メモリ使用量は変化します。
再確保する前に、別のどこかで確保してしまえば、同じにはなりません。
ほかで確保していなければ、同じになるはずです。
DynArrayClearは、内部で管理しているメモリを(内部処理的に)破棄しています。OSにすぐに返しているわけではなさそうです。
お持ちのDelphiにはソースはありませんか?System.pasあたりに書いてあったと思います。
> どの部分の処理のことかわかりませんので教えてください
DynArrayClearでは、最終的にはSysFreeMemで解放処理をしています。
このSysFreeMemは、解放したい領域を内部でとっておき、再利用しています。
Delphi-MLの、
http://www2.big.or.jp/~osamu/Delphi/delphi-browse.cgi?index=057158
このあたりの記事が、少し参考になるかと思います。
http://www.asahi-net.or.jp/~HA3T-NKMR/DGS/index.htm
ここのDHGLに含まれるNkMMモジュールを使ってもBitmapの問題は発生しますか?
もしかしたらこれで直るかも。
独自メモリマネージャの実装なので、ソースを読むだけでも勉強になりますよ。
返信が遅くなり申し訳ありません。
GetHeapStatusを使ってSetLength(*,0)の処理前、処理後の動的に割り当てた領域のバイト数を調べたところやはり開放されていることがわかりました。
それと、DynArrayClearでやった場合だとメモリが開放されていないこともわかりました。
DynArrayClearについて、にしのさんに教わったとおり、System.pasを調べてみたところ、SetLength(*,0)とすると、内部でDynArrayClearが呼ばれていることがわかりました。
教えていただいたMLざっと読んでみました。
メモリ開放のタイミングは限定できないという感じなんですかね?
DynArrayClearでSysFreeMemが呼ばれているのに開放されてない点もそういったことからなんですかね。
にしのさんは今回起きている現象についてどうお考えでしょうか?
私はいまいち整理ができません。
よろしくお願いします。
メモリマネージャの件、ありがとうございます。
勉強しながら、組み込んでみようと思います。
ツイート | ![]() |