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

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

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

配列のメモリ管理に関して

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

■92394 / inTopicNo.1)  配列のメモリ管理に関して
  
□投稿者/ パパラッチ (1回)-(2019/09/19(Thu) 21:55:25)

分類:[.NET 全般] 

複数の大きなサイズの2次元の配列を使用したいのですが、
Dim bbb(500, 1000, 1000) As Single

のように宣言すると一つの全ての配列が2 GByte以内に収まらないとエラーとなります。

一方で、

        Dim aaa(100000)(,) As Single

        For i = 0 To 1000

            ReDim aaa(i)(10000, 10000)

        Next


のように宣言すると、それぞれの2次元配列が2GByteを超えなければ
メモリの上限まで使用できるようです。

上記のコードを実行すると
i = 167のところでエラーとなります。
この時のデータ量は
【167*10000*10000*4/1000/1000/1000】
66.8 GByte
となります。


        Dim aaa(100000)(,) As Short
で試してみると
i = 330でエラーとなります。
この時のデータ量は
【330*10000*10000*2/1000/1000/1000】
66 GByte
となり、Singleの時とほぼ同じであることが分かります。

しかし、いま
32 GBのメモリを積んだWin10 64bit PCを使用しており、
66 GByteまで格納できるわけがないのですが、
なぜこのようなサイズになってもエラーにならないのでしょうか?

あと、タスクマネージャーでメモリ使用量を確認すると、
このような大きなメモリ使用量のコードを実行しても
わずかしかメモリ使用量は増えず、ほとんど変動がありません。

なぜタスクマネージャーにはメモリ使用量が反映されないのでしょうか?

引用返信 編集キー/
■92396 / inTopicNo.2)  Re[1]: 配列のメモリ管理に関して
□投稿者/ 魔界の仮面弁士 (2384回)-(2019/09/20(Fri) 11:09:18)
2019/09/20(Fri) 11:12:11 編集(投稿者)

No92394 (パパラッチ さん) に返信
> 複数の大きなサイズの2次元の配列を使用したいのですが、

巨大な配列の確保はお奨めできません。
2 次元配列は、LINQ との相性も悪いですし…。

それと、配列宣言で各次元に指定される値は、
「要素数」ではなく「添字の最大値」なのでご注意を。

※ Dim x(500) As Single なら、500 個ではなく 501 個。


> 一つの全ての配列が2 GByte以内に収まらないとエラーとなります。

スタック領域の制限ですね。

VB.NETだけでなく、VBA でも同様の事情があります。
https://docs.microsoft.com/ja-jp/office/vba/language/reference/user-interface-help/out-of-stack-space-error-28


> のように宣言すると、それぞれの2次元配列が2GByteを超えなければ
> メモリの上限まで使用できるようです。

いわゆる jagged array ですね。
配列への参照がヒープ領域に置かれるためです。


Marshal クラスの AllocHGlobal / AllocCoTaskMem メソッドはヒープを確保するので、
 Dim bbb(500, 1000, 1000) As Single
の代わりに
ptrBBB = Marshal.AllocCoTaskMem(501 * 1001 * 1001 * Marshal.SizeOf(Of Single)())
で確保することはできるかも知れません。

解放はこんな感じ。
Marshal.FreeCoTaskMem(ptrBBB)
ptrBBB = IntPtr.Zero

Single 値の読み書きは、Marshal.ReadInt32 / WriteInt32 経由となるので
VB だとちょっと手間がかかりますが、カプセル化してしまえばどうにかなるかも。
http://schima.hatenablog.com/entry/20090627/1246030121


> 32 GBのメモリを積んだWin10 64bit PCを使用しており、
> 66 GByteまで格納できるわけがないのですが、

物理メモリと仮想メモリ?


> なぜこのようなサイズになってもエラーにならないのでしょうか?

https://ufcpp.net/study/computer/MemoryManagement.html


> 上記のコードを実行すると
> i = 167のところでエラーとなります。

上限値は実行環境によりますね。

下記で実験したところ、i = 8 程度でエラーになる環境もあれば、
50000 以上でも耐えられる環境があることを確認しました。

Public Class Compiler
 Public Shared Sub Main()
  Dim aaa(100000)(,) As Single
  Dim i As Integer
  Try
   For i = 0 To aaa.GetUpperBound(0)
    ReDim aaa(i)(10000, 10000)
   Next i
  Catch
   Console.Error.WriteLine("Error")
  Finally
   Console.WriteLine("Finish: {0}", i)
  End Try
 End Sub
End Class


> あと、タスクマネージャーでメモリ使用量を確認すると、
> このような大きなメモリ使用量のコードを実行しても
> わずかしかメモリ使用量は増えず、ほとんど変動がありません。

メモリ監視をしたいなら、タスクマネージャーではなく、
パフォーマンスモニタを使った方が良いかも。
引用返信 編集キー/
■92439 / inTopicNo.3)  Re[1]: 配列のメモリ管理に関して
□投稿者/ 大谷刑部 (18回)-(2019/09/26(Thu) 09:43:36)
No92394 (パパラッチ さん) に返信
> 複数の大きなサイズの2次元の配列を使用したいのですが、
> Dim bbb(500, 1000, 1000) As Single

なぜ2次元(宣言上3次元に見えますが)配列を使いたいのでしょうか?
用途にもよりますが、メモリ限界の問題もさることながら、
基本的に多次元配列は処理が重たくなるのであまりお勧めしません。
特にForループで回したりする場合、正比例でなく、要素数が増えると累積的に遅くなります。

.Netになってから試したことはありませんが、
VB6の場合(VBAやVBScriptの場合は今も)integer(16bit)の限界値を超えるあたりからたとえ変数の宣言がLongであっても急激に遅くなります。
2次元で行、列の概念(要するにテーブルとかExcelのセルインデックスの代わり)で処理をしたいなら、
構造体配列にして、配列自体は1次元で処理した方が、ソースの可読性も上がりますし、処理的にも重くなりにくいかと。
VB6時代のtype型とほぼ同じで、古い技術ではありますが、そう馬鹿にしたもんじゃないかと。

>
> のように宣言すると一つの全ての配列が2 GByte以内に収まらないとエラーとなります。
>
> 一方で、
>
> Dim aaa(100000)(,) As Single
>
> For i = 0 To 1000
>
> ReDim aaa(i)(10000, 10000)
>
> Next
>
>
> のように宣言すると、それぞれの2次元配列が2GByteを超えなければ
> メモリの上限まで使用できるようです。
>
> 上記のコードを実行すると
> i = 167のところでエラーとなります。
> この時のデータ量は
> 【167*10000*10000*4/1000/1000/1000】
> 66.8 GByte
> となります。
>
>
> Dim aaa(100000)(,) As Short
> で試してみると
> i = 330でエラーとなります。
> この時のデータ量は
> 【330*10000*10000*2/1000/1000/1000】
> 66 GByte
> となり、Singleの時とほぼ同じであることが分かります。
>
> しかし、いま
> 32 GBのメモリを積んだWin10 64bit PCを使用しており、
> 66 GByteまで格納できるわけがないのですが、
> なぜこのようなサイズになってもエラーにならないのでしょうか?

弁さんも書いてましたが、多分仮想メモリを使ってるからかと。
OS上の設定を確認してください。
ただし、エラーにはなりませんが、仮想メモリを使う処理になった瞬間に物理メモリを使う場合より処理速度は劣化します。

>
> あと、タスクマネージャーでメモリ使用量を確認すると、
> このような大きなメモリ使用量のコードを実行しても
> わずかしかメモリ使用量は増えず、ほとんど変動がありません。
>
> なぜタスクマネージャーにはメモリ使用量が反映されないのでしょうか?
反映されないんじゃなくて、タスクマネージャの目盛では視覚的にわからないだけでは?
これも弁さんの言うとおり、リソースモニターで確認した方がいいと思います。

引用返信 編集キー/
■92443 / inTopicNo.4)  Re[2]: 配列のメモリ管理に関して
□投稿者/ 774RR (730回)-(2019/09/26(Thu) 13:25:29)
windows10 のタスクマネージャの場合、「詳細」タブを開き
名前 PID 状態とあるタイトル行で右クリック→列の選択

「メモリ」とある項目がいっぱいあるのに気づくと思うんだけど、それぞれ意味が違うっす。
標準でタスクマネージャが表示しているのは「アクティブなプライベートワーキングセット」
これの意味は「プログラムのコードやデータのうち、今この瞬間、真に物理メモリ上にある量」
(非アクティブな UWP アプリは「未使用」扱い)

