.net - 我如何知道给定类型的 .net 数组可以分配的实际最大元素数?

标签 .net arrays 64-bit large-object-heap

我知道.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。我现在只需要最高的最大值。

最佳答案

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

his own answer AZ 解释说,最大数组大小限制为小于 2GB 上限,经过一些尝试和错误(或其他方法?)发现以下结果(摘要):

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

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

数组的数据布局

了解为什么 CLR 不允许 2GB / IntPtr.Size我们需要知道数组的结构。一个很好的起点是 SO article ,但不幸的是,有些信息似乎是错误的,或者至少是不完整的。这个in-depth article on how the .NET CLR creates runtime objects事实证明是无价的,还有这个Arrays Undocumented关于 CodeProject 的文章。

根据这些文章中的所有信息,32 位系统中的数组布局可归结为以下:

Single dimension, built-in type
SSSSTTTTLLLL[...data...]0000
^ sync block
    ^ type handle
        ^ length array
                        ^ NULL 

Each part is one system DWORD in size. On 64 bit windows, this looks as follows:

Single dimension, built-in type
SSSSSSSSTTTTTTTTLLLLLLLL[...data...]00000000
^ sync block
        ^ type handle
                ^ length array
                                    ^ NULL 

The layout looks slightly different when it's an array of objects (i.e., strings, class instances). As you can see, the type handle to the object in the array is added.

Single dimension, built-in type
SSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
^ sync block
        ^ type handle
                ^ length array
                        ^ type handle array element type
                                            ^ NULL 

Looking further, we find that a built-in type, or actually, any struct type, gets its own specific type handler (all uint share the same, but an int has a different type handler for the array then a uint or byte). All arrays of object share the same type handler, but have an extra field that points to the type handler of the objects.

A note on struct types: padding may not always be applied, which may make it hard to predict the actual size of a struct.

Still not 56 bytes...

To count towards the 56 bytes of the AZ's answer, I have to make a few assumptions. I assume that:

  1. the syncblock and type handle count towards the size of an object;
  2. the variable holding the array reference (object pointer) counts towards the size of an object;
  3. the array's null terminator counts towards the size of an object.

A syncblock is placed before the address the variable points at, which makes it look like it's not part of the object. But in fact, I believe it is and it counts towards the internal 2GB limit. Adding all these, we get, for 64 bit systems:

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

还没有56。也许有人可以在调试时使用 Memory View 查看一下数组的布局在 64 位 Windows 下的样子。

我的猜测是这样的(选择、混合和匹配):

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

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

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

      XXXXXXXXXXXXXXXXXXXXXXXXSSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
    

    哪里XXX..代表跳过的字节。

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

结论

从我在这项小研究中收集到的所有信息来看,我认为 Overhead + Aligning - Objectref是最有可能、最恰当的结论。然而,“真正的”CLR 专家也许能够对这个特殊的主题提供一些额外的启发。

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

感谢您提供了一个具有挑战性的主题,我一路上学到了一些东西。也许有些人发现这个新答案与问题更相关时可以取消投票(我最初误解了这个问题,并对这可能造成的困惑表示歉意)。

关于.net - 我如何知道给定类型的 .net 数组可以分配的实际最大元素数?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1840481/

相关文章:

c# - 使用 String[] 而不是 List<String> 有什么好处?

c# - 如何使用反射调用带有 ref/out 参数的方法

c# - 规范模式与扩展方法?

.net - 为什么 C++/CLI 编译器不会针对过时的属性调用生成警告?

python - 在 matlab 和 python 中创建迭代数组

c++ - 将 32 位遗留代码移植到 64 位时如何处理不断变化的数据类型大小?

python - 在 Python 中求解 ODE 时,如何获得比 linspace 更多的变量值? (编辑)

javascript - 使用 ajax jQuery 解析 JSON

ios - 在 iPhone 5s 64 位上运行 32 位库

c++ - Visual C++ x64 带进位加法