Home > Back-end >  Why dynamic allocation of small objects in C reserves much more memory than actually needed?
Why dynamic allocation of small objects in C reserves much more memory than actually needed?

Time:02-19

I have the following program:

#include <unistd.h>
#include <cstdlib>
int main()
{
    for (int i=0; i<512*512*512; i  )
        int *v = (int *) malloc(sizeof(int));

    sleep(1000000000);  // time to see memory consumption from htop

    return 0;
}

Using top (htop), I can see how much memory is allocated (~4 GiB). Rewriting line 6 into

    int *v = (int *) malloc(6*sizeof(int));

I can see that the same amount of memory is being allocated. This changes with malloc(7*sizeof(int)); by such experimentation, I notice that the program always allocates a 32-byte sized block. This becomes very costly because in total I count 32 bytes for the block and 8 bytes for the pointer (If I store them in a vector) = 40 bytes just for one integer, which would otherwise cost only 4 bytes, therefore 10 times higher memory consumption using this dynamic memory allocation. I would like to know why the memory block size is chosen of this size, and why the compiler does not optimize it by reserving a smaller block (just for one integer instead of the one that can accommodate 6 integers). Since I don't have a computer science background and lack the proper terminology, I'm unable to find the correct literature to understand this in more detail. Any recommendation? Thank you.

CodePudding user response:

Why dynamic allocation of small objects in C reserves much more memory than actually needed?

  1. Memory allocations have to account for alignment requirements of objects that are created in the memory. When you allocate dynamic memory, the implementation doesn't know what type of object you are going to create. Hence, it must align the allocation to the strictest alignment that may be required by any scalar type. Since no dynamic allocation can have lesser alignment, the memory between the allocation and the next aligned address cannot be used.
  2. In order to be able to deallocate and reuse memory, the memory allocation system must record all allocations in a data structure. This data structure consumes memory itself, in relation to the total number of allocations. With many small allocations, the overhead is relatively larger than with few large allocations.

Any recommendation?

  • Avoid using std::malloc in C .
  • Don't leak memory.
  • Avoid unnecessary dynamic allocation.
  • Prefer to allocate larger chunks of memory when possible.
    • When not possible, consider whether it would be a good idea to use a custom allocator instead of the global one. This is an advanced technique.

CodePudding user response:

Memory allocation strategies is a complex area, so it will be be hard to give an answer that covers every scenario. However, typically your library will allocation a large chunk of memory from the operating system and then divide that up into blocks which it will use for allocation requests.

The library will need to keep housekeeping information about the blocks, such as if they are allocated. If the memory you allocate is larger than a single block then the blocks will need to be physically next to each other in memory, and the library will need to know how many blocks are part of the requested allocation (more housekeeping).

Additionally alignment rules mean that the library will been to return a pointer to memory that is correctly aligned. On a 64bit operating system this means that the address will be a multiple of 8, so if you're requesting an allocation less than this then you'll pay a cost.

The library designers will have likely done analysis and worked out what is the optimal block size based on the cost of managing the blocks versus what sizes people typically allocate. It's very rare to just allocate sizeof(int) in the real world, typically it a struct/class or run of memory, at which point the unused portion of the block/blocks not used by your allocation become insignificant.

  • Related