30

在我的 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 个字节的开销也可以忽略不计。而且我可以使用 custom 轻松解决限制BigArray<T>

4

3 回答 3

20

您需要 56 字节的开销。最大尺寸实际上是 2,147,483,649-1减去56。这就是为什么您的57 有效而56 无效的原因。

正如Jon Skeet所说:

但是,实际上,我不相信任何实现都支持如此庞大的数组。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,剩下 7 个字节,不足 2 GB。

编辑:

但是等等,还有更多!

环顾四周并与 Jon Skeet 交谈,他指给我看他写的一篇关于记忆和字符串的文章。在那篇文章中,他提供了一张尺寸表:

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 中的两个 Int32 真实数据,仍然有一个 x64 对象。

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

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

最后 Jon Skeet 回答了我在另一个问题中向他提出的问题,他说(作为对我向他展示的InformIT 文章的回应):

看起来您所指的文章只是从限制中推断出开销,这是 愚蠢的IMO。

因此,要回答您的问题,根据我的收集,实际开销是24 字节32 字节的备用空间。

于 2011-07-07T20:47:19.447 回答
3

可以肯定的是,您不能拥有奇数个字节,它通常是本机字长的倍数,在 64 位进程上是 8 字节。因此,您可以向数组添加另外 7 个字节。

于 2011-07-07T20:37:44.830 回答
2

您实际上可以在 .net 源代码中找到明确设置和验证的此限制,它提供了一些关于为什么这样做的见解(高级范围检查消除的有效实施):

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

inline SIZE_T MaxArrayLength()
{
    // Impose limits on maximum array length to prevent corner case integer overflow bugs
    // Keep in sync with Array.MaxArrayLength in BCL.
    return 0X7FFFFFC7;
}
...

if ((SIZE_T)cElements > MaxArrayLength())
    ThrowOutOfMemoryDimensionsExceeded();
于 2021-02-07T13:55:36.803 回答