Arrays 动态分配阵列的理想增长率是多少?

Arrays 动态分配阵列的理想增长率是多少?,arrays,math,vector,arraylist,dynamic-arrays,Arrays,Math,Vector,Arraylist,Dynamic Arrays,C++有std::vector,Java有ArrayList,许多其他语言都有自己的动态分配数组形式。当一个动态数组空间不足时,它会被重新分配到一个更大的区域中,旧值会被复制到新数组中。这种阵列性能的核心问题是阵列大小的增长速度。如果你总是长得足够大以适应当前的压力,那么你每次都会重新分配。因此,将数组大小加倍或乘以1.5倍是有意义的 有理想的生长因子吗?2倍?1.5倍?所谓理想,我指的是数学上合理的,最好的平衡性能和浪费的内存。我认识到,从理论上讲,考虑到您的应用程序可能具有任何潜在的推送分布

C++有std::vector,Java有ArrayList,许多其他语言都有自己的动态分配数组形式。当一个动态数组空间不足时,它会被重新分配到一个更大的区域中,旧值会被复制到新数组中。这种阵列性能的核心问题是阵列大小的增长速度。如果你总是长得足够大以适应当前的压力,那么你每次都会重新分配。因此,将数组大小加倍或乘以1.5倍是有意义的

有理想的生长因子吗?2倍?1.5倍?所谓理想,我指的是数学上合理的,最好的平衡性能和浪费的内存。我认识到,从理论上讲,考虑到您的应用程序可能具有任何潜在的推送分布,这在某种程度上取决于应用程序。但我很想知道是否有一个值“通常”是最好的,或者在某些严格的约束条件下被认为是最好的


我听说某个地方有一篇关于这方面的论文,但我一直找不到。

这将完全取决于用例。您是否更关心复制数据(和重新分配阵列)所浪费的时间,还是额外的内存?阵列将持续多长时间?如果它不会持续很长时间,使用更大的缓冲区可能是个好主意——惩罚是短期的。如果它将继续存在(例如,在Java中,进入越来越老的一代),这显然是一个更大的惩罚

没有所谓的“理想生长因子”,它不仅在理论上依赖于应用,而且绝对依赖于应用

2是一个非常常见的增长因子-我很确定这就是.NET中的
ArrayList
List
所使用的ArrayList使用1.5


编辑:正如Erich指出的,.NET中的字典使用“将大小加倍,然后增加到下一个素数”,这样散列值可以在存储桶之间合理分配。(我确信我最近看到的文档表明,素数实际上并不适合分发散列桶,但这是另一个答案的论据。)

我同意Jon Skeet的观点,即使是我的theorycrafter朋友也坚持认为,当将因子设置为2x时,可以证明这是O(1)


在每台机器上,cpu时间和内存之间的比率是不同的,因此该系数也会有同样大的变化。如果您的机器具有千兆字节的ram,并且CPU速度较慢,那么将元素复制到新阵列的成本要比在速度较快的机器上高出很多,而速度较快的机器可能内存较少。这是一个理论上可以回答的问题,对于一个统一的计算机,在真实的场景中,它根本没有帮助你。

< P>我记得多年前阅读为什么1.5比两个更好,至少适用于C++(这可能不适用于托管语言,在那里运行时系统可以随意重新定位对象)。 理由是:

  • 假设您从一个16字节的分配开始
  • 当需要更多时,分配32个字节,然后释放16个字节。这会在内存中留下一个16字节的漏洞
  • 如果需要更多,则分配64个字节,释放32个字节。这会留下一个48字节的洞(如果16和32相邻)
  • 如果需要更多,则分配128个字节,释放64个字节。这会留下一个112字节的洞(假设之前的所有分配都是相邻的)
  • 等等
  • 我们的想法是,通过2倍的扩展,在时间点上产生的漏洞永远不会大到足以重新用于下一次分配。使用1.5倍的分配,我们有以下内容:

  • 从16字节开始
  • 当您需要更多时,分配24个字节,然后释放16个字节,留下一个16字节的洞
  • 当您需要更多时,分配36个字节,然后释放24个字节,留下一个40字节的洞
  • 当您需要更多时,分配54个字节,然后释放36个字节,留下76个字节的洞
  • 当您需要更多时,分配81个字节,然后释放54个字节,留下130个字节的洞
  • 需要更多时,使用130字节孔中的122字节(向上舍入)

  • 这要看情况而定。有些人通过分析常见的用例来找到最佳的数量


    我以前见过使用1.5x2.0x phi x和2的幂。

    回答这样的问题时,一种方法是“作弊”,看看流行的库会做什么,假设广泛使用的库至少不会做可怕的事情

    因此,快速检查一下,Ruby(1.9.1-p129)在附加到数组时似乎使用1.5x,Python(2.6.2)使用1.125x加上常量(in):

    /*这与列表大小成比例的超额分配,腾出空间
    *为了进一步的增长。这种过度分配是温和的,但也很严重
    *足以在长期内产生线性时间摊销行为
    *存在性能不佳的附件时的附件序列()
    *系统realloc()。
    *其生长模式为:0,4,8,16,25,35,46,58,72,88。。。
    */
    新分配=(新闻大小>>3)+(新闻大小<9?3:6);
    /*检查整数溢出*/
    如果(新分配>PY大小\U最大值-新闻大小){
    PyErr_nomery();
    返回-1;
    }否则{
    新分配+=新闻大小;
    }
    

    newsize
    以上是数组中的元素数。请注意,
    newsize
    被添加到
    new\u allocated
    ,因此带有位移位和三元运算符的表达式实际上只是计算超额分配。

    如果您对数组长度有一个分布,并且您有一个实用函数,表示您多么喜欢浪费空间而不是浪费时间,然后,您可以选择最佳的调整大小(和初始大小)策略

    之所以使用简单常数倍数,显然是因为每次追加都有固定的摊销时间。但这并不意味着你不能对小尺码使用不同(更大)的比例

    在Scala中,您可以覆盖stan的loadFactor
    /* This over-allocates proportional to the list size, making room
     * for additional growth.  The over-allocation is mild, but is
     * enough to give linear-time amortized behavior over a long
     * sequence of appends() in the presence of a poorly-performing
     * system realloc().
     * The growth pattern is:  0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ...
     */
    new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
    
    /* check for integer overflow */
    if (new_allocated > PY_SIZE_MAX - newsize) {
        PyErr_NoMemory();
        return -1;
    } else {
        new_allocated += newsize;
    }
    
    T*x^n <= T + T*x + T*x^2 + ... + T*x^(n-2)
    
    x^n <= 1 + x + x^2 + ... + x^(n-2)
    
    x^3 <= 1 + x 
    
    n  maximum-x (roughly)
    
    3  1.3
    
    4  1.4
    
    5  1.53
    
    6  1.57
    
    7  1.59
    
    22 1.61