C# と VB.NET の質問掲示板

ASP.NET、C++/CLI、Java 何でもどうぞ

C# と VB.NET の入門サイト

Re[9]: C++/CLIでメモリ解放が遅い


(過去ログ 54 を表示中)

[トピック内 12 記事 (1 - 12 表示)]  << 0 >>

■30301 / inTopicNo.1)  C++/CLIでメモリ解放が遅い
  
□投稿者/ あしび (1回)-(2008/12/20(Sat) 16:38:42)

分類:[C++/CLI] 

初めて投稿させていただきます。
原因がわからず困っております。

VS2005でC++/CLIを利用しています。
ロジック部分をC++で、インターフェース部分をC#で作っているアプリケーションなんですが、その橋渡しをするためにC++/CLIでくるんだモジュールを使用しています。
メモリチェックテストを行ったところ、このC++/CLIのクラスがひっかかりました。
チェックの仕方は、マネージドな各クラスをfor文でnewし、ある程度たまったところでのGC.Collect()後のメモリ使用量を調べるという方法です。
(実際のチェックテストでは、メソッド呼び出しまで調べていますが、今回の該当クラスでは、newするだけでメモリ圧迫が起きることがわかっています)
最初はネイティブコード部分のメモリリークを疑ったのですが、ツールで調べてもメモリリークは検知されませんでした。
さらに調べていくと、どうやらリークではなく、C++の例外機能を利用した場合にメモリ解放が遅いために使用量が増えていっているようです。
GC.Collect()後にSleepで時間を置いてあげると改善されます。
また、対象部分のコンストラクタとファイナラザ(およびデストラクタ)部分をネイティブなC++でクラス化し、それをnew/deleteする関数を持つDLLを作成して、C#とC++/CLIそれぞれでDllImportで動かした場合、メモリの圧迫は起きません。
さらに、同じコードをVC++2003でくるんだ場合もメモリの圧迫はありませんでした。

ほぼC++/CLIに問題があると考えているのですが、確信がありません。
もし、同じような現象に会ったことがある方がいらっしゃれば教えていただけますか。
どうぞよろしくお願いいたします。


引用返信 編集キー/
■30306 / inTopicNo.2)  Re[1]: C++/CLIでメモリ解放が遅い
□投稿者/ Azulean (246回)-(2008/12/20(Sat) 19:27:31)
どんなクラスかは分かりませんが、マネージオブジェクトはすぐにメモリ領域を解放しません。
その前提を踏まえても、おかしいと思える動作だったということでしょうか?

できれば、簡単なサンプルを示して頂ければ、どこに問題があるか指摘ができる可能性があります。
サンプルができなくとも、そのクラスでどんなメモリ確保・リソース確保が行われているか、デストラクタ・ファイナライザで何かやってるかといった情報が寄せられれば、回答しやすいかと思います。


# 例外でDisposeされていない(C++/CLIならdeleteか)ということはないと思いますが、念のため。
引用返信 編集キー/
■30308 / inTopicNo.3)  Re[2]: C++/CLIでメモリ解放が遅い
□投稿者/ C.John (1回)-(2008/12/20(Sat) 21:11:39)
メモリ使用量が何を見ているのかも気になりますね
Private BytesなのかGen2 Heap SizeなどCLRの部分か
引用返信 編集キー/
■30311 / inTopicNo.4)  Re[2]: C++/CLIでメモリ解放が遅い
□投稿者/ あしび (2回)-(2008/12/20(Sat) 23:17:43)
No30306 (Azulean さん) に返信

ソースの内容を付けられなくてすみません。

> どんなクラスかは分かりませんが、マネージオブジェクトはすぐにメモリ領域を解放しません。
> その前提を踏まえても、おかしいと思える動作だったということでしょうか?

説明が前後してしまうかもしれませんが、メモリ状況の把握は、System.Diagnostics.Process.GetCurrentProcess().PagedMemorySize64で値を取得して確認しています。
GC.Collect()すると、ガベージコレクトされる対象となり、ある程度メモリが溜まったら解放されるという認識でいます。
実際、メモリ圧迫していないクラスでは、PagedMemorySize64で取得した値がfor分を何回回しても一定の値で保たれています。
しかし、今回のクラスではメモリが延々増え続け、最後にはプログラムが落ちてしまうという状況になってしまいます。

> できれば、簡単なサンプルを示して頂ければ、どこに問題があるか指摘ができる可能性があります。
> サンプルができなくとも、そのクラスでどんなメモリ確保・リソース確保が行われているか、デストラクタ・ファイナライザで何かやってるかといった情報が寄せられれば、回答しやすいかと思います。
> # 例外でDisposeされていない(C++/CLIならdeleteか)ということはないと思いますが、念のため。

