为什么此应用没有像预期的那样消耗那么多内存

Why this app doesn't consume as much memory as expected

本文关键字:内存 应用 为什么      更新时间:2023-10-16

我写了一个简单的应用程序来测试内存消耗。在此测试应用程序中,我创建了四个进程来持续消耗内存,除非进程退出,否则这些进程不会释放内存。

我预计此测试应用程序会消耗最多的 RAM 内存,并导致其他应用程序变慢或崩溃。但结果与预期并不相同。下面是代码:

 #include <stdio.h>
 #include <unistd.h>
 #include <list>
 #include <vector>
 using namespace std;
 unsigned short calcrc(unsigned char *ptr, int count)
 {
     unsigned short crc;
     unsigned char i;
     //high cpu-consumption code 
     //implements the CRC algorithm
     //CRC is Cyclic Redundancy Code
 }

 void* ForkChild(void* param){
    vector<unsigned char*>  MemoryVector;
    pid_t PID = fork();
    if (PID > 0){
        const int TEN_MEGA = 10 * 10 * 1024 * 1024;
        unsigned char* buffer = NULL;
        while(1){
            buffer  = NULL;
            buffer = new unsigned char [TEN_MEGA];
            if (buffer){
                 try{
                    calcrc(buffer, TEN_MEGA);
                    MemoryVector.push_back(buffer);
                 } catch(...){
                    printf("An error was throwed, but caught by our app!n");
                    delete [] buffer;
                    buffer = NULL;
                 }
            }
            else{
                 printf("no memory to allocate!n");
                 try{
                     if (MemoryVector.size()){
                        buffer = MemoryVector[0];
                        calcrc(buffer, TEN_MEGA);
                        buffer = NULL;
                     } else {
                        printf("no memory ever allocated for this Process!n");
                        continue;
                     }
                 } catch(...){
                    printf("An error was throwed -- branch 2," 
                           "but caught by our app!n");
                    buffer = NULL;
                 }
             }
         }  //while(1)
    } else if (PID == 0){
    } else {
      perror("fork error");
    }   
    return NULL;
}

int main(){
int children = 4;
    while(--children >= 0){
    ForkChild(NULL);
    };
    while(1) sleep(1);
    printf("exiting main processn");
    return 0;
 }
  1. 顶部命令

    PID  USER      PR  NI  VIRT  RES  SHR S  %CPU %MEM    TIME+  COMMAND           
    2775 steve     20   0 1503m  508  312 R  99.5  0.0   1:00.46 test              
    2777 steve     20   0 1503m  508  312 R  96.9  0.0   1:00.54 test              
    2774 steve     20   0 1503m  904  708 R  96.6  0.0   0:59.92 test              
    2776 steve     20   0 1503m  508  312 R  96.2  0.0   1:00.57 test
    

    虽然 CPU 很高,但内存百分比仍为 0.0。怎么可能??

  2. 自由命令

                      free  shared    buffers     cached          
    Mem:           3083796       0      55996     428296
    

    可用内存超过 3G RAM 中的 4G。

有谁知道为什么这个测试应用程序无法按预期工作?

Linux 使用乐观内存分配:在实际写入该页之前,它不会物理分配该页内存。因此,您可以分配比可用内存多得多的内存,而不会增加系统的内存消耗。

如果你想强制系统分配(提交)一个物理页面,那么你必须写入它。

以下行不会发出任何写入,因为它是 unsigned char 的默认初始化,这是一个无操作:

buffer = new unsigned char [TEN_MEGA];

如果要强制提交,请使用零初始化:

buffer = new unsigned char [TEN_MEGA]();

将注释变成答案:

  • Linux 不会为进程分配内存页,直到它写入它们(写入时复制)。
  • 此外,您不会在任何地方写入缓冲区,因为 unsigned char 的默认构造函数不执行任何初始化,并且new[]默认初始化所有项。

fork()在父项中返回 PID,在子项中返回 0。您书面ForkChild将执行父级的所有工作,而不是子项。

标准new运算符永远不会返回 null;如果它无法分配内存,它将抛出(但由于过度使用,它实际上也不会在 Linux 中这样做)。这意味着您在分配后对buffer的测试毫无意义:它总是要么接受第一个分支,要么永远不会到达测试。如果你想要一个空返回,你需要写new (std::nothrow) ... .包括<new>才能正常工作。

但是你的程序实际上正在做你期望它做的事情。正如答案所指出的(@Michael Foukarakis的答案),未使用的内存不会被分配。在top程序的输出中,我注意到virt列对于运行程序的每个进程都有大量内存。后来谷歌搜索了一下,我看到了这是什么:

VIRT -- Virtual Memory Size (KiB). The total amount of virtual memory used by the task. 它包括所有代码、数据和共享库,以及已换出的页面和已映射但未使用的页面。

如您所见,您的程序实际上确实为自己生成内存,但以页面的形式存储为虚拟内存。我认为这是一件明智的事情。

此维基页面的片段

内存页或虚拟页 -- 固定长度的连续虚拟内存块,它是以下各项的最小数据单位:

  • 操作系统为程序执行的内存分配;以及
  • 在主内存和任何其他辅助存储(如硬盘驱动器)之间传输。

。因此,程序可以寻址比计算机中物理存在的更多的(虚拟)RAM。虚拟内存是一种方案,它给用户一种使用大块连续内存空间(甚至可能比真实内存更大的)的错觉,而实际上他们的大部分工作都在辅助存储(磁盘)上。作业的固定大小块(页)或可变大小块根据需要读入主内存。

资料来源

  • http://www.computerhope.com/unix/top.htm
  • https://stackoverflow.com/a/18917909/2089675
  • http://en.wikipedia.org/wiki/Page_(computer_memory)

如果你想吞噬大量内存:

int mb = 0;
char* buffer;
while (1) {
    buffer = malloc(1024*1024);
    memset(buffer, 0, 1024*1024);
    mb++;
}

我使用了这样的东西来确保在进行一些文件 I/O 时序测量时文件缓冲区缓存为空。

正如其他答案已经提到的,您的代码在分配缓冲区后永远不会写入缓冲区。这里 memset 用于写入缓冲区。