2

我正在做一些堆地址增长的实验,发生了一些有趣的事情。(操作系统:CentOS,)

但我不明白,为什么会这样?谢谢!

这是我首先做的:

double *ptr[1000];
for (int i=0;i<1000;i++){
    ptr[i] = new double[**10000**];
    cout << ptr[i] << endl;
}

输出是增量的(最后几行):

....
....
0x2481be0
0x2495470
0x24a8d00
0x24bc590
0x24cfe20
0x24e36b0
0x24f6f40
0x250a7d0
0x251e060

然后我将10000改为20000:

double *ptr[1000];
for (int i=0;i<1000;i++){
    ptr[i] = new double[**20000**];
    cout << ptr[i] << endl;
}

该地址变得更像堆栈空间的地址(并且递减):

....
....
0x7f69c4d8a010
0x7f69c4d62010
0x7f69c4d3a010
0x7f69c4d12010
0x7f69c4cea010
0x7f69c4cc2010
0x7f69c4c9a010
0x7f69c4c72010
0x7f69c4c4a010
0x7f69c4c22010
0x7f69c4bfa010
0x7f69c4bd2010
0x7f69c4baa010
0x7f69c4b82010
4

5 回答 5

7

不同的环境/实现使用不同的策略分配内存,因此没有一个正确的规则。然而,一个常见的模式是对小对象和大对象使用不同的分配策略。

通常,一个运行时会为不同大小的对象提供多个堆,这些堆针对不同的使用模式进行了优化。例如,小对象往往被频繁分配和快速删除,而大对象往往很少被创建并且具有较长的生命周期。

如果你对所有东西都使用一个堆,那么一些小对象将很快散布在你的内存空间中,留下许多中等大小的块可用,但大对象只需要很少或不需要大块。这称为内存碎片,即使名义上您的应用程序有大量可用内存,也可能导致分配失败。

使用不同堆的另一个原因是针对不同的对象大小使用不同的使用跟踪方法。例如,一个实现可能会从操作系统请求一个新的内存块用于大对象,而对于小对象,使用一些较小的操作系统内存块以及由 C 运行时堆管理器处理的子分配。对于大型对象非常有效的内存使用跟踪机制对于较小的对象可能非常昂贵,因为用于跟踪使用的内存成为每个对象实际使用的内存的重要部分。

在您的情况下,我的猜测是运行时在内存空间的开头分配小对象,自下而上,而在结尾附近分配较大的对象,自上而下,以避免碎片。

于 2013-04-16T02:47:46.763 回答
4

你不会在这里得到很好的答案,因为新函数可以选择它想要分配内存的任何方法。我的猜测是这里的算法将池分为大小分配池,而大分配池向下增长以便它们可以在中间相遇(以免浪费任何空间)。

于 2013-04-16T02:41:50.603 回答
0

在 UNIX 上,分配器使用 sbrk(2) 和 mmap(2) 从操作系统获取内存。sbrk 返回的地址定义明确,但 mmap 的地址是“任何可用的”。在 Windows 上,分配器使用类似于 mmap 的 VirtualAlloc()。

于 2013-04-16T02:49:12.210 回答
0

实现可以自由地混合不同的分配方案。在 C++ 中,有数千甚至数百万个相对较小的对象是很正常的,因此库的内存分配例程确保它们打包好并且非常轻量级是有意义的。您对 10000 个双精度的分配是这样做的:它们相隔 80016 个字节 - 80000 个用于 10000 个 8 字节变量和只有 16 个字节的填充。节点特别指出大小与 2 的幂没有关系,而当分配 20000 加倍时,它们每次减少 163840 字节......奇怪的是,正好是 10 * 2^14。这向我表明,以前的分配正在从一个堆中得到满足,该堆旨在通过 C++ 分配支持有效的小对象分配new函数,而后者已经越过阈值并且可能被发送到malloc来自不同堆的内存,浪费更多。

于 2013-04-16T02:51:35.013 回答
0

从某种意义上说,您很幸运,10000 个双精度数和 20000 个双精度数恰好位于称为 MMAP_THRESHOLD 的临界阈值的相对两侧。

MMAP_THRESHOLD 默认为 128KB。因此,80KB(即 10000 双倍)内存分配请求在堆上提供服务,而 160KB(20000 双倍)内存分配请求由匿名内存映射(通过 mmap 系统调用)提供服务。(请注意,由于其不同的底层内存分配处理机制,对大型内存分配使用内存映射可能会产生额外的惩罚。您可能需要调整 MMAP_THRESHOLD 以获得应用程序的最佳性能。)

Linux Man for malloc 中

通常,malloc() 从堆中分配内存,并根据需要调整堆的大小,使用 sbrk(2)。当分配大于 MMAP_THRESHOLD 字节的内存块时,glibc malloc() 实现使用 mmap(2) 将内存分配为私有匿名映射。MMAP_THRESHOLD 默认为 128 kB,但可以使用 mallopt(3) 进行调整。使用 mmap(2) 执行的分配不受 RLIMIT_DATA 资源限制的影响(请参阅 getrlimit(2))。

于 2015-08-20T19:13:03.627 回答