問題のクラスは、コンストラクタでログ出力の設定を行っています。
ログ出力機構はネイティブなC++で作成されたものを使用しています。
Singletonパターンを利用したログ出力機構です。
クラスライブラリと同じフォルダ内に指定したログ設定用ファイルがない場合はログを吐き出さないような仕組みしています。
ちょっと複雑な機構なので、概略で申し訳ないですが下記に記載させていただきます。
下記のようにして、DisposeにもGCにも対応可能にしています。
なお、後始末関係も二重の後始末が起きないようにケアしてあります。


// マネージドクラス
public ref class Klass
{
public:
	//コンストラクタ
	Klass()
	{
		//ログ出力の準備をする
		try
		{
			ReadSettingFile readSettingFile;
			readSettingFile.readSettingFile();
		}
		catch(MyException&)
		{
			//いろいろ後始末
			//今回は必ずここに入る
		}
		finally
		{
			//いろいろ後始末
		}
		
		...
	}
	
	//デストラクタ
	~Klass()
	{
		//ファイナライザを呼び出す
		!Klass()
	}
	
	// ファイナライザ
	!Klass()
	{
		//ログ出力の後始末をする
		...
	}
};

// 以下ネイティブC++クラス
// iniファイルから設定を読み込むクラス
class ReadIniFile
{
public:
	void readIniFile()
	{
		// 指定のiniファイルが開けなかった場合例外を投げる
		// 今回は必ずthrowされる
		ifstream fs;
		fs.open(指定のファイルパス);
		if(!fs)
			throw MyExeption("コメント");
			
		...
	}
};

// ログの設定ファイルを読み込むクラス
class ReadSettingFile
{
public:
	void readSettingFile()
	{
		ReadIniFile readIniFile;
	
		try
		{
			readIniFile.readIniFile();
		}
		catch(MyExeption& ex)
		{
			//コメントを追加して自分自身をthrowします
			ex.addInfo("コメント");
		}
		
		...
	}
};


ご指摘、ご教授いただけるとありがたいです。



引用返信 編集キー/
■30312 / inTopicNo.5)  Re[3]: C++/CLIでメモリ解放が遅い
□投稿者/ あしび (3回)-(2008/12/20(Sat) 23:20:37)
No30308 (C.John さん) に返信
> メモリ使用量が何を見ているのかも気になりますね
> Private BytesなのかGen2 Heap SizeなどCLRの部分か

メモリ使用量はSystem.Diagnostics.Process.GetCurrentProcess().PagedMemorySize64でチェックしています。
実は、外注先からこれでチェックをしているという連絡をもらって調査しているため、これの詳細についてはきちんと理解できているとは言い難い状況です。
ご指摘、ご教授よろしくお願いします。


引用返信 編集キー/
■30316 / inTopicNo.6)  Re[3]: C++/CLIでメモリ解放が遅い
□投稿者/ Azulean (247回)-(2008/12/21(Sun) 00:05:21)
> GC.Collect()すると、ガベージコレクトされる対象となり、ある程度メモリが溜まったら解放されるという認識でいます。
GC.Collectはガベージコレクションを強制的に実行するメソッドです。
ガベージコレクションの対象を指定だけしたり、検出だけさせるものではありません。

> Singletonパターンを利用したログ出力機構です。
サンプルはSingletonっぽくないような気がしましたが…大丈夫かな?

> 下記のようにして、DisposeにもGCにも対応可能にしています。
> なお、後始末関係も二重の後始末が起きないようにケアしてあります。
そのメモリ解放ができていないときはファイナライザ(!で始まる方)で解放しているとかありませんか?
すぐにアンマネージリソースを解放したい場合は、例外が起きてもDispose(デストラクタ、~で始まる方)が走るようにちゃんとロジックを組むべきです。

# サンプルではその部分が確認できなかったため、本番のコードではちゃんとできているのであれば良いのですが...

> メモリ使用量はSystem.Diagnostics.Process.GetCurrentProcess().PagedMemorySize64でチェックしています。
「メモリ使用量」の定義次第ですが、恐らく、PagedMemorySize64プロパティじゃカバーし切れていないのではないかと思います。



今回の原因がどうであれ、繰り返しの多くなるループの中ではnew(gcnew)を避けた方が無難です。
引用返信 編集キー/
■30319 / inTopicNo.7)  Re[4]: C++/CLIでメモリ解放が遅い
□投稿者/ あしび (4回)-(2008/12/21(Sun) 01:37:33)
No30316 (Azulean さん) に返信
>>Singletonパターンを利用したログ出力機構です。
> サンプルはSingletonっぽくないような気がしましたが…大丈夫かな?

