代码之家  ›  专栏  ›  技术社区  ›  Anzurio

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

  •  2
  • Anzurio  · 技术社区  · 15 年前

    我知道.NET中的所有数组都限制在2GB,在这个前提下,我尽量不在数组中分配更多的n=(2^31)-1)/8倍。然而,元素的数量似乎仍然是无效的。任何人都知道如何在运行时确定给定sizeof(t)的最大元素数?

    我知道,任何接近这个数字的数量都只是很多元素,但是,就所有的意图和目的而言,让我们说我需要它。

    注意:我在一个64位的环境中,有一个目标平台用于我的anycpu应用程序,并且在RAM中至少有3100 MB的空闲空间。

    更新: 谢谢你们的贡献,很抱歉我这么安静。给您带来不便,我深表歉意。我无法重新表述我的问题,但我可以补充一点,我想要的是解决如下问题:

    template <class T>
    array<T>^ allocateAnUsableArrayWithTheMostElementsPossible(){
        return gcnew array<T>( ... );
    }
    

    我自己的答案是 有点 满意但不够好。此外,我还没有在另一台机器上测试它(有点难找到另一台超过4GB的机器)。此外,我一直在自己做一些研究,似乎没有廉价的方法来计算这个运行时。不管怎样,这只是一个优点,没有一个用户 我正在努力实现的目标 可以使用我正在尝试实现的功能,但没有能力。

    所以,换句话说,我只想理解为什么一个数组的最大元素数加起来不到2GB 其他条件相同 .我现在只需要最高限额。

    5 回复  |  直到 15 年前
        1
  •  2
  •   Community CDub    8 年前

    更新: 答案完全重写。原始答案包含通过分而治之找到任何系统上最大可能可寻址数组的方法,如果您感兴趣,请参阅此答案的历史记录。新的答案试图解释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 关于代码项目的文章。

    根据本文中的所有信息,可以得出32位系统中数组的以下布局:

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

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

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

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

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

    进一步看,我们发现一个内置类型,或者实际上是任何结构类型,都会得到它自己的特定类型处理程序(所有 uint 分享同样的东西,但是 int 对数组具有不同的类型处理程序,然后 无符号整型 byte )所有对象数组都共享相同的类型处理程序,但有一个额外的字段指向对象的类型处理程序。

    关于结构类型的注意事项:可能不总是应用填充,这可能使预测结构的实际大小变得困难。

    仍然不是56字节…

    为了计算az答案的56字节,我必须做一些假设。我假设:

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

    同步块放在变量指向的地址之前,这使得它 看起来它不是物体的一部分 .但事实上,我相信这是事实,而且它也接近内部2GB的限制。我们得到,对于64位系统,添加所有这些内容:

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

    不是 五十六 然而。也许有人可以在调试期间查看内存视图,以检查64位窗口下数组的布局。

    我猜是沿着这条线(挑选、混合和匹配)的东西:

    • 2GB永远不可能实现,因为这是进入下一段的一个字节。最大的块应该是 2GB - sizeof(int) . 但这很愚蠢,因为mem索引应该从零开始,而不是从一开始;
    • 任何大于85016字节的对象都将放在LOH(大对象堆)上。这可能包括一个额外的指针,甚至一个包含LOH信息的16字节结构。也许这算是达到了极限;
    • 对齐:假设ObjectRef不计数(它仍然在另一个mem段中),则总间隙为32字节。很可能系统更喜欢32字节的边界。重新查看内存布局。如果起始点需要在32字节边界上,并且在此之前需要为同步块留出空间,则同步块将结束在前32字节块的末尾。像这样:

      XXXXXXXXXXXXXXXXXXXXXXXXSSSSSSSSTTTTTTTTLLLLLLLLtttttttt[...data...]00000000
      

      在哪里? XXX.. 表示跳过的字节。

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

    结论

    从我在这项小研究中收集到的资料来看,我认为 Overhead + Aligning - Objectref 是最有可能和最合适的结论。然而,一个“真正的”clr大师可能会对这个特殊的主题有一些额外的了解。

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

    谢谢你的挑战性主题,在我的过程中学到了一些东西。也许有些人在发现这个新的答案与问题更相关(我最初误解了这一点,并为这可能导致的混乱道歉)时会放弃投票。

        2
  •  2
  •   Abel    15 年前

    所以,我运行了一个l i'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 .

        3
  •  0
  •   Ruben Bartelink    15 年前

    除非您[编译了anycpu或x64]并在x64进程[在x64计算机上]中运行,否则您的进程空间限制为2GB。这就是你可能遇到的。计算这个过程中的净空不是一门精确的科学。

    (NITPICKERS角:有A/3GB开关和其他边缘箱的堆叠,影响这一点。此外,进程还需要分配虚拟或物理空间。关键是,目前,大多数人在每个进程中遇到操作系统的次数比任何.NET限制都要多)

        4
  •  0
  •   mxg    8 年前

    更新: 我的 other answer contains the solution 但是我把这个留给了mono,c,clr链接和讨论线程的信息

    数组的最大大小受整数大小的限制,而不是它所包含对象的大小。但是.NET中的任何对象都限制为2GB,句点(感谢Luke和See Edit),这限制了数组的总大小,即单个元素加上一点开销的总和。

    它阻塞您的系统的原因是,嗯,系统的可用内存。而Win32进程的系统只允许使用2GB内存,其中的程序和clr甚至在启动数组之前就已经使用了相当多的内存。其余的可以用于阵列:

    int alot = 640000000;
    byte[] xxx = new byte[1U << 31 - alot];
    

    这取决于您的CLR是如何配置的,不管您是否耗尽了内存。例如,在ASP.NET下,默认情况下,您被绑定到计算机总可用内存的60%。

    编辑: This answer to a related post 深入研究了64位的主题和问题。在64位系统上是可能的,但只能使用解决方法。它指向 this excellent blog post on the subject 这说明 BigArray<T> .

    注1:其他CLR,即Mono,只允许大于2GB的对象。

    注2:不是语言限制了你。这在C中编译得很好,但是试着对一台不加处理的机器进行处理是一个相当前卫的想法(坦率地说,数组类中保存长度的字段是 int 这意味着这将 总是 在32位上抛出,但不一定,但极有可能在任何64位实现上抛出):

    int[] xxx = new int[0xFFFFFFFFFFFFFFFF];  // 2^64-1
    
        5
  •  -1
  •   x0n    15 年前

    您还需要向每个sizeof(t)添加指针大小(system.intptr.size),以说明指向任何给定数组元素中对象的指针。

    推荐文章