2

我知道 .net 中的所有数组都限制为 2 GB,在此前提下,我尽量不在数组中分配更多的 n = ((2^31) - 1) / 8 双倍。尽管如此,这个数量的元素似乎仍然无效。任何人都知道如何在运行时确定给定 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

更新:完全重写答案。原始答案包含通过分而治之在任何系统上找到最大可能可寻址数组的方法,如果您有兴趣,请参阅此答案的历史。新答案试图解释 56 字节的差距。

他自己的回答中,AZ 解释说最大数组大小限制为小于 2GB 上限,并且通过一些试验和错误(或其他方法?)发现以下(总结):

  • 如果类型的大小为 1、2、4 或 8 字节,则最大可占用大小为 2GB - 56 字节;
  • 如果类型的大小为 16 字节,则最大值为 2GB - 48 字节;
  • 如果类型的大小为 32 字节,则最大值为 2GB - 32 字节。

我不完全确定 16 字节和 32 字节的情况。如果数组是结构数组或内置类型,则数组的总可用大小可能会有所不同。我将强调 1-8 字节的类型大小(我也不确定,见结论)。

数组的数据布局

要理解为什么 CLR 不允许精确的2GB / IntPtr.Size元素,我们需要知道数组的结构。一个很好的起点是这篇SO article,但不幸的是,有些信息似乎是错误的,或者至少是不完整的。这篇关于 .NET CLR 如何创建运行时对象的深入文章以及CodeProject 上的这篇Arrays Undocumented文章被证明是无价的。

综合这些文章中的所有信息,可以归结为 32 位系统中的阵列的以下布局:

单维,内置型
SSSSTTTLLLL[...数据...]0000
^ 同步块
    ^ 型手柄
        ^ 长度数组
                        ^ 空

每个部分DWORD在大小上都是一个系统。在 64 位窗口上,如下所示:

单维,内置型
SSSSSSSSTTTTTTTTLLLLLLL[...数据...]00000000
^ 同步块
        ^ 型手柄
                ^ 长度数组
                                    ^ 空

当它是一个对象数组(即字符串、类实例)时,布局看起来略有不同。如您所见,添加了数组中对象的类型句柄。

单维,内置型
SSSSSSSSTTTTTTTTLLLLLLLtttttttt[...数据...]00000000
^ 同步块
        ^ 型手柄
                ^ 长度数组
                        ^ 类型句柄数组元素类型
                                            ^ 空

进一步看,我们发现一个内置类型,或者实际上,任何结构类型,都有自己特定的类型处理程序(都uint共享相同的,但是inta 对数组有不同的类型处理程序然后 auintbyte)。所有对象数组共享相同的类型处理程序,但有一个额外的字段指向对象的类型处理程序。

关于结构类型的注意事项:可能并不总是应用填充,这可能会导致难以预测结构的实际大小。

仍然不是 56 字节...

要计入 AZ 答案的 56 个字节,我必须做出一些假设。我假设:

  1. 同步块和类型句柄计入对象的大小;
  2. 保存数组引用(对象指针)的变量计入对象的大小;
  3. 数组的空终止符计入对象的大小。

一个同步块放置在变量指向的地址之前,这使它看起来不是对象的一部分。但事实上,我相信它是并且它计入内部 2GB 限制。添加所有这些,我们得到,对于 64 位系统:

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

还没到56。也许有人可以在调试期间查看内存视图,以检查数组的布局在 64 位窗口下的样子。

我的猜测是沿着这些思路(选择,混合和匹配):

  • 2GB 永远不可能,因为这是下一段的一个字节。最大的块应该是2GB - sizeof(int). 但这很愚蠢,因为 mem 索引应该从零开始,而不是从一开始;

  • 任何大于 85016 字节的对象都将放在 LOH(大对象堆)上。这可能包括一个额外的指针,甚至是一个包含 LOH 信息的 16 字节结构。也许这算作极限;

  • 对齐:假设 objectref 不计数(无论如何它在另一个 mem 段中),总间隙为 32 个字节。系统很可能更喜欢 32 字节边界。重新审视内存布局。如果起始点需要位于 32 字节边界上,并且它需要为之前的同步块留出空间,则同步块将在前 32 字节块的末尾结束。像这样的东西:

      XXXXXXXXXXXXXXXXXXXXXXXXSSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
    

    其中XXX..代表跳过的字节。

  • 多维数组:如果您Array.CreateInstance使用 1 维或更多维动态创建数组,则将使用两个额外的 DWORDS 创建单个暗数组,其中包含维度的大小和下限(即使您只有一个维度,但前提是下限被指定为非零)。我发现这不太可能,因为如果您的代码中出现这种情况,您可能会提到这一点。但这会使总开销达到 56 个字节;)。

结论

从我在这个小研究中收集到的所有信息中,我认为这Overhead + Aligning - Objectref是最有可能和最合适的结论。然而,一位“真正的”CLR 大师或许能够对这个特殊的主题提供一些额外的启示。

这些结论都没有解释为什么 16 或 32 字节数据类型分别具有 48 和 32 字节间隙。

感谢一个具有挑战性的主题,在我的过程中学到了一些东西。当一些人发现这个新答案与问题更相关时,也许有些人可以取消投票(我最初误解了这一点,并为这可能造成的混乱道歉)。

于 2009-12-03T19:52:47.410 回答
2

所以,我运行了一个 li'l 程序来找出一些硬值,这就是我发现的:

  • 给定一个类型 T,f(sizeof(T)) = N + d

    • 其中 f 是 Ts 数组的实际最大大小。
    • 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元素的数量。要d以字节为单位查找,请执行sizeof(T) * d.

于 2009-12-03T20:36:52.523 回答
0

您的进程空间限制为 2GB,除非您 [编译为 anycpu 或 x64] 并在 x64 进程中运行 [在 x64 机器上]。这可能是您实际遇到的情况。无论如何,计算你在这个过程中的净空不是一门精确的科学。

(吹毛求疵的角落:有一个 /3GB 开关和其他影响这一点的边缘情况的堆栈。此外,该进程也需要分配虚拟或物理空间。关键是目前,大多数人会更多经常遇到操作系统每个进程的限制,而不是任何 .NET 限制)

于 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 类中保存长度的字段是 an 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 回答