仮想記憶のある OS では new/Dim/ReDim しても仮想記憶空間が取られるだけで、そのうちのほんの一部だけが物理メモリにマップされる仕様っす。タスクマネージャの表示はこの「ほんの一部」。
(真に「物理メモリ」を使っている量)

大量に new/Dim すると「コミットサイズ」が増えるっす。これの意味は「プログラムが OS に要求したメモリ量」。物理メモリにマップされていない量っすね。
引用返信 編集キー/
■92475 / inTopicNo.5)  Re[3]: 配列のメモリ管理に関して
□投稿者/ パパラッチ (2回)-(2019/09/29(Sun) 21:38:25)
ありがとうございます。

物理メモリを超えたサイズの配列を作成できるのは
ページファイルを使っているためなのですね。


https://pc-kaizen.com/win10-virtual-memory

自動的に管理する、にチェックが入っているため、
SSDのサイズなどから自動で計算された上限値が用いられているのでしょうか?
(これはどのようにして決まるか分かりますか?)


そしてそれはタスクマネージャー・パフォーマンスモニタ上では
コミットサイズとして表示されていることも分かりました。






> Marshal クラスの AllocHGlobal / AllocCoTaskMem メソッドはヒープを確保するので、
>  Dim bbb(500, 1000, 1000) As Single
> の代わりに
> ptrBBB = Marshal.AllocCoTaskMem(501 * 1001 * 1001 * Marshal.SizeOf(Of Single)())
> で確保することはできるかも知れません。

ジャグ配列の代わりに
Marshal クラスを使うメリットは何でしょうか?
1次元配列になっているので高速処理できるということですか?

引用返信 編集キー/
■92478 / inTopicNo.6)  Re[4]: 配列のメモリ管理に関して
□投稿者/ 大谷刑部 (22回)-(2019/09/30(Mon) 10:08:58)
No92475 (パパラッチ さん) に返信
> 自動的に管理する、にチェックが入っているため、
> SSDのサイズなどから自動で計算された上限値が用いられているのでしょうか?
> (これはどのようにして決まるか分かりますか?)

詳しいことはわかりませんが、

https://www.billionwallet.com/goods/windows10/win10-virtual-memory.html

↑上記の記載を見る限り、「自動」を選択している場合、OSインストールの時点で、特別な割り当てをしてない限り、
OSの入っているドライブ(通常はC)の中で仮想メモリを管理することになるので、最適化するにはカスタマイズした方がいいでしょうね。

ただし、ドライブが分かれていても、同一HDD内の別パーティションに割り当てした場合、逆効果になるケースもあるようなので、
もともと2つ以上ハードディスクが差さっているか、増設が可能な場合に有効な策でしょうけど。

それとお客さんの端末だとなかなか厳しいかもしれませんが、
処理速度も考慮すると、物理メモリを増設した方が効果があるかと。
仮想メモリの読み書きは通常よりハードへの読み書きを多くすることを意味するので、
繰り返すとハードディスクの寿命を縮めることにつながります。



引用返信 編集キー/
■92480 / inTopicNo.7)  Re[1]: 配列のメモリ管理に関して
□投稿者/ 774RR (732回)-(2019/09/30(Mon) 10:30:20)
.NET Framework 配下だと AnyCPU 設定時に x86/x64 互換性問題が起きにくいよう、
標準では 2GiB より大きい配列は使えない設定ならしい
https://docs.microsoft.com/ja-jp/dotnet/framework/configure-apps/file-schema/runtime/gcallowverylargeobjects-element
ジャグ配列にして1つの配列が 2GiB 未満に収まるようにするとこの設定が不要になるわけだ。

そもそも論として

巨大な配列を無造作にランダムアクセスすることは、キャッシュメモリとか仮想記憶機構とかと相性が最悪で、
メモリを使いまくる富豪的プログラミングで速度を向上させたかったにもかかわらず
記憶域のスラッシングが発生して性能低下しただけ、なーんてのはよくある話。
単なる技術的興味であるならいざ知らず、実用に供するなら巨大配列を使わないロジックを考えたほうが手早そう。


引用返信 編集キー/

このトピックをツリーで一括表示


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

このトピックに書きこむ