2

In Linux, the kernel doesn't allocate any physical memory pages until we actually using that memory, but I am having a hard time here trying to find why it does in fact allocate this memory:

   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*)); 
         }
   }

I then access this data structure to add one element to it like this:

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

Essentially, I run my program with htop on the side and Linux does allocate more memory if I increase the no. "60" I have in the code above. Why? Shouldn't it only allocate one page when the first element is added to the array?

a3mlord
  • 1,060
  • 6
  • 16
  • 2
    First -> [Do not cast the return value of `malloc()`](http://stackoverflow.com/a/605858/1983495), and did you ask your self how does top determine the memory usage for a process? – Iharob Al Asimi Mar 07 '15 at 21:37
  • I'm not sure to understand your question; `malloc` often gets memory using `mmap` but usually don't return it (thru `munmap`) to the kernel at `free` (but manage the freed memory to be reused by future `malloc`-s) – Basile Starynkevitch Mar 07 '15 at 21:37
  • @BasileStarynkevitch In the past, Linux often wouldn't actually allocate any memory when malloc was called until said memory was actually touched by the program. So, you could malloc a huge block of memory, malloc would return a pointer (indicating success) then go and iterate over that memory touching it only to eventually cause a seg fault, or some other kind of exceptional out-of-memory failure. I believe you can alter the system's behavior though some environmental variables to be "more reasonable". – jschultz410 Mar 07 '15 at 21:40
  • @BasileStarynkevitch Here's a decent summary about what I was talking: http://www.etalabs.net/overcommit.html – jschultz410 Mar 07 '15 at 21:50
  • @jdvhultz410: Thank you, I knew about memory overcommit. But is it what the OP is asking? – Basile Starynkevitch Mar 07 '15 at 21:51
  • @iharob Not casting delivers the following error: error: a value of type "void *" cannot be assigned to an entity of type "Node **" ; Yes, I know exactly how htop does that, and I know that in this case it is true because increasing the 60 to 200 crashes the machine since it runs out of memory. Whats your point? – a3mlord Mar 07 '15 at 21:52
  • It would be easier to reason about your observations if you could provide a program that we can actually compile and run. For the toy examples I have compiled on my machine, the behavior is as expected. – 5gon12eder Mar 07 '15 at 21:54
  • 2
    @a3mlord you are using a c++ compiler because if a c compiler gives that error it means that it's not standard compilant, please ensure that your project is build it the correct compiler. – Iharob Al Asimi Mar 07 '15 at 21:54
  • @iharob Yes, I am using a c++ compiler because this is actually C++ code, sry for the confusion. – a3mlord Mar 07 '15 at 21:57
  • 2
    You aren't checking the return value of malloc. It is quite possible, depending on your system's setup, that when you try to allocate too much memory, malloc will start returning NULL. Then when you try to set something through that variable it will cause a seg fault. The amount of virtual memory mapped by your process will increase due to the above code, even if you don't touch it. – jschultz410 Mar 07 '15 at 21:58
  • @jschultz410 This code is almost older than me, and never had a seg. fault in its life. – a3mlord Mar 07 '15 at 22:03
  • 1
    That doesn't really matter as you are changing the parameters under which this program is running (i.e. - how much memory it tries to allocate). If malloc returns NULL, then the above code tries to dereference it and that will almost surely cause a seg fault. – jschultz410 Mar 07 '15 at 22:06
  • @jschultz410 I understand, but thats irrelevant now, and I don't see how that relates to the question. – a3mlord Mar 07 '15 at 22:07
  • @a3mlord then you are using the wrong casting style, it should be `reinterpret_cast(malloc(...))` and then you would be using the wrong allocation function you should `new Node*[60];`. – Iharob Al Asimi Mar 07 '15 at 22:14
  • I think you are failing to understand what allocating means in this context, because if the system didn't know how much memory is reserved for your program then what would happen when it needs to "*allocate*" it? So it doesn't mean that the virtual memory of your process will not increas, it means that the OS will not do anythin physically with the memory block until you read/write from/to it. – Iharob Al Asimi Mar 07 '15 at 22:23
  • @iharob ONE: the behavior of "new Node*[60]" and its correspondent malloc is the same - I just tested it. TWO: No, I believe that I am right. If I set that "60" to "100", then the machine freezes because I try to allocate more memory than I have (and I need to physically reset it). And in both cases (60 or 100), I am using that array up to its 30th position (so it shouldn't no more than that should ultimately allocated - there might be a bit more because its basically by the page size). – a3mlord Mar 07 '15 at 22:26
  • @a3mlord I didn't say that it would behave differently with `new` but it's just the c++ way for dynamic allocation. – Iharob Al Asimi Mar 07 '15 at 22:29
  • If you can actually lock up your system (as opposed to making it run REALLY slowly due to swapping) just by having a user process allocate too much memory, then something is badly broken on your system. – jschultz410 Mar 07 '15 at 22:35
  • @jschultz410 By "freezes" thats what I mean: it runs so slow that it doesn't even answer my command to kill the process. – a3mlord Mar 07 '15 at 22:56

3 Answers3

7

It depends on how your Linux system is configured.

Here's a simple C program that tries to allocate 1TB of memory and touches some of it.

#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;
}

When I run top by its side, it says the VIRT memory usage goes to 931g (where g means GiB), while RES only goes to 4380 KiB.

Now, when I change my system to use a different overcommit strategy by /sbin/sysctl -w vm.overcommit_memory=2 and re-run it, I get:

malloc failed!: Cannot allocate memory

So your system may be using a different overcommit strategy than you expected. For more information read this.

jschultz410
  • 2,849
  • 14
  • 22
  • Excelent, although the OP said it was c++, but still there is no reason for a different behavior so your answer is good. If I could upvote twice ... – Iharob Al Asimi Mar 07 '15 at 22:30
  • Sadly, this doesn't help me. I know overcommit very well and I like it a lot. However, in my code I have: Node** pool = (Node**)malloc(300000*sizeof(Node*)); for(int u=0;u<300000;u++) pool[u] = new Node(); and this doesn't consume "any memory", i.e. only the memory that corresponds to the positions of the vector I read/write. However, for the code above, it does allocate tons of memory, and changing "60" to "100" makes me run out of memory. Why? – a3mlord Mar 07 '15 at 22:48
  • @a3mlord I know you don't think it matters but could you tell us the values of T and B that cause this problem for you? – jschultz410 Mar 07 '15 at 23:11
  • @jschultz410 I believe that matters... T = 1278 and B = 131072 – a3mlord Mar 08 '15 at 11:39
  • @a3mlord I said that because you said "And in both cases (60 or 100), I am using that array up to its 30th position (_so it shouldn't no more than that should ultimately allocated_ - there might be a bit more because its basically by the page size)." Anyway, the real answer is below: malloc / new use some of your allocation for their own internal book keeping structures, so the base of every allocation will be mapped to physical memory. Increasing the array size matters because more pages will be needed to fit all the allocations, the bases of which will be mapped to physical memory. – jschultz410 Mar 08 '15 at 15:10
  • @jschultz410 In that case what would be the most efficient way (i.e. avoid the max. no. of mallocs) to reserve space for those arrays, if I don't know how many elements there might be? What I am doing is to create this pool of 60 elements and realloc the array if need be. – a3mlord Mar 08 '15 at 15:16
  • @a3mlord: The first idea that jumps into my head is rather than allocating an array for each iteration of b, simply allocate one much larger array for each iteration of T. That way, much less physical memory will be mapped initially. If you need each "b array" to be able to grow, then you can manually divvy up the T space as needed, potentially moving b arrays around as necessary within the T array. Ultimately if you exhaust / fragment the T array too much, then you can realloc it. – jschultz410 Mar 08 '15 at 15:26
  • @a3mlord: In particular, you can initially allocate the T array as malloc(B * (30 + X) * sizeof(Node*)), which should be a worst case to fit all your b arrays. Then initially give each b array X (e.g. - 10 or 15 or whatever your expected usage case is) pointers of the T array. If a b array needs to get bigger than X, then give them 30 pointers at the end of the current b arrays. – jschultz410 Mar 08 '15 at 15:36
  • @jschultz410 I don't fully get the idea, but I am currently traveling and can't think about that well. Would that work even if I didn't know the upper bounds of my arrays? Because I don't. – a3mlord Mar 08 '15 at 15:54
  • @a3mlord The basic idea is that rather than depending on malloc / realloc for your b arrays, you will instead allocate a much larger T array and then do your own memory management for the b arrays it contains. You can try to keep it simple, like I did in my previous example when you know an upper bound on each b array, or you can get fancier. If you exhaust your T array (i.e. - you need to grow a b array but can't find a big enough open space in your T array) then you can realloc your T array to be bigger. – jschultz410 Mar 08 '15 at 16:08
  • @a3mlord I'll sketch something in another answer. – jschultz410 Mar 08 '15 at 16:47
  • @jschultz410 I got it now, but the problem is that I have functions like addElement( Matrix[x][y] ) and removeElement(Node a, Matrix[x][y]) , this becomes very complicated to manage with your method, I guess. – a3mlord Mar 08 '15 at 16:49
2

Your assumption that malloc / new doesn't cause any memory to be written, and therefore assigned physical memory by the OS, is incorrect (for the memory allocator implementation you have).

I've reproduced the behavior you are describing in the following simple program:

#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;
}

When I run this with various small size parameters as input, the VIRT and RES memory footprints (as reported by top) grow together in-step, even though I'm not explicitly touching the inner arrays that I'm allocating.

This basically holds true until size exceeds ~512. Thereafter, RES stays constant at 64 MiB while VIRT can be extremely large (e.g. - 1220 GiB when size is 10M). That is because 512 * 8 = 4096, which is a common virtual page size on Linux systems, and 128 * 128 * 4096 B = 64 MiB.

Therefore, it looks like the first page of every allocation is being mapped to physical memory, probably because malloc / new itself is writing to part of the allocation for its own internal book keeping. Of course, lots of small allocations may fit in and be placed on the same page, so only one page gets mapped to physical memory for many such allocations.

In your code example, changing the size of the array matters because it means less of those arrays can be fit on one page, therefore requiring more memory pages to be touched by malloc / new itself (and therefore mapped to physical memory by the OS) over the run of the program.

When you use 60, that takes about 480 bytes, so ~8 of those allocations can be put on one page. When you use 100, that takes about 800 bytes, so only ~5 of those allocations can be put on one page. So, I'd expect the "100 program" to use about 8/5ths as much memory as the "60 program", which seems to be a big enough difference to make your machine start swapping to stable storage.

If each of your smaller "60" allocations were already over 1 page in size, then changing it to be bigger "100" wouldn't affect your program's initial physical memory usage, just like you originally expected.

PS - I think whether you explicitly touch the initial page of your allocations or not will be irrelevant as malloc / new will have already done so (for the memory allocator implementation you have).

jschultz410
  • 2,849
  • 14
  • 22
0

Here's a sketch of what you could do if you typically expect that your b arrays will usually be small, usually be less than 2^X pointers (X = 5 in the code below), but also handles exceptional cases where they get even bigger.

You can adjust X down if your expected usage doesn't match. You could also adjust the minimum size arrays up from 0 (and not allocate the smaller 2^i levels), if you expect most of your arrays will usually use at least 2^Y pointers (e.g. - Y = 3).

If you think that actually X == Y (e.g. - 4) for your usage pattern, then you can just do one allocation of B * (0x1 << X) * sizeof(Node*) and divvy up that T array to your b's. Then if a b array needs to exceed 2^X pointers, then resort to malloc for it followed by realloc's if it needs to grow even further.

The main point here is that the initial allocation will map to very little physical memory, addressing the problem that initially spurred your original question.

#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;
}
jschultz410
  • 2,849
  • 14
  • 22
  • I got it now, but the problem is that I have functions like addElement( Matrix[x][y] ) and removeElement(Node a, Matrix[x][y]) , this becomes very complicated to manage with your method, I guess. – a3mlord Mar 09 '15 at 10:19
  • @a3mlord I gave examples of those functions in my code that might work for you. – jschultz410 Mar 09 '15 at 13:33