【问题标题】:C++ allocates abnormally large amout memory for variablesC++ 为变量分配异常大的内存
【发布时间】:2013-05-13 12:44:02
【问题描述】:

我最近知道一个整数从内存中占用 4 个字节。

首先运行这段代码,并测量内存使用情况:

int main()
{
   int *pointer;
}

  • 占用了 144KB。

然后我修改代码分配1000个整数变量

int main()
{
   int *pointer;

   for (int n=0; n < 1000; n++)
     { 
       pointer = new int ; 
     }
}

  • 然后它占用了 (168-144=) 24KB
    但假设 1000 个整数占用 (4bytes x 1000=) 3.9KB

然后我决定制作 262,144 个整数变量,它们应该消耗 1MB 内存

int main()
{
   int *pointer;

   for (int n=0; n < 262144; n++)
     { 
       pointer = new int ; 
     }
}

令人惊讶的是,现在它需要 8MB

内存使用量随着整数的数量呈指数增长。
为什么会这样?

我使用的是 Kubuntu 13.04 (amd64)
请给我一点解释。 谢谢!

注意:sizeof(integer) 返回4

【问题讨论】:

  • 为了好玩,请尝试将int 替换为long double 并进行比较。
  • 顺便说一句,int 不必是 4 个字节长。在 32 位 Intel CPU(现在过于流行)上它是,但没有什么要求它总是 4 字节。
  • 尝试每次循环打印pointer的值。
  • 改用新的 int[262144]。
  • 我认为真正的问题是,为什么在了解如何分配内存之前,您要与 CPP 混为一谈……

标签: c++ memory memory-management integer function-pointers


【解决方案1】:

单独分配的动态对象的内存不需要是连续的。事实上,由于new char[N] 的对齐要求(即与alignof(std::maxalign_t) 对齐,通常为16),标准内存分配器可能永远不会费心返回任何 16 字节的内容对齐内存。所以每个int 分配实际上消耗(至少)16 个字节。 (分配器可能需要更多内存用于内部簿记。)

当然,你应该使用std::vector&lt;int&gt;(1000000) 来合理处理一百万个动态整数。

【讨论】:

  • @Barmar:我认为事实并非如此。 OP 确实喜欢两个实验,并声称这证明了一些事情。
  • 可能,int 分配占用 32 个字节。对于带有 glibc 的 malloc,您会在分配区域 iirc 之前得到两个记账字,因此 malloc 曾经使用的最小区域是 32 个字节,如果 new 完全不同,我会感到惊讶。 2^18 ints * 32 bytes/int = 8 MB.
  • @Naveen 因为ints 数组中与ints 之间的距离为4。
  • @Naveen 因为int 需要 4 个字节的存储空间。但是使用new,您不仅可以获得int 所需的内存,还可以获得分配开销。在某个地方,需要存储分配内存的大小,以便实现知道delete [] 的大小。所以你会得到每次分配的簿记+对齐开销。如果你一次性分配Nints,你只会得到一次开销。
  • @Naveen:不要混淆 memoryobjects。这就像混淆了面粉和苹果派。你需要一个才能得到另一个,但是当它们被扔到你脸上时会感觉非常不同。
【解决方案2】:

普通分配器中的非优化分配会产生一些开销。您可以想到两个“块”:一个 INFO 块和一个 STORAGE 块。 Info 块很可能就在您的 STORAGE 块的前面。

因此,如果您分配,您的记忆中就会有类似的东西:

        Memory that is actually accessible
        vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
--------------------------------------------
|  INFO |  STORAGE                         |
--------------------------------------------
^^^^^^^^^
Some informations on the size of the "STORAGE" chunk etc.

此外,块将按照一定的粒度对齐(在 int 的情况下有点像 16 个字节)。

我会写一下这在 MSVC12 上的样子,因为我现在可以对此进行测试。

让我们看看我们的记忆。箭头表示 16 字节边界。

如果您分配一个 4 字节整数,您将在某个 16 字节边界(第二个边界之后的橙色方块)处获得 4 字节内存。 该块前面的 16 个字节(蓝色字节)用于存储附加信息。 (我将在这里跳过字节序等内容,但请记住,这会影响这种布局。)如果您在分配的内存前读取这个 16 字节块的前四个字节,您会发现分配的数量字节。

如果您现在分配第二个 4 字节整数(绿色框),它的位置将至少是 16 字节边界的 2 倍,因为 INFO 块(黄色/红色)必须放在它前面,而在右下边界。红色块又是包含字节数的块。

如您所见:如果绿色块早 16 个字节,红色块和橙色块将重叠 - 不可能。

