2

私は、.net のすべての配列が 2 GB に制限されていることを知っています。この前提の下では、配列に n = ((2^31) - 1) / 8 double を割り当てないようにしています。それにもかかわらず、その要素数はまだ有効ではないようです。sizeof(T) を指定して、実行時に要素の最大数を決定する方法を知っている人はいますか?

その数に近づく量が何であれ、要素がたくさんあることは知っていますが、すべての意図と目的のために、それが必要だとしましょう.

注: 私は 64 ビット環境にいて、AnyCPU のアプリケーション用のターゲット プラットフォームと、RAM に少なくとも 3100 MB の空き容量があります。

更新: 皆様の貢献に感謝し、私がとても静かで申し訳ありませんでした. ご不便をおかけして申し訳ございません。私は自分の質問を言い換えることができませんでしたが、それを追加することができます.私が探しているのは、次のようなものを解決することです:

template <class T>
array<T>^ allocateAnUsableArrayWithTheMostElementsPossible(){
    return gcnew array<T>( ... );
}

私自身の回答の結果は、満足のいくものですが、十分ではありません。さらに、別のマシンでテストしていません (4 GB を超える別のマシンを見つけるのは難しいです)。その上、私は自分でいくつかの調査を行ってきましたが、実行時にこれを計算する安価な方法はないようです. とにかく、それは単なるプラスでした.私が達成しようとしているもののユーザーは、容量がなければ実装しようとしている機能を使用することを期待できません.

言い換えれば、配列の要素の最大数が 2GB ceteris paribusにならない理由を理解したいだけです。今のところ必要なのは最大値だけです。

4

5 に答える 5

2

だから、私はいくつかのハード値を見つけるためにli'lプログラムを実行しました。これが私が見つけたものです:

  • 型 T を指定すると、f(sizeof(T)) = N + d

    • ここで、f は T の配列の実際の最大サイズです。
    • N は理論上の最大サイズです。つまり、Int32::MaxValue / sizeof(T) です。
    • d は N と f(x) の差です。

結果:

  • f(1) = N - 56
  • f(2) = N - 28
  • f(4) = N - 14
  • f(8) = N - 7
  • f(16) = N -3
  • f(32) = N - 1

サイズが倍増するたびに、実際のサイズと理論上のサイズの差は倍になりますが、2 の累乗ではありません。理由はありますか?

編集:タイプ要素dの量です。バイト単位でT検索するには、 を実行します。dsizeof(T) * d

于 2009-12-03T20:36:52.523 に答える
2

更新:回答は完全に書き直されました。元の回答には、分割統治によって任意のシステムで可能な最大のアドレス可能な配列を見つける方法が含まれていました。興味がある場合は、この回答の履歴を参照してください。新しい回答は、56 バイトのギャップを説明しようとしています。

自身の回答で、AZ は、最大配列サイズが 2 GB の上限よりも小さく制限されており、いくつかの試行錯誤 (または別の方法?) を使用して、次の (要約) を見つけると説明しました。

  • 型のサイズが 1、2、4、または 8 バイトの場合、占有可能な最大サイズは 2GB - 56 バイトです。
  • 型のサイズが 16 バイトの場合、最大は 2GB - 48 バイトです。
  • タイプのサイズが 32 バイトの場合、最大は 2GB - 32 バイトです。

16 バイトと 32 バイトの状況については、よくわかりません。構造体の配列または組み込み型の場合、配列の使用可能な合計サイズは異なる場合があります。1 ~ 8 バイトの型サイズを強調します (これについてもよくわかりません。結論を参照してください)。

配列のデータ レイアウト

CLR が正確な要素を許可しない理由を理解するに2GB / IntPtr.Sizeは、配列がどのように構造化されているかを知る必要があります。良い出発点はこのSO 記事ですが、残念ながら、一部の情報は間違っているか、少なくとも不完全なようです。.NET CLR がランタイム オブジェクトを作成する方法に関するこの詳細な記事と、CodeProject に関するこの文書化されていない配列の記事は非常に貴重であることが証明されました。