すみません、サンプルは大分はしょってあるのでわかりにくいですね・・・。
ここはネイティブ環境で確認済みなので大丈夫です。

> そのメモリ解放ができていないときはファイナライザ(!で始まる方)で解放しているとかありませんか?
> すぐにアンマネージリソースを解放したい場合は、例外が起きてもDispose(デストラクタ、~で始まる方)が走るようにちゃんとロジックを組むべきです。
> # サンプルではその部分が確認できなかったため、本番のコードではちゃんとできているのであれば良いのですが...

例外でnewの部分自体がだめになるわけではないので、Disposeかファイナライザは必ず呼ばれます。
やっぱり簡略で書き過ぎでわかりにくいですね。
すみません・・・。

> 「メモリ使用量」の定義次第ですが、恐らく、PagedMemorySize64プロパティじゃカバーし切れていないのではないかと思います。
> 今回の原因がどうであれ、繰り返しの多くなるループの中ではnew(gcnew)を避けた方が無難です。

PagedMemorySize64プロパティの部分については自分もきちんと理解できていないので、もう少し定義を調べた方が良さそうですね。
手をつけるところがある可能性が出てきたので良かったです。
テストのために連続でnewを行っているようなのですが、意図をもう少し外注先とつめた方が良さそうですね。
PagedMemorySize64プロパティについてもう少し理解すればその糸口もつかめそうな気がしてきました。
どうもありがとうございます。


引用返信 編集キー/
■30324 / inTopicNo.8)  Re[5]: C++/CLIでメモリ解放が遅い
□投稿者/ Azulean (250回)-(2008/12/21(Sun) 10:39:38)
> 例外でnewの部分自体がだめになるわけではないので、Disposeかファイナライザは必ず呼ばれます。
「Dispose(デストラクタ)が必ず呼ばれる」ならOKですが、「Dispose(デストラクタ)が呼ばれなくても、ファイナライザが呼ばれるから大丈夫」ならNGです。

ファイナライザが動くのはガベージコレクションが動いた後ということで、いつになるか分かりません。
forループを抜けても、そのメソッドを抜けても、ファイナライザが呼ばれない可能性はあります。
そのため、Disposeが途中で例外がスローされても呼び出されるようにしておかないと、アンマネージメモリ解放が遅くなる可能性があります。

# Disposeはあくまでアンマネージリソースの解放タイミングを与えるものですので、マネージオブジェクトが巨大な場合は効果が薄いかも知れません。


ところで、サンプルではデストラクタがファイナライザのコードを呼んでいますが、意味合いとしては分けた方が良いと思います。
マネージオブジェクトの解放順は保障されていないため、コードの書き方によっては意図せぬ動作をする可能性があります。
http://msdn.microsoft.com/ja-jp/library/system.object.finalize.aspx
引用返信 編集キー/
■30325 / inTopicNo.9)  Re[6]: C++/CLIでメモリ解放が遅い
□投稿者/ あしび (5回)-(2008/12/21(Sun) 11:18:59)
No30324 (Azulean さん) に返信

> 「Dispose(デストラクタ)が必ず呼ばれる」ならOKですが、「Dispose(デストラクタ)が呼ばれなくても、ファイナライザが呼ばれるから大丈夫」ならNGです。

両方に同じ解放内容を定義してあるので、タイミングが選べなくても(いつかは)必ずアンマネージリソースが解放されるという意味でした。
(多重解放にはならないようにしてあります。)
ただ、C#から見ればDisposeを呼ぶのが正しいでしょうし、C++/CLIでは解放したいタイミングでdeleteすべきです。
テスト環境では必ず呼ばれるようにしてテストしています。

> # Disposeはあくまでアンマネージリソースの解放タイミングを与えるものですので、マネージオブジェクトが巨大な場合は効果が薄いかも知れません。

上記のことをふまえると、ご意見の通りのような気がします。
マネージオブジェクトの解放の仕方がC++/CLIではDllImportを用いた場合やマネージドC++の場合とで違うということかもしれません。
(もしくはメモリ展開状態が違うというか・・・)
PagedMemorySize64プロパティも含めて、C++/CLIでメモリ状態がどうなるのかもっとよく理解すべきですね。

> ところで、サンプルではデストラクタがファイナライザのコードを呼んでいますが、意味合いとしては分けた方が良いと思います。
> マネージオブジェクトの解放順は保障されていないため、コードの書き方によっては意図せぬ動作をする可能性があります。
> http://msdn.microsoft.com/ja-jp/library/system.object.finalize.aspx

