The T3E Memory Pool allows you managing the memory in your C programs in a more simple and effective way than with standard allocators like e.g. malloc(). It allows the dynamic allocation of one or more fixed-sized blocks of heap memory at once; chunks of these blocks can then freely be used and reused in your program as with the standard allocator but without the need of freeing them each time. All memory pools can be destroyed in one step at the end of the program.
This memory pool manager is not particularly much faster than malloc(), but its low weight and simplicity, specially in complicated malloc()-free() logics structures like in loops or recursions, makes its implementation attractive.
T3E Memory Pool offers a default pool, which can be used without any explicit allocation, this facilitates a quick start if we need fast implementation and if just one pool is enough. Up to 99 additional pools are available with one single allocation for each one. Analogous functions like malloc(), calloc() and realloc() are available for the allocation of the memory chunks within each of the pools. With a certain parameter it can be specified from which of the pools the memory will be taken.
The memory pool manager can be used in a multithreaded architecture by just following a few simple rules. It also supports the implementation on a Unix-like as well as on Windows platforms.
Each memory pool assigns by default a block of 250 KB from the heap to start with the actual memory allocation. Whenever this block is fully used and the next allocation exceeds the amount of free memory available in this block, another block with the same default size is created. Whenever an allocation with a size bigger than the default block size is required then that size is used for the block creation. The total amount of memory can grow up to a default value of 3 GB for 32 bits platforms and 5 GB for 64 bits, this value can be adjusted if needed.
Whenever we create a memory pool, we need to store its ID for further access. When we have more than one memory pool, switching from one to another can easily be done with its ID. So to allocate memory from a particular memory pool, we can either make that memory pool currently active, we can push it in the memory pools stack, or we can specify it directly during allocation as parameter.
When we do not need the content of a memory pool (i.e. the allocated memory from all its blocks), we can clear its content for a later use, this will make all the memory in that particular pool immediately available for a later use in the program. But in case we do not need the pool anymore we can directly delete it, this will release from the heap all the allocated resources for that particular pool. In the same way, before the program ends we have to release the memory allocated for all memory pools, this can be done with just one single command.
The memory pool manager offers a couple of tools that could be helpful for memory debugging purposes. It offers the statistics of usage of all memory pools printed to a defined output. This includes its ID, name, number of blocks assigned, total memory used and total of free memory. Additionally to the statistics, the memory pool manager can also dump to a text file the content of all the memory pools in hexadecimal format, this is split by pool and by assigned blocks.
A feature for defining an own customized trace function is also provided, this will be used by the above functionalities for displaying the provided information in the desired format and to the desired file descriptor.
The manager does not display any errors on the screen, in case of error the client code should check the return code of each of the functions and display the corresponding error message. For helping on this task, a function is provided which shows a descriptive message in case an error is encountered.
It is safe to use the memory pool manager in a multithreading environment. A few conditions should be kept for the correct operation. Firstly the default memory pool should not be used by any other thread than the main one. And secondly every thread should create and use its own memory pool. The attempt by a thread of using the pool of a different thread will lead to an error.
On the second version of the library the memory pool manager was adapted to run also on the Windows platform additionally to the already supported Unix/Linux platform.
The T3E Memory Pool also communicates with valgrind for a more accurate memory check when debugging with this tool on the Linux platform.
Functions for allocation of memory aligned to a particular multiple of memory address are provided. The memory delivered is aligned by default to 8 bytes
The library can be downloaded from here. On Unix-like platforms the library can be built by just running the make command on the root directory of the package, this creates the static library "libmp.a" in that folder. The compiler "gcc" is used, if needed other compilers can be used (e.g. CC on Solaris) by editing the makefile.
On Windows a provided Visual Studio C project (win_lib) can be opened and by right-clicking on the project's name and selecting "build", the library called "mp.lib" is created inside the "Debug" folder.
The multi-threading support is active by default, on Unix-like platforms this requires to compile with the POSIX pthread library. In case the T3E Memory Pool library is not used in a multi-threading environment, this can be turned off by setting the macro MP_THREAD_SAFE to 0 in the "mp.h" file before the build..
The library doesn't need any particular installation, it can simply be added to your projects.
On Unix-like platforms just add the "mp.h" header file and the library "libmp.a" to be accessible to your project via your makefile, or from the command line.
On Windows add the "mp.h" header file to your project and the path to the "mp.lib" library to the Linker's input configuration.
The T3E Memory Pool instructions can be used in any program by just including the header file "mp.h" at the top of each source code file.
Following code shows a very simple example on how the T3E Memory Pool manager works. It uses the function mpmalloc() to allocate memory for the 10 elements of an array of integers. The allocation is done from the default memory pool. Each element is assigned with a value which later, in a different pool, is printed out to the screen. The function mpdel_all() is then used to delete the memory pool and to release all the allocated memory from the heap..
All sample codes can be found in the folder examples inside the library package and can be compiled with the make command. For compiling a certain example in particular the following command can be used on Linux e.g.:
The following code shows a basic example on how to use more than one memory pool at the same time. First it starts by using the function mpmalloc() to allocate a piece of memory from the default memory pool. The same operation is done later but each time from a different memory pool which were first created and then set for use (with mpset()). At the end before removing all memory pools with the function mpdel_all() another function called mpprn() is used for printing out on the screen the statistics of each of the pools used.
The output would be following
Following we have a more realistic usage of two memory pools application. The idea is to use one of the pools for allocating memory for the structures and the other one for the allocation of their elements.
Following the output
Additional examples can be found also in the root folder of the package. The file "test.c" has a system test of all the single functions available in the package. The file "test_thread.c" does a system test of the initialization of the memory pools for threads. Both are automatically built when building the project on a Unix-like platform. MSVS projects for these two are also available in the package ("win_test" and "win_test_thread" respectively)
int mpnew(char *descr;)
The mpnew() function creates/initializes a new memory pool with the description pointed to by descr. If the descr is NULL then "-" is used as description. If the manager runs in a multithreading environment it assigns the thread ID to the memory pool so that only that thread can access it during its lifetime.
This function returns the memory pool ID of the allocated memory pool.
int mppush(int mpid);
The mppush() function moves the current memory pool ID to the stack and makes mpid the current one. The stack has only one element, for now.
Upon successful completion this function returns MP_ERRNO_SUCCESS. In case mpid is out of the limits of allowed memory pools IDs, it returns MP_ERRNO_MPID. And in case the memory pool corresponding to mpid is not initialized with mpnew() then the function returns MP_ERRNO_NOIN.
The mppop() function sets the previous Memory Pool ID moved to the stack by mppush() as the current one.
When no error, the function returns MP_ERRNO_SUCCESS. In case there was no memory pool ID stored in the stack the function returns MP_ERRNO_NOPP.
The mpget() function gets the current memory pool ID set by mpset() and the one which is currently used for the allocation.
This function returns the actual memory pool ID.
int mpset(int mpid);
The mpset() function tells the memory pool manager that mpid should be the active memory pool from which all further allocations are made.
Upon successful completion this function returns MP_ERRNO_SUCCESS. If the memory pool referred by mpid is not yet initialized by mpnew() then it returns MP_ERRNO_NOIN. If mpid is out the limits allowed by the library then the function returns MP_ERRNO_MPID. If the thread trying to set the memory pool is not the same one that initialized/created it with mpnew() it returns MP_ERRNO_THRD.
int mpdel(int mpid);
The mpdel() function frees all resources (memory blocks) allocated for the memory pool referrenced by mpid and initializes it, this last happens unless mpid refers to the default memory pool, which cannot be uninitialized.
On successful completion this function returns MP_ERRNO_SUCCESS. If mpid is out the limits allowed by the library then the function returns MP_ERRNO_MPID. If the thread trying to delete the memory pool is not the same one that initialized/created it with mpnew() it returns MP_ERRNO_THRD.
The mpdel_all() function frees the resources (memory blocks) allocated for each of the memory pools and initializes each of the pools except by the default one, which is always kept initialized.
Note: This function should always be called by the main thread and only when all the other threads stopped using any of the memory pools. Doing otherwise may cause unexpected behaviour.
This function always returns MP_ERRNO_SUCCESS.
int mpclr(int mpid);
The mpclr() function resets to zero all the memory usage pointers used by the memory pool referred by mpid making immediate availability of the memory blocks allocated for that pool.
On successful completion this function returns MP_ERRNO_SUCCESS. If mpid is out the limits allowed by the library then the function returns MP_ERRNO_MPID. If the thread trying to clear the memory pool is not the same one that initialized/created it using mpnew() it returns MP_ERRNO_THRD.
The mpprn() function prints on the screen the different information of each of the created/initialized memory pools. For the display it uses the output set by the function mptrc_set_fn() or the default standard output if no other output was set.
The information showed in the statistics are the memory pool ID, the memory pool name, the number of memory blocks assigned to that pool, the total memory size allocated for all the blocks, the total free memory among all memory pools and the percentages of used and free memory.
Example of the output:
This function doesn't returns any value.
int mpdmp(char *filename);
The function mpdmp() dumps to the file pointed by filename the content of the memory allocated for each of memory blocks assigned to the initialized memory pools. The filename can contain the file path, if no path is specified the file is created in the current directory.
The function will try to skip dumping zero bytes blocks and it will replace them with the string "(skipped zero bytes...)"
The dump is split by memory pool, showing the memory pool ID and its name, and by its memory blocks, showing in the title the sequence of the block number, its size and its range of memory address.
Upon successfully termination this function returns MP_ERRNO_SUCCESS. If the function is called with filename equal NULL then it returns MP_ERRNO_PARM. In case there is any error delivered by the system function fopen() when opening the file, the function returns MP_ERRNO_SYSE, the mperrno variable is also set with the same value in order to get the corresponding error message with mpstrerror().
Example of the output dumped to filename:
void mpset_memlim(size_t size);
The mpset_memlim() function allows setting the maximum memory limit in bytes that can be allocated by all memory pools at the same time. Any attempt of allocation above this limit causes an error. The default maximum memory limit set automatically by the manager during startup is of 3 GB for 32 bits platforms and 5 GB for 64 bits platforms.
This function does not returns any value.
The mpget_memlim() function delivers the current maximum memory limit of the memory pool manager in bytes.
void mpset_blksz(size_t size);
The function mpset_blksz() allows setting the size in bytes of the blocks that are allocated for each memory pool. The default size used by the manager is of 250 KB.
This function does not returns any value.
The mpget_blksz() function returns the size of the memory blocks used by the manager for allocating memory from the heap for each of the memory pools.
void *mpmalloc(size_t size);
void mpfree(void *ptr);
void *mpcalloc(size_t nelem, size_t size);
void *mprealloc(void *ptr, size_t size);
void *mpmemalign(size_t alignment, size_t size);
void *mpmalloc_mpid(size_t size, int mpid);
void *mpcalloc_mpid(size_t nelem, size_t size, int mpid);
void mpfree_mpid(void *ptr, int mpid);
void *mprealloc_mpid(void *ptr, size_t size, int mpid);
void *mpmemalign_mpid(size_t alignment, size_t size, int mpid);
The mpmalloc() function is analog to the malloc() function and allocates size bytes and returns a pointer to the allocated memory within the active memory pool. The memory is not initialized. If size is 0, then mpmalloc() returns anyway unique pointer.
The mpfree() function is actually a dummy function that doesn't do anything. It exists only as analogy to the free() function. Due to the current architecture of the memory pool manager each chunk of memory requested to the manager is taken sequentially from the pool and its space is not reused until the whole pool is clear or freed until the pool is deleted.
The mpcalloc() function is analog to the calloc() function and allocates memory for an array of nelem elements of size bytes each and returns a pointer to the allocated memory within the active memory pool.
The memory is set to zero. If nelem or size is 0, then mpcalloc() returns anyway unique pointer.
The mprealloc() function is analog to the realloc() function and changes the size of the memory block pointed to by ptr to size bytes. The contents will be unchanged in the range from the start of the region up to the minimum of the old and new sizes. If the new size is larger than the old size, the added memory will not be initialized. If ptr is NULL, then the call is equivalent to mpmalloc(size), for all values of size; if size is equal to zero, and ptr is not NULL, then anyway a pointer is delivered. Unless ptr is NULL, it must have been returned by an earlier call to mpmalloc(), mpcalloc() or mprealloc().
The function mpmemalign() is analog to the memalign() function and allocates size bytes and returns a pointer to the allocated memory within the current memory pool. The memory address will be multiple of alignment, which must be a power of two and a multiple of sizeof(void *). If size is 0 then mpmemalign returns anyway an unique pointer.
The functions mpmalloc_mpid(), mpfree_mpid(), mpcalloc_mpid(), mprealloc_mpid() and mpmemalign_mpid() are equivalent to the functions mpmalloc(), mpfree(), mpcalloc(), mprealloc() and mpmemalign() respectively, except that they are called additionally with mpid, which indicates explicitely from which particular memory pool that chunk of memory will be taken, no matter what the currently active memory pool is.
char *mpstrdup(const char *s1);
char *mpstrdup_mpid(const char *s1, int mpid);
The mpstrdup() function is analog to the strdup() function and returns a pointer to the new string which is a duplicate of the string s1. Memory for the new string is obtained with mpmalloc() from the currently active memory pool.
The mpstrdup_mpid() function equivalent to the mpstrdup() function, except that it is called additionally with mpid, which indicates explicitlyfrom which particular memory pool that chunk of memory will be taken, no matter what the currently active memory pool is.
int mpasprintf(char **strp, const char *fmt, ...);
int mpasprintf_mpid(char **strp, int mpid, const char *fmt, ...);
The mpasprintf() function is analog to the asprintf() function and allocates a string large enough to hold the output including the terminating null byte ('\0'), and returns a pointer to it via the first argument.
The mpasprintf_mpid() function equivalent to the mpasprintf() function, except that it is called additionally with mpid, which indicates explicitely from which particular memory pool that chunk of memory will be taken, no matter what the currently active memory pool is.
The mpstrerror() function is analog to the strerror() function and returns a pointer to a string that describes the error code set by any of the memory pool manager functions an error is encountered. (For example if the code set by any function is MP_ERRNO_NOMM, the returned description will be "Out of memory".) This string is read-only and may not be modified. The mpstrerror() is thread safe. The string always includes a terminating null byte ('\0').
The mpstrerror() function returns the appropriate error description string, or and "Error number not recognized" message if the error number is unknown.
In case the error is due to a system error, the result of the function strerror(errno) is delivered.
The "mp.h" header file defines the internal integer variable mperrno, analog to errno and which is set by memory pool manager functions calls in the event of an error to indicate what went wrong. Its value is significant only when the return value of the call indicates an error (i.e. a negative value or NULL); a function that succeeds is allowed to change mperrno.
mperrno is thread-local; setting it in one thread does not affect its value in any other thread.
Below is a list of the symbolic error names that are defined for this library.
|MP_ERRSTR_MPID||Memory pool ID out of range|
|MP_ERRSTR_NOMM||Out of memory|
|MP_ERRSTR_EXMM||Memory limit exceeded|
|MP_ERRSTR_ALLO||Error allocating memory|
|MP_ERRSTR_EXAL||Alignment is not bigger than void* or not multiple of 2|
|MP_ERRSTR_NOIN||Memory pool is not the default (0) and it is not initialized: use mpnew() first|
|MP_ERRSTR_EXMP||Limit of number of Memory Pools exceeded|
|MP_ERRSTR_NOPP||Nothing to pop, use first mppush()|
|MP_ERRSTR_DISP||Error displaying a message|
|MP_ERRSTR_PARM||Error on parameter passed to the function|
|MP_ERRSTR_THRD||Expected different thread ID|
int mptrc_set_fn(int (*function)(FILE *fd, char *fmt, va_list ap));
The mptrc_set_fn() function allows setting a user defined function to control the format in which the functions mpprn() and mpdmp() display their output. For example the user has a log file with a certain format, say with process ID and timestamp. Then a customized function can be implemented so mpprn() also writes the process ID and the timestamp when showing the memory pool statics.
A way of implementation is as following example: