【问题标题】:Linux really allocating memory it shoudn't in C++ codeLinux 真的分配了它不应该在 C++ 代码中的内存
【发布时间】:2015-03-07 21:34:43
【问题描述】:

在 Linux 中,内核在我们实际使用该内存之前不会分配任何物理内存页面,但我在这里很难找到它为什么实际上分配该内存:

   for(int t = 0; t < T; t++){
      for(int b = 0; b < B; b++){
         Matrix[t][b].length = 0;
         Matrix[t][b].size = 60;
         Matrix[t][b].pointers = (Node**)malloc(60*sizeof(Node*)); 
         }
   }

然后我访问这个数据结构以向其中添加一个元素,如下所示:

   Node* elem = NULL;
   Matrix[a][b].length++;
   Matrix[a][b]->pointers[ Matrix[a][b].length ] = elem;

基本上,我在运行程序时使用 htop,如果我增加编号,Linux 确实会分配更多内存。 “60”我在上面的代码中。为什么?当第一个元素添加到数组时,它不应该只分配一个页面吗?

【问题讨论】:

  • First -> Do not cast the return value of malloc(),你有没有问过自己 top 如何确定进程的内存使用量?
  • 我不确定你的问题; malloc 经常使用mmap 获取内存,但通常不会将其(通过munmap)返回到free 的内核(但管理释放的内存以供未来malloc-s 重用)
  • @a3mlord 您正在使用 c++ 编译器,因为如果 c 编译器给出该错误,则意味着它不是标准编译器,请确保您的项目构建的是正确的编译器。
  • 您没有检查 malloc 的返回值。根据您的系统设置,很有可能当您尝试分配过多内存时,malloc 将开始返回 NULL。然后,当您尝试通过该变量设置某些内容时,它将导致段错误。由于上面的代码,你的进程映射的虚拟内存量会增加,即使你不去碰它。
  • 这并不重要,因为您正在更改该程序运行的参数(即 - 它尝试分配多少内存)。如果 malloc 返回 NULL,那么上面的代码会尝试取消引用它,这几乎肯定会导致 seg 错误。

标签: c++ linux memory dynamic-memory-allocation


【解决方案1】:

这取决于您的 Linux 系统是如何配置的。

这是一个简单的 C 程序,它尝试分配 1TB 内存并触及其中的一部分。

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main()
{
  char *array[1000];
  int i;

  for (i = 0; i < 1000; ++i)
  {
    if (NULL == (array[i] = malloc((int) 1e9)))
    {
      perror("malloc failed!");
      return -1;
    }

    array[i][0] = 'H';
  }

  for (i = 0; i < 1000; ++i)
    printf("%c", array[i][0]);

  printf("\n");

  sleep(10);

  return 0;
}

当我在它旁边运行 top 时,它说 VIRT 内存使用量达到 931g(其中 g 表示 GiB),而 RES 仅达到 4380 KiB。

现在,当我通过/sbin/sysctl -w vm.overcommit_memory=2 更改我的系统以使用不同的过度使用策略并重新运行它时,我得到:

malloc failed!: Cannot allocate memory

因此,您的系统可能使用了与您预期不同的过度使用策略。更多信息请阅读this

【讨论】:

  • 非常好,虽然 OP 说它是 c++,但仍然没有理由采取不同的行为,所以你的回答很好。如果我可以投票两次...
  • 遗憾的是,这对我没有帮助。我非常了解过度使用,我非常喜欢它。但是,在我的代码中,我有: Node** pool = (Node**)malloc(300000*sizeof(Node*)); for(int u=0;u
  • @a3mlord 我知道你认为这并不重要,但你能告诉我们导致这个问题的 T 和 B 的值吗?
  • @jschultz410 我认为这很重要... T = 1278 和 B = 131072
  • @a3mlord 我说因为你说“在这两种情况下(60 或 100),我都在使用该数组直到它的第 30 位(所以它不应该超过那个应该最终分配 - 可能会更多,因为它基本上是由页面大小决定的)。”无论如何,真正的答案如下: malloc / new 将您的一些分配用于他们自己的内部簿记结构,因此每个分配的基础都将映射到物理内存。增加数组大小很重要,因为需要更多的页面来适应所有分配,其基础将映射到物理内存。
【解决方案2】:

您认为 malloc / new 不会导致写入 任何 内存并因此由操作系统分配物理内存的假设是不正确的(对于您拥有的内存分配器实现)。

我已经在以下简单程序中重现了您所描述的行为:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

int main(int argc, char **argv)
{
  char **array[128][128];
  int    size;
  int    i, j;

  if (1 == argc || 0 >= (size = atoi(argv[1])))
    fprintf(stderr, "usage: %s <num>; where num > 0\n", argv[0]), exit(-1);

  for (i = 0; i < 128; ++i)
    for (j = 0; j < 128; ++j)
      if (NULL == (array[i][j] = malloc(size * sizeof(char*))))
      {
        fprintf(stderr, "malloc failed when i = %d, j = %d\n", i, j);
        perror(NULL);
        return -1;
      }

  sleep(10);

  return 0;
}

当我使用各种小的 size 参数作为输入运行此程序时,VIRT 和 RES 内存占用量(如顶部所报告)一起增长,即使我没有明确地触摸我所使用的内部数组分配。

size 超过 ~512 之前,这基本上是正确的。此后,RES 保持在 64 MiB 不变,而 VIRT 可能非常大(例如 - 1220 GiB,size 为 10M)。这是因为 512 * 8 = 4096,这是 Linux 系统上常见的虚拟页面大小,而 128 * 128 * 4096 B = 64 MiB。

因此,看起来每个分配的第一页都被映射到物理内存,可能是因为 malloc / new 本身正在写入部分分配以用于其内部簿记。当然,许多小的分配可能适合并放置在同一页上,因此对于许多此类分配,只有一页被映射到物理内存。

在您的代码示例中,更改数组的大小很重要,因为这意味着更少的数组可以放在一个页面上,因此需要 malloc / new 本身访问更多内存页面(因此通过映射到物理内存操作系统)在程序的运行。

当您使用 60 时,大约需要 480 个字节,因此其中大约 8 个分配可以放在一页上。当您使用 100 时,大约需要 800 个字节,因此这些分配中只有约 5 个可以放在一页上。所以,我预计“100 程序”使用的内存大约是“60 程序”的 8/5,这似乎足以让您的机器开始交换到稳定存储。

如果每个较小的“60”分配的大小已经超过 1 页,那么将其更改为更大的“100”不会影响程序的初始物理内存使用量,就像您最初预期的那样。

PS - 我认为您是否显式触摸分配的初始页面将无关紧要,因为 malloc / new 已经这样做了(对于您拥有的内存分配器实现)。

【讨论】:

    【解决方案3】:

    如果您通常期望您的 b 数组通常很小,通常小于 2^X 指针(下面代码中的 X = 5),但也可以处理异常情况更大。

    如果您的预期使用情况不匹配,您可以将 X 调低。如果您希望大多数数组通常使用至少 2^Y 指针(例如 - Y = 3),您还可以从 0 向上调整最小大小数组(并且不分配较小的 2^i 级别)。

    如果您认为实际上 X == Y (例如 - 4) 适合您的使用模式,那么您可以只分配 B * (0x1

    这里的重点是初始分配将映射到非常少的物理内存,从而解决最初引发您最初问题的问题。

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    
    #define T           1278
    #define B           131072
    #define CAP_MAX_LG2 5        
    #define CAP_MAX     (0x1 << CAP_MAX_LG2)  // pre-alloc T's to handle all B arrays of length up to 2^CAP_MAX_LG2
    
    typedef struct Node Node;
    
    typedef struct
    {
      int    t;                               // so a matrix element can know to which T_Allocation it belongs
      int    length;
      int    cap_lg2;                         // log base 2 of capacity; -1 if capacity is zero
      Node **pointers;
    
    } MatrixElem;
    
    typedef struct
    {
      Node **base;                            // pre-allocs B * 2^(CAP_MAX_LG2 + 1) Node pointers; every b array can be any of { 0, 1, 2, 4, 8, ..., CAP_MAX } capacity
      Node **frees_pow2[CAP_MAX_LG2 + 1];     // frees_pow2[i] will point at the next free array of 2^i pointers to Node to allocate to a growing b array
    
    } T_Allocation;
    
    MatrixElem   Matrix[T][B];
    T_Allocation T_Allocs[T];
    
    int  Node_init(Node *n) { return 0; } // just a dummy
    void Node_fini(Node *n) { }           // just a dummy 
    int  Node_eq(const Node *n1, const Node *n2)  { return 0; } // just a dummy
    
    void Init(void)
    {
      for(int t = 0; t < T; t++) 
      {
        T_Allocs[t].base = malloc(B * (0x1 << (CAP_MAX_LG2 + 1)) * sizeof(Node*));
    
        if (NULL == T_Allocs[t].base)
          abort();
    
        T_Allocs[t].free_pows2[0] = T_Allocs[t].base;
    
        for (int x = 1; x <= CAP_MAX_LG2; ++x)
          T_Allocs[t].frees_pow2[x] = &T_Allocs[t].base[B * (0x1 << (x - 1))];
    
        for(int b = 0; b < B; b++)
        {
          Matrix[t][b].t        = t;
          Matrix[t][b].length   = 0;
          Matrix[t][b].cap_lg2  = -1;
          Matrix[t][b].pointers = NULL;
        }
      }
    }
    
    Node *addElement(MatrixElem *elem)
    {
      if (-1 == elem->cap_lg2 || elem->length == (0x1 << elem->cap_lg2))  // elem needs a bigger pointers array to add an element
      {
        int new_cap_lg2 = elem->cap_lg2 + 1;
        int new_cap     = (0x1 << new_cap_lg2);
    
        if (new_cap_lg2 <= CAP_MAX_LG2)            // new b array can still fit in pre-allocated space in T
        {
          Node **new_pointers = T_Allocs[elem->t].frees_pow2[new_cap_lg2];
    
          memcpy(new_pointers, elem->pointers, elem->length * sizeof(Node*));
          elem->pointers = new_pointers;
    
          T_Allocs[elem->t].frees_pow2[new_cap_lg2] += new_cap;
        }
        else if (elem->cap_lg2 == CAP_MAX_LG2)     // exceeding pre-alloc'ed arrays in T; use malloc
        {
          Node **new_pointers = malloc(new_cap * sizeof(Node*));
    
          if (NULL == new_pointers)
            return NULL;
    
          memcpy(new_pointers, elem->pointers, elem->length * sizeof(Node*));
          elem->pointers = new_pointers;
        } 
        else                                       // already exceeded pre-alloc'ed arrays in T; use realloc
        {
          Node **new_pointers = realloc(elem->pointers, new_cap * sizeof(Node*));
    
          if (NULL == new_pointers)
            return NULL;
    
          elem->pointers = new_pointers;
        }
    
        ++elem->cap_lg2;
      }
    
      Node *ret = malloc(sizeof(Node);
    
      if (ret)
      {
        Node_init(ret);
        elem->pointers[elem->length] = ret;
        ++elem->length;
      }
    
      return ret;
    }
    
    int removeElement(const Node *a, MatrixElem *elem)
    {
      int i;
    
      for (i = 0; i < elem->length && !Node_eq(a, elem->pointers[i]); ++i);
    
      if (i == elem->length)
        return -1;
    
      Node_fini(elem->pointers[i]);
      free(elem->pointers[i]);
      --elem->length;
      memmove(&elem->pointers[i], &elem->pointers[i+1], sizeof(Node*) * (elem->length - i));
    
      return 0;
    }
    
    int main()
    {
      return 0;
    }
    

    【讨论】:

    • 我现在明白了,但问题是我有 addElement( Matrix[x][y] ) 和 removeElement(Node a, Matrix[x][y]) 之类的函数,这变得非常用你的方法管理起来很复杂,我猜。
    • @a3mlord 我在我的代码中给出了可能对你有用的那些函数的例子。
    猜你喜欢
    • 2015-05-31
    • 1970-01-01
    • 2017-02-09
    • 2010-09-19
    • 2020-08-11
    • 2010-10-24
    • 1970-01-01
    • 2021-09-01
    • 1970-01-01
    相关资源
    最近更新 更多