参考ページありがとうございます。
唯一出版されているC++/CLI本にこうするといいよ、と記載されていたのを参考にしたのですが(笑)
鵜呑みにしていて、まだまだ理解が足りていないような気がしてきました。
自分の状況に合っているのかもう一度確認したいと思います。

どうやら、同じような現象に陥った方もいらっしゃないようですし、自分のメモリ管理に対する知識が足りていないということがわかりました。
C++/CLIのメモリ管理をもっと調べる必要がありそうですね。
一旦閉じさせていただきます。
何か結果がわかったら追加記載させてください。

とても勉強になりました。
それから、できることがありそうに思えて、少し気が楽になりました。
どうもありがとうございました。


解決済み
引用返信 編集キー/
■30328 / inTopicNo.10)  Re[7]: C++/CLIでメモリ解放が遅い
□投稿者/ Azulean (251回)-(2008/12/21(Sun) 16:11:46)
> マネージオブジェクトの解放の仕方がC++/CLIではDllImportを用いた場合やマネージドC++の場合とで違うということかもしれません。
> (もしくはメモリ展開状態が違うというか・・・)
DllImportの先のコードは全てアンマネージコード(ネイティブ)ですよね?(/clrオプションなどがついていない)
deleteを実行した時点でその場でメモりは解放されるかもしれません。

C++/CLIのdeleteは見た目こそ同じですが、マネージオブジェクトに対して実行した場合はDisposeを呼び出したのと同等の扱いを受けるだけで、マネージオブジェクトが使用していたメモリ領域は、すぐには解放されません。(例:メンバー変数にある巨大な配列(array<T>^)等)
ガベージコレクションが行われるタイミングで解放されるかもしれません。

VC2003で作成したものは、.NET Framework 1.1で動くものですので、詳細には語れません。申し訳ないです。
解決済み
引用返信 編集キー/
■30547 / inTopicNo.11)  Re[8]: C++/CLIでメモリ解放が遅い
□投稿者/ あしび (6回)-(2008/12/26(Fri) 02:21:18)
No30328 (Azulean さん) に返信
その後を報告させていただきます。

結局、メモリ解放が遅い確実な理由は突き止められませんでした。
ただ、VC++2003でコンパイルしたマネージコードとC++/CLIでコンパイルしたマネージコード(含んでいるアンマネージコードは同じもの)を逆アセンブリしてみたところ、アンマネージで記述されている例外処理の部分に違いあることがわかりました。
VC++2003でコンパイルしたものは、部分的にアンマネージコードをマネージコードに置き換えてありましたが、C++/CLIはそのままアンマネージコードでした。
(C++/CLIの方がソースコードに忠実ということですね)
DllImportで読み込んでいるものは、アンマネージな部分はマネージモジュールの中には全く含まれていませんでした。

これらのことから、アンマネージの解放はかなり負担が大きいのだと思われます。
そしておそらく、オブジェクトが大きいのでオーバーヘッドが大きくなっているのではないかと思われます。

根本解決はできませんでしたが、実運用上、このような無茶な生成/破棄を行うことはないため、このままにする方向で解決しました。
また、実使用においては、おかげさまでかなり勉強になりました。
C++/CLI、とっても便利ですけど、オーバーヘッドはよく気をつけないといけないですね。

アドバイスどうもありがとうございました。


引用返信 編集キー/
■30549 / inTopicNo.12)  Re[9]: C++/CLIでメモリ解放が遅い
□投稿者/ Azulean (259回)-(2008/12/26(Fri) 07:49:31)
> これらのことから、アンマネージの解放はかなり負担が大きいのだと思われます。
> そしておそらく、オブジェクトが大きいのでオーバーヘッドが大きくなっているのではないかと思われます。
その推論は何か怖い。
「アンマネージの解放」が何を指し示すのか分かりませんが、「オブジェクトが大きいのでオーバーヘッドが大きくなっているのではないか」は違う気がする。
GC.Collectをすればすぐに解放されて終わるのであれば、オーバーヘッドが大きいわけじゃないですよね?

> C++/CLI、とっても便利ですけど、オーバーヘッドはよく気をつけないといけないですね。
オーバーヘッドはともかく、アンマネージ・マネージの両方に詳しくないとハマることがあることから、難易度は高いかも知れません。
ただ、過去の資産を生かすという要求は少なくないため、必要に迫られることはよくあります。
引用返信 編集キー/


トピック内ページ移動 / << 0 >>

このトピックに書きこむ

過去ログには書き込み不可

管理者用

- Child Tree -