为什么byte []的最大大小为2 GB - 57 B?

时间:2011-07-07 20:26:46

标签: .net arrays limits

在我的64位计算机上,这个C#代码有效:

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

但是这个会引发OutOfMemoryException

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

为什么?

据我所知,托管对象的最大大小为2 GB,而我正在创建的数组对象包含的内容超过了我想要的字节数。也就是说,同步块编号有4个字节(或8?),MethodTable参考有8个字节,数组大小有4个字节。这是24个字节,包括填充,所以为什么我不能分配一个2G-24字节的数组?最大尺寸是否完全 2 GB?如果是这种情况,2 GB的剩余部分用于什么?

注意:我实际上不需要分配一个包含200万字节的数组。即使我这样做,56个字节的开销可以忽略不计。我可以使用自定义BigArray<T>轻松解决限制。

3 个答案:

答案 0 :(得分:19)

您需要56个字节的开销。最大尺寸实际上是2,147,483,649-1 减去 56。这就是你的减去 57工作而减去 56的原因。

正如Jon Skeet所说:

  

然而,实际上,我没有   相信任何实现支持   如此巨大的数组。 CLR有一个   每个对象限制有点短2GB,   所以即使一个字节数组实际上也不行   有2147483648个元素。一点   实验表明,在我的盒子上,   你可以创建的最大阵列是   新字节[2147483591]。 (那就是   64位.NET CLR; Mono的版本   我已经安装了扼流圈。)

有关同一主题,请参阅此InformIT article。它提供以下代码来演示最大的大小和开销:

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,留给你7   字节短于2千兆字节。

修改

但等等,还有更多!

环顾四周并与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

先生。斯凯特接着说:

  

你可能会被原谅看   上面的数字,并认为   对象的“开销”是12   x86中的字节和x64中的24 ...但是   那不太对劲。

和此:

  
      
  • 在x86中每个对象有8个字节的“基本”开销,每个16个   x64中的对象......鉴于我们可以   在x86中存储Int32的“真实”数据   并且对象大小仍为12,   同样我们可以存储两个Int32   x64中的实际数据仍然有一个   x64的对象。

  •   
  • 分别有12个字节和24个字节的“最小”大小。在   换句话说,你不能拥有一个类型   这只是开销。请注意如何   “Empty”类占用相同的值   大小为创建实例   对象......实际上有一些   备用房间,因为CLR没有   喜欢在没有的对象上操作   数据。 (注意没有的结构   字段占用空间,即使是   局部变量。)

  •   
  • x86对象填充到4字节边界;在x64上它是8个字节   (和以前一样)

  •   

最后,Jon Skeet在另一个问题中对question I asked of him作出了回应(他回应了我向他展示的InformIT article):

  

看起来像你的文章   指的是推断开销   只是从极限,这是   愚蠢 IMO。

所以为了回答你的问题,实际的开销是 24字节 32字节的备用空间,来自我收集的内容。

答案 1 :(得分:3)

有一件事是肯定的是你不能有奇数个字节,它通常是本机字大小的倍数,在64位进程上是8字节。所以你可以在数组中添加另外7个字节。

答案 2 :(得分:2)

您实际上可以在 .net 源代码中找到明确设置和验证的此限制,它提供了一些有关这样做的原因(未来高级范围检查消除的有效实现,以及字节情况下的向后兼容性):< /p>

https://github.com/dotnet/runtime/blob/596ee7cc7fef74d40223bccacdee3e1e7f21bbef/src/coreclr/vm/gchelpers.cpp

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>