MEMORY-POOL-ZERO (MPZ)
The MPZ is a simple implementation of a very fast and effective memory pool. It’s designed for using in applications with a lot of memory space allocations of the repeatedly sizes up to 1.024 bytes (default, configurable) per allocation, e.g. for structs in tokenization applications.
Features
- Written in pure
C. - Supports thread safety.
- Doesn’t require any external libraries.
- Optimized for 32 and 64 bit systems on little-endian machines.
- Prevents internal memory space fragmentation (freed memory space is reusable).
- Implements memory space alignment to avoid wasting CPU cycles when accessing the data.
- Implements a very fast reset of the allocated memory space to permitt the memory space to be efficiently reused, which in most memory pool implementations is either unavailable or quite slow.
- Constant time for the memory space allocations from the internal memory space
unless the MPZ needs to grab a new memory space from the OS via
malloc(). - Constant time for the
freeoperations unless the MPZ needs to release the memory space back to the OS viafree(). - Implements simple security checks for
segmentation faultsanddouble freeerrors. - Very easy to modify or extend.
Limitations
- The maximum allocation size from MPZ is limited to
2^30 - 1(1.073.741.823) bytes per allocation request. - The code compiles and runs on Linux systems. Other platforms haven’t been tested.
TODOs
- Implementation of a
mpz_prealloc()function.
Bugs
- Fixed: The overhead size of the slot was not added to the requested allocation size (04f53a4)
About MPZ
The MPZ implements memory space alignment to avoid wasting CPU cycles when accessing
the data. The alignment is continuously implemented for the accessing internal metadata
as well as for the return pointers to the user. The alignment size for memory space
allocations from the OS is borrowed from GNU libc and is defined as 2 * sizeof(size_t)
which is 16 bytes on 64 bit systems. By the way, the memory pool of nginx
is one of the fastest pools today (as of year 2018) and it also uses this alignment,
too. To allow a finer granulated usage of the memory space inside the MPZ, the MPZ
implements binning of slots. The size of the memory space alignment for slots
inside the MPZ is 8 bytes (default, configurable).
Pool
The pool is the main object of MPZ. It’s the owner of:
- an array of linked lists for the free
slotsin thepoolgrouped by aligned size of theslots(binning). - a doubly-linked list of the allocated
slabsfrom the OS (stack).
The size of a MPZ pool is 1.040 bytes on 64 bit systems.
Slabs
Every memory block allocated from the OS in MPZ is called slab. A slab consists
of a small metadata (16 bytes on 64 bit systems) and of an 1-to-n number of
slots, where n is defined as MPZ_SLAB_ALLOC_MUL constant (default 16,
configurable).
Regular slots
Every chunk allocated from the pool in MPZ is called slot. A slot consists
of a small metadata (32 bits for a header and 32 bits for a footer) and of a data
part. The data part is the memory space that is returned to a user on an
allocation request. The regular slots have a size of up to 1.024 bytes (default,
configurable) and are managed by the bins-array of the pool.
Huge slots
In cases that the used needs memory space that is larger as the configurable default
of 1.024 bytes, the MPZ will allocate an extra memory space from the OS and assign
this space to a single slot in an extra slab. The differences to the regular
slots are:
- The huge
slabsaren’t managed by the bins-array of thepooland they are consequently not reusable. - If a huge
slabis freed, their memory space is immediately released back to the OS.
Illustrations
Illustration of a MPZ pool
SLABS (stack)
/
||============================================|| \ ||=====================||
|| slab 1 || slab 2 || / || slab n ||
|| || || \ || ||
|| metadata | slots || metadata | slots || / || metadata | slots ||
|| | || | || \ || | ||
|| prev | next | ..... || prev | next | ..... || / || prev | next | ..... ||
||===\======\==========/===/=======\==========|| \ ||==/=======\==========||
\ \ \ / / \ / / \
\ \ \ / / \ ... / \
\ NULL \____/ / \________/ \/ NULL
\__________________/
BINS (binning)
||===============================================================||
|| 0 | ..... | 7 | 8 | ..... | 96 | ..... | 127 || <<< bin-index
||---------------------------------------------------------------||
|| 8 | ..... | 64 | 72 | ..... | 776 | ..... | 1024 || <<< size in bytes per slot
||===============================================================||
| | |
| | |
|=======| |=======| |=======|
| | | | | |
|=======| |=======| |=======|
| |
| | <<< free slots
|=======| |=======|
| | | |
|=======| |=======|
|
|
|=======|
| |
|=======|
Illustration of the regular MPZ slots inside the bins-list
slot from slab x slot from slab y
/
||==============================================|| \ ||==============================================||
|| header | data part | footer | || / || header | data part | footer | ||
|| | | | || \ || | | | ||
|| (32 bits) | (min 8 bytes) | (32 bits) | next || / || (32 bits) | (min 8 bytes) | (32 bits) | next ||
||===========================================\==|| \ ||===========================================\==||
\ / / \
\____/ NULL
Illustration of a MPZ slot header
The header contains the metadata of the slot. All of the 32 bits of the footer
a filled with 1. This fact allows to detect segmentation fault errors if an
application writes over the allocated memory space (unreliable but simple).
=======
| 31 | <<< "huge" flag (MSB on little-endian machines)
-------
| 30 | <<< "used" flag (allows to detect "double free" errors)
-------
| 29 | <<< currently unused (reserved for possible extensions)
-------
| 28 |\
------- \
| ... | <<< represent the slot size
------- /
| 0 |/ (LSB on little-endian machines)
=======
MPZ API
mpz_pool_create()
Allocates an aligned memory space for the pool, initializes the allocated memory
space to zero and returns a pointer to the pool object. Returns NULL if failed.
mpz_pool_t *mpz_pool_create(mpz_void_t);
mpz_pool_reset()
Resets the allocated memory space to permitt him to be efficiently reused by the
pool. Note that the memory space of huge slots is immediately released back
to the OS.
mpz_void_t mpz_pool_reset(mpz_pool_t *pool);
mpz_pool_destroy()
Destroys the pool. The total allocated memory space inclusive the memory space
of the pool itself is released back to the OS.
mpz_void_t mpz_pool_destroy(mpz_pool_t *pool);
mpz_pmalloc()
Allocates size bytes of the memory space from the pool. If the requested size
is greater than the configurable default of 1.024 bytes, a huge slot is directly
allocated from the OS like described above in the section Huge slots. If the pool
hasn’t enouth free memory space to serve the requested size of bytes, a new slab
is allocated from the OS. Returns NULL if failed.
mpz_void_t *mpz_pmalloc(mpz_pool_t *pool, mpz_csize_t size);
mpz_pcalloc()
Allocates size bytes of the memory space from the pool like mpz_pmalloc()
function. Additionaly, all bits of the allocated memory space are initialized with
zero. Returns NULL if failed.
Note that the `mpz_pcalloc()` function is always slower than the `mpz_pmalloc()`,
because the initializing of the bits in the memory space with zero requires an extra
iteration over the requested memory space.
mpz_void_t *mpz_pcalloc(mpz_pool_t *pool, mpz_csize_t size);
mpz_free()
Releases the allocated memory space back to the pool. The memory space of huge
slots are immediately released back to the OS.
mpz_void_t mpz_free(mpz_pool_t *pool, mpz_void_t *data);
Using MPZ
The user should use the MPZ API. The direct usage of functions outside the MPZ-API
can lead to unpredictable consequences if there is a lack of knowledge about the
internal implementation. To modify or extend the MPZ see the description FOR
DEVELOPER WHO WANT TO ADAPT THE MPZ TO THE REQUIREMENTS OF THEIR APPLICATIONS in
the mpz_core.h.
The MPZ should be used wisely. Note that the MPZ is a high-level application. It’s
a simple interface between the user and the malloc() and the free() functions.
The MPZ doesn’t interact with the OS directly. Any invalid read/write errors are
difficultly to detect than using malloc() function directly. For example, many
users forget to allocate an extra byte to terminate a string with a \0 byte. Here
is a big difference between the allocation of memory space from the malloc() function
and the MPZ. If the user allocates 5 bytes of memory space from the MPZ to write
a Hello string into the allocated memory space, the MPZ provides at least 8 bytes
of the memory space, where, by contrast, the malloc() function would provide only
5 bytes of really usable memory space. The invalid writing of the final 0-byte could
easy be detected by the direct usage of the malloc() function. Using the MPZ,
this error wouldn’t be detected.
Example
#include "mpz_alloc.h"
int example_function()
{
mpz_pool_t *pool;
void *data;
pool = mpz_pool_create();
if (NULL == pool) {
return 1;
}
data = mpz_pmalloc(pool, 123);
if (NULL == data) {
mpz_pool_destroy(pool);
return 1;
}
/* Do here something with the "data"... */
mpz_free(pool, data);
/* Do other operations with the pool... */
mpz_pool_destroy(pool);
return 0;
}