これらの記事のすべての情報を考慮すると、32 ビット システムの配列の次のレイアウトになります。

一次元ビルトインタイプ
SSSSTTTTLLL[...データ...]0000
^ 同期ブロック
    ^ 型ハンドル
        ^ 長さ配列
                        ^ヌル

各パーツDWORDのサイズは 1 システムです。64 ビット Windows では、これは次のようになります。

一次元ビルトインタイプ
SSSSSSSSTTTTTTTTLLLLLLLLL[...データ...]00000000
^ 同期ブロック
        ^ 型ハンドル
                ^ 長さ配列
                                    ^ヌル

オブジェクトの配列 (つまり、文字列、クラス インスタンス) の場合、レイアウトは少し異なります。ご覧のとおり、配列内のオブジェクトへの型ハンドルが追加されています。

一次元ビルトインタイプ
SSSSSSSSTTTTTTTTLLLLLLLLLtttttttt[...データ...]00000000
^ 同期ブロック
        ^ 型ハンドル
                ^ 長さ配列
                        ^ type ハンドル配列要素の型
                                            ^ヌル

さらに調べてみると、組み込み型、または実際には任意の構造体型が、独自の特定の型ハンドラーを取得することがわかります (すべてuint同じものを共有しますが、intは配列に対して異なる型ハンドラーを持ち、 auintまたはを持ちbyteます)。オブジェクトのすべての配列は同じ型ハンドラーを共有しますが、オブジェクトの型ハンドラーを指す追加のフィールドがあります。

構造体型に関する注意: パディングが常に適用されるとは限らないため、構造体の実際のサイズを予測するのが難しくなる場合があります。

まだ56バイトではありません...

AZ の回答の 56 バイトにカウントするには、いくつかの仮定を行う必要があります。私はそれを仮定します:

  1. syncblock とタイプ ハンドルは、オブジェクトのサイズにカウントされます。
  2. 配列参照 (オブジェクト ポインター) を保持する変数は、オブジェクトのサイズにカウントされます。
  3. 配列の null ターミネータは、オブジェクトのサイズにカウントされます。

変数が指し示すアドレスの前に syncblock が配置されるため、オブジェクトの一部ではないように見えます。しかし、実際にはそうであり、内部の 2GB 制限にカウントされます。これらをすべて追加すると、64 ビット システムでは次のようになります。

ObjectRef + 
Syncblock +
Typehandle +
Length +
Null pointer +
--------------
40  (5 * 8 bytes)

まだ56ではありません。おそらく、誰かがデバッグ中にメモリ ビューを見て、配列のレイアウトが 64 ビット ウィンドウでどのように見えるかを確認できます。

私の推測では、これらの線に沿ったものです (選択して、組み合わせて一致させてください):

  • 2GB は、次のセグメントへの 1 バイトであるため、決して可能ではありません。最大のブロックは2GB - sizeof(int). しかし、これはばかげています。メモリ インデックスは 1 ではなく 0 から開始する必要があるためです。

  • 85016 バイトを超えるオブジェクトは、LOH (ラージ オブジェクト ヒープ) に配置されます。これには、追加のポインター、または LOH 情報を保持する 16 バイトの構造体が含まれる場合があります。おそらく、これは制限にカウントされます。

  • 整列: objectref がカウントされないと仮定すると (別の mem セグメントにある)、合計ギャップは 32 バイトです。システムが 32 バイト境界を好む可能性は十分にあります。メモリ レイアウトを見直してください。開始点が 32 バイト境界上にある必要があり、その前に同期ブロック用のスペースが必要な場合、同期ブロックは最初の 32 バイト ブロックの終わりになります。このようなもの:

      XXXXXXXXXXXXXXXXXXXXXXXXSSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
    

    whereXXX..はスキップされたバイトを表します。

  • 多次元配列: Array.CreateInstance1 つ以上の次元で配列を動的に作成する場合、次元のサイズと下限を含む 2 つの追加の DWORD を含む単一の次元配列が作成されます (次元が 1 つしかない場合でも、下限は非ゼロとして指定されます)。これがあなたのコードに当てはまる場合、おそらくこれについて言及したであろうため、これはほとんどありそうにないと思います。しかし、合計で 56 バイトのオーバーヘッドが発生します ;)。

