30

私の 64 ビット マシンでは、次の C# コードが機能します。

new byte[2L * 1024 * 1024 * 1024 - 57]

しかし、これは次をスローしOutOfMemoryExceptionます:

new byte[2L * 1024 * 1024 * 1024 - 56]

なんで?

管理対象オブジェクトの最大サイズは 2 GB であり、作成中の配列オブジェクトには必要以上のバイト数が含まれていることを理解しています。つまり、syncblock 番号に 4 バイト (または 8?)、MethodTable 参照に 8 バイト、配列のサイズに 4 バイトがあります。これはパディングを含めて 24 バイトなので、なぜ 2G - 24 バイトの配列を割り当てることができないのでしょうか? 最大サイズは本当に正確に2 GB ですか? その場合、残りの 2 GB は何に使用されますか?

注: 実際には、200 万バイトの配列を割り当てる必要はありません。仮にそうしたとしても、56 バイトのオーバーヘッドは無視できます。そして、 custom を使用して制限を簡単に回避できましたBigArray<T>

4

3 に答える 3

20

56 バイトのオーバーヘッドが必要です。実際には、2,147,483,649-1から最大サイズの 56 を引いた値です。そのため、マイナス57 は機能し、マイナス56 は機能しません。

ジョン・スキートがここで言っているように:

ただし、実際には、このような巨大な配列をサポートする実装はないと思います。CLR には 2GB に少し足りないオブジェクトごとの制限があるため、バイト配列でさえ実際には 2147483648 要素を持つことはできません。少し実験したところ、私のボックスでは、作成できる最大の配列は new byte[2147483591] であることがわかりました。(これは 64 ビットの .NET CLR 上にあります。私がインストールした Mono のバージョンは、その上にチョークをインストールしています。)

同じ主題に関するこのInformIT の記事も参照してください。最大サイズとオーバーヘッドを示す次のコードを提供します。

class Program
{
  static void Main(string[] args)
  {
    AllocateMaxSize<byte>();
    AllocateMaxSize<short>();
    AllocateMaxSize<int>();
    AllocateMaxSize<long>();
    AllocateMaxSize<object>();
  }

  const long twogigLimit = ((long)2 * 1024 * 1024 * 1024) - 1;
  static void AllocateMaxSize<T>()
  {
    int twogig = (int)twogigLimit;
    int num;
    Type tt = typeof(T);
    if (tt.IsValueType)
    {
      num = twogig / Marshal.SizeOf(typeof(T));
    }
    else
    {
      num = twogig / IntPtr.Size;
    }

    T[] buff;
    bool success = false;
    do
    {
      try
      {
        buff = new T[num];
        success = true;
      }
      catch (OutOfMemoryException)
      {
        --num;
      }
    } while (!success);
    Console.WriteLine("Maximum size of {0}[] is {1:N0} items.", typeof(T).ToString(), num);
  }
}

最後に、この記事には次のように書かれています。

計算すると、配列を割り当てるためのオーバーヘッドが 56 バイトであることがわかります。オブジェクトのサイズが原因で、最後にいくつかのバイトが残っています。たとえば、268,435,448 個の 64 ビット数値は 2,147,483,584 バイトを占有します。56 バイトの配列オーバーヘッドを追加すると、2,147,483,640 になり、2 ギガバイトに 7 バイト不足することになります。

編集:

しかし、待ってください。

Jon Skeet を見回して話していると、彼はOf memory and stringsに書いた記事を教えてくれました。その記事で、彼はサイズの表を提供しています。

Type            x86 size            x64 size
object          12                  24
object[]        16 + length * 4     32 + length * 8
int[]           12 + length * 4     28 + length * 4
byte[]          12 + length         24 + length
string          14 + length * 2     26 + length * 2

スキート氏は次のように続けます。

上記の数値を見て、オブジェクトの「オーバーヘッド」が x86 では 12 バイト、x64 では 24 バイトであると考えるのは許されるかもしれませんが、それは正しくありません。

この:

  • x86 ではオブジェクトごとに 8 バイト、x64 ではオブジェクトごとに 16 バイトの「ベース」オーバーヘッドがあります... x86 で「実際の」データの Int32 を格納でき、オブジェクト サイズは 12 のままであり、同様に格納できます。 x64 の実データの 2 つの Int32 であり、x64 のオブジェクトがまだあります。

  • それぞれ 12 バイトと 24 バイトの「最小」サイズがあります。つまり、オーバーヘッドだけの型を持つことはできません。「Empty」クラスが Object のインスタンスを作成するのと同じサイズを占めることに注意してください... CLR はデータのないオブジェクトを操作することを好まないため、事実上、いくらかの余裕があります。(ローカル変数であっても、フィールドのない構造体もスペースを占有することに注意してください。)

  • x86 オブジェクトは 4 バイト境界までパディングされます。x64 では 8 バイトです (以前と同様)。

そして最後に、Jon Skeet は別の質問で私が彼に尋ねた質問に答えました (私が彼に見せたInformIT の記事への回答として):

あなたが参照している記事は、制限からオーバーヘッドを推測しているように見えますが、これは ばかげたIMO です。

あなたの質問に答えるために、実際のオーバーヘッドは24 バイトで、32 バイトの空き容量があります。

于 2011-07-07T20:47:19.447 に答える
3

1つ確かなことは、奇数のバイト数を持つことはできないということです。通常、64ビットプロセスでは8バイトであるネイティブワードサイズの倍数になります。したがって、配列にさらに7バイトを追加することができます。

于 2011-07-07T20:37:44.830 に答える