您可以自己检查。我正在使用 MSVC 2012,这对我有用:

char * mem = new char[4096];
cout << "Number of allocated bytes for mem is: " << *(unsigned int*)(mem-16) << endl;
delete [] mem;


double * dmem = new double[4096];
cout << "Number of allocated bytes for dmem is: " << *(unsigned int*)(((char*)dmem)-16) << endl;
delete [] dmem;

打印

Number of allocated bytes for mem is: 4096
Number of allocated bytes for dmem is: 32768

这是完全正确的。因此,在 MSVC12 的情况下,使用 new 分配的内存有一个额外的“INFO”块,其大小至少为 16 字节。

【讨论】:

    【解决方案3】:

    您正在分配多个动态变量。每个变量包含 4 个字节的数据,但内存管理器通常存储一些有关已分配块的附加信息,并且每个块都应该对齐,这会产生额外的开销。

    试试pointer = new int[262144]; 看看有什么不同。

    【讨论】:

    • 是的,这简直是最简单的回答,新手都能看懂。。非常感谢。。。
    【解决方案4】:

    每次分配int:

    • 内存分配器必须为您提供一个 16 字节对齐的空间块,因为一般来说,内存分配必须提供合适的对齐方式,以便内存可以用于任何目的。正因为如此,每个分配通常返回至少 16 个字节,即使请求的更少。 (对齐要求可能因系统而异。可以想象,可以优化小分配以使用更少的空间。但是,有经验的程序员知道要避免进行许多小分配。)
    • 内存分配器必须使用一些内存来记住分配了多少空间,以便在调用free 时知道有多少空间。 (这可以通过结合使用delete 运算符的空间知识来优化。但是,一般的内存分配例程通常与编译器的newdelete 代码分开。)
    • 内存分配器必须为数据结构使用一些内存来组织有关已分配和释放的内存块的信息。也许这些数据结构需要 O(n•log n) 空间,因此当有许多小分配时开销会增加。

    另一个可能的影响是,随着内存使用量的增长,分配器会从系统请求并初始化更大的块。可能在您第一次使用初始内存池时,分配器再请求 16 个页面。下一次,它请求 32。下一次,64。我们不知道内存分配器向系统请求的内存有多少实际用于满足您对 int 对象的请求。

    不要动态分配许多小对象。请改用数组。

    【讨论】:

      【解决方案5】:

      两种解释:

      1. 动态内存分配(在堆上)不一定是连续的。使用new 时,您执行动态分配。
      2. 如果您包含调试符号(-g 编译器标志),您的内存使用量可能会超出预期。

      【讨论】:

      • 调试符号不应依赖于循环的迭代次数。
      • 确实如此,但它们可以使程序比预期的更大。正如您所指出的,对于较小的测试尤其如此。
      • 但问题是为什么内存与他分配的整数数量不成比例地增长。
      【解决方案6】:

      每个声明都会创建一个适合编译器对齐选项的新变量,该选项需要在(变量的起始地址应该是 128 或 64 或 32(位)的倍数)之间有空格,这会导致许多变量的内存效率低下与一个阵列)。拥有连续区域的数组更有用。

      【讨论】:

        【解决方案7】:

        我认为这取决于编译器如何创建输出程序。

        程序的内存使用量包括程序的所有部分(如 .text,其中包含程序的汇编指令),因此在加载时会占用一些空间。

        对于更多变量,当您分配一些内存(内存对齐)时,内存并不是真正连续的,因此它可能会占用比您想象的更多的内存。

        【讨论】:

          【解决方案8】:

          您分配的不仅仅是一个 int,您还分配了一个有开销的堆块(因平台而异)。有些东西需要跟踪堆信息。如果您改为分配一个整数数组,您会发现内存使用情况更符合您的预期。

          【讨论】:

            【解决方案9】:

            除了其他问题中提到的对齐和开销问题之外,这可能是由于 C++ 运行时请求从操作系统处理内存分配的方式。

            当进程的数据部分填满时,运行时必须为进程分配更多内存。它可能不会以相同大小的块执行此操作。一种可能的策略是,每次它请求内存时,它都会增加它请求的数量(可能每次都将堆大小增加一倍)。对于不使用太多内存的程序,这种策略可以保持较小的内存分配,但减少了大型应用程序必须请求新分配的次数。

            尝试在strace 下运行您的程序并查找对brk 的调用,并注意每次请求的大小。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2015-06-02
              • 1970-01-01
              • 1970-01-01
              • 2011-06-16
              • 1970-01-01
              • 1970-01-01
              • 2015-04-05
              • 1970-01-01
              相关资源
              最近更新 更多