結論

Overhead + Aligning - Objectrefこの小さな調査で集めたすべてのことから、これが最も可能性が高く、最も適切な結論だと思います。ただし、「本物の」CLR の第一人者であれば、この独特なテーマにさらに光を当てることができるかもしれません。

これらの結論のいずれも、16 バイトまたは 32 バイトのデータ型にそれぞれ 48 バイトおよび 32 バイトのギャップがある理由を説明していません。

やりがいのあるテーマをありがとう、途中で何かを学びました。おそらく、この新しい回答が質問に関連していることがわかったときに、反対票を投じることができる人もいるでしょう(私が最初に誤解したものであり、これが原因で混乱が生じたことをお詫びします)。

于 2009-12-03T19:52:47.410 に答える
0

[anycpu または x64 でコンパイル] し、[x64 マシン上で] x64 プロセスで実行していない限り、プロセス空間は 2GB に制限されています。これは、おそらく実際に遭遇しているものです。プロセスに余裕があるかどうかを計算することは、決して正確な科学ではありません。

(ちょっとしたコーナー: /3GB スイッチと、これに影響を与える他のエッジ ケースのスタックがあります。また、プロセスには、割り当てられる仮想または物理スペースも必要です。ポイントは、現時点では、ほとんどの人がより多くの場合、.NET 制限よりもプロセスごとの OS 制限に達します)

于 2009-12-03T15:17:19.847 に答える
0

更新:の他の回答には解決策が含まれていますが、Mono、C#、CLR リンク、およびディスカッション スレッドに関する情報のためにこれを残します

配列の最大サイズは、含まれるオブジェクトのサイズではなく、整数のサイズによって制限されます。しかし、.NET のオブジェクトは 2GB に制限されています (Luke に感謝し、EDIT を参照してください)。これにより、配列の合計サイズが制限されます。これは、個々の要素の合計に少しのオーバーヘッドを加えたものです。

それがあなたのシステムを詰まらせる理由は、まあ、システムの利用可能なメモリです。また、win32 プロセスのシステムでは 2GB のメモリしか使用できず、アレイを開始する前から、プログラムと CLR が既にかなりの量を使用しています。配列に使用できる残りの部分:

int alot = 640000000;
byte[] xxx = new byte[1U << 31 - alot];

メモリが不足しているかどうかは、CLR の構成方法によって異なります。たとえば、ASP.NET では、既定で、マシンの使用可能なメモリの合計の 60% にバインドされます。

編集:関連する投稿へのこの回答は、主題と 64 ビットの問題についてもう少し深く掘り下げています。64 ビット システムでは可能ですが、回避策を使用する必要があります。これは、このテーマに関する優れたブログ投稿を示していますBigArray<T>

注 1: 他の CLR、つまり Mono は、単純に 2GB を超えるオブジェクトを許可します。

注 2: あなたを制限するのは言語ではありません。これは C# で問題なくコンパイルされますが、それをスローしないマシンを試してみるのはかなり未来的な考えです (率直に言って、長さを保持する Array クラスのフィールドは ですint。つまり、これは常に32 ビットでスローされます)。 、しかし必ずしもそうではありませんが、64ビット実装では非常に可能性が高いです):

int[] xxx = new int[0xFFFFFFFFFFFFFFFF];  // 2^64-1
于 2009-12-03T15:22:04.370 に答える
-1

また、特定の配列要素内のオブジェクトへのポインターを考慮するために、ポインター サイズ (System.IntPtr.Size) を各 sizeof(T) に追加する必要があります。

于 2009-12-03T15:18:36.483 に答える