在c ++中定义全局或静态数组时,它的内存不会立即保留在程序的开头,而只会在我们写入数组时保留。我发现令人惊讶的是,如果我们只写入数组的一小部分,它仍然不会保留整个内存。考虑以下小例子,它稀疏地写入全局数组:
#include <cstdio>
#include <cstdlib>
#define MAX_SIZE 250000000
double global[MAX_SIZE];
int main(int argc, char** argv) {
if(argc<2) {
printf("usage: %s <step size>\n", argv[0]);
exit(EXIT_FAILURE);
}
size_t step_size=atoi(argv[1]);
for(size_t i=0; i<MAX_SIZE; i+=step_size) {
global[i]=(double) i;
}
printf("finished\n"); getchar();
return EXIT_SUCCESS;
}
现在针对不同的步长执行此操作,并查看top的输出,例如:
./a.out 1000000
./a.out 100000
./a.out 10000
./a.out 1000
./a.out 100
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
15718 user 20 0 1918m 1868 728 S 0 0.0 0:00.00 a.out
15748 user 20 0 1918m 10m 728 S 0 0.1 0:00.00 a.out
15749 user 20 0 1918m 98m 728 S 1 0.8 0:00.04 a.out
15750 user 20 0 1918m 977m 728 S 0 8.1 0:00.39 a.out
15751 user 20 0 1918m 1.9g 728 S 23 15.9 0:00.80 a.out
RES列表示内存仅以小块保留,这也意味着阵列不太可能在物理内存中连续。任何人都对更低层次的事情有了更多的了解?
这也有负副作用,我可以很容易地运行许多程序,其中所有VIRT的总和超过物理内存,只要RES的总和低于。但是,只要它们全部写入全局数组,系统就会耗尽物理内存,并且程序会发送sigkill或其他内容。
理想情况下,我想告诉编译器在开始时保留全局变量和静态变量的内存。可能的?
修改
@Magnus:线条实际上是正确的顺序。 :)以第一行为例./a.out 1000000
意味着我在数组中每写第100个条目,因此总共只有250个。这相当于只有1868k的RES。在最后一个示例./a.out 100
中,每写入一百个条目,然后总物理内存也被物理分配RES = VIRT = 1.9g。从数字看,每当条目写入数组时,物理内存上会保留一个完整的4k块。
@Nawaz:数组在虚拟地址空间中是连续的,但据我所知,操作系统可能很懒,只在实际需要时保留物理内存。由于这是在小块中完成的,而不是一次完成整个阵列,如何保证它在物理内存中是连续的?
@Nemo:非常感谢,确实在调用a.out
的多个实例时,这些实例在开始时暂停,然后写入数组,我在oom-killer
中收到/var/log/messages
条消息,的确如此sysctrl
命令阻止我首先启动太多a.out
个实例。谢谢!
Jun 1 17:49:16 localhost kernel: [32590.293421] a.out invoked oom-killer: gfp_mask=0x280da, order=0, oomkilladj=0
Jun 1 17:49:18 localhost kernel: [32592.110033] kded4 invoked oom-killer: gfp_mask=0x201da, order=0, oomkilladj=0
Jun 1 17:49:20 localhost kernel: [32594.718757] firefox invoked oom-killer: gfp_mask=0x201da, order=0, oomkilladj=0
最后两行略显令人担忧。 :)
@doron:谢谢,很好的解释,抱歉不能upvote / select。
答案 0 :(得分:6)
您正在查看正在提交的虚拟内存页面。操作系统通常只会在您的代码明确写入或读取页面时提交页面。这与C ++无关,后者保证数组是连续的。如果您在启动时询问如何让操作系统提交所有进程的页面,则需要使用特定于操作系统的内容(如果存在)。
答案 1 :(得分:2)
这里有两件事情可以发挥作用。虚拟内存和物理内存。
静态数据的虚拟内存,就像程序指令一样,在程序开始执行之前分配。通过这个我的意思是你的程序的地址总是被定义。
操作系统可能是懒惰的,但是当涉及将静态数据和程序指令加载到物理内存RAM中时。这种方式的工作原理如下:
这是操作系统允许执行的一小部分操作,因为它对于正在运行的进程完全无法检测到。除非我们当然缺乏记忆。
答案 2 :(得分:1)
我不认为您发布的表证明了任何实质性内容。
就静态存储阵列而言,它是在程序启动之前分配的,根据定义,在程序进入main()
函数之前,运行时将内存分配给全局数组,并持续计划的持续时间:
§3.7.1/ 1
所有既没有动态的物体 储存时间也不是当地的 静态存储时间。存储 因为这些物体应该持久 计划的持续时间(3.6.2, 3.6.3)。
无论是全局还是本地,数组总是具有连续的内存。
答案 3 :(得分:1)
这听起来像一个Linux系统,一旦使用的内存超过可用虚拟内存,“OOM杀手”就会唤醒并开始查杀进程。 grep for / var / log / messages中的“oom”确认。
如果是,则此设置:
sysctl -w vm.overcommit_memory=2
...将阻止内核允许您的进程分配比可用VM更多的内容。