4

在 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:谢谢,很好的解释,抱歉不能投票/选择。

4

4 回答 4

6

您正在查看正在提交的虚拟内存页面。操作系统通常只会在您的代码明确写入或读取页面时提交页面。这与 C++ 无关,后者保证数组是连续的。如果您询问如何让您的操作系统在启动时提交所有进程的页面,您需要使用操作系统特定的东西(如果存在的话)。

于 2011-06-01T18:09:32.307 回答
2

这里有两件事在起作用,即。虚拟内存和物理内存。

用于静态数据的虚拟内存,就像在程序开始执行之前分配程序的指令一样。我的意思是始终定义程序的地址。

操作系统可能很懒惰,但是在将静态数据和程序指令加载到物理内存 RAM 时。它的工作方式是这样的:

  • 进程加载器为静态数据分配进程虚拟内存,但不将数据加载到 RAM 中。
  • 当尝试访问这些地址时,会触发处理器异常,然后我们进入内核模式。
  • 内核现在将数据加载到 RAM 中并将 RAM 链接到进程虚拟地址空间。
  • 内核切换回用户模式到处理器异常发生的确切位置。
  • 由于 RAM 现在已链接到进程虚拟地址空间,因此程序现在将继续执行,就好像什么都没发生过一样。

这是操作系统被允许做的完全轻微的操作,因为它完全无法被正在运行的进程检测到。当然,除非我们记忆力不足。

于 2011-06-01T20:20:27.377 回答
1

我认为您发布的表格没有任何实质意义。

就静态存储数组而言,它是在程序启动之前分配的,顾名思义,就是在程序进入main()函数之前,运行时为全局数组分配内存,并在程序运行期间持续存在:

§3.7.1/1

所有既不具有动态存储持续时间也不是本地对象的对象具有静态存储持续时间。这些对象的存储应持续到程序的持续时间(3.6.2、3.6.3)。

而且无论是全局的还是局部的,数组总是有连续的内存。

于 2011-06-01T18:10:35.747 回答
1

这听起来像一个 Linux 系统,一旦使用的内存超过可用的虚拟内存,“OOM 杀手”就会唤醒并开始杀死进程。在 /var/log/messages 中对“oom”进行 Grep 确认。

如果是这样,这个设置:

sysctl -w vm.overcommit_memory=2

...将阻止内核允许您的进程分配比可用 VM 更多的内存。

于 2011-06-01T19:07:57.137 回答