WARNING: The online documentation has moved to https://docs.pjsip.org.

Visit the new documentation at https://docs.pjsip.org:

BLOG | DOCUMENTATION | GITHUB

Home --> Documentations --> PJLIB Reference

Fast Memory Pool

Memory pools allow dynamic memory allocation comparable to malloc or the new in operator C++. Those implementations are not desirable for very high performance applications or real-time systems, because of the performance bottlenecks and it suffers from fragmentation issue. More...

Modules

 Memory Pool Object
 The memory pool is an opaque object created by pool factory. Application uses this object to request a memory chunk, by calling pj_pool_alloc(), pj_pool_calloc(), or pj_pool_zalloc(). When the application has finished using the pool, it must call pj_pool_release() to free all the chunks previously allocated and release the pool back to the factory.
 
 Pool Factory and Policy
 A pool object must be created through a factory. A factory not only provides generic interface functions to create and release pool, but also provides strategy to manage the life time of pools. One sample implementation, pj_caching_pool, can be set to keep the pools released by application for future use as long as the total memory is below the limit.
 
 Caching Pool Factory
 Caching pool is one sample implementation of pool factory where the factory can reuse memory to create a pool. Application defines what the maximum memory the factory can hold, and when a pool is released the factory decides whether to destroy the pool or to keep it for future use. If the total amount of memory in the internal cache is still within the limit, the factory will keep the pool in the internal cache, otherwise the pool will be destroyed, thus releasing the memory back to the system.
 
 Stack/Buffer Based Memory Pool Allocator
 Stack/buffer based pool.
 

Detailed Description

Memory pools allow dynamic memory allocation comparable to malloc or the new in operator C++. Those implementations are not desirable for very high performance applications or real-time systems, because of the performance bottlenecks and it suffers from fragmentation issue.

PJLIB's Memory Pool

Advantages

PJLIB's pool has many advantages over traditional malloc/new operator and over other memory pool implementations, because:

  • unlike other memory pool implementation, it allows allocation of memory chunks of different sizes,
  • it's very very fast.
    Memory chunk allocation is not only an O(1) operation, but it's also very simple (just few pointer arithmetic operations) and it doesn't require locking any mutex,
  • it's memory efficient.
    Pool doesn't keep track individual memory chunks allocated by applications, so there is no additional overhead needed for each memory allocation (other than possible additional of few bytes, up to PJ_POOL_ALIGNMENT-1, for aligning the memory). But see the Caveats below.
  • it prevents memory leaks.
    Memory pool inherently has garbage collection functionality. In fact, there is no need to free the chunks allocated from the memory pool. All chunks previously allocated from the pool will be freed once the pool itself is destroyed. This would prevent memory leaks that haunt programmers for decades, and it provides additional performance advantage over traditional malloc/new operator.

Even more, PJLIB's memory pool provides some additional usability and flexibility for applications:

  • memory leaks are easily traceable, since memory pool is assigned name, and application can inspect what pools currently active in the system.
  • by design, memory allocation from a pool is not thread safe. We assumed that a pool will be owned by a higher level object, and thread safety should be handled by that object. This enables very fast pool operations and prevents unnecessary locking operations,
  • by default, the memory pool API behaves more like C++ new operator, in that it will throw PJ_NO_MEMORY_EXCEPTION exception (see Exception Handling) when memory chunk allocation fails. This enables failure handling to be done on more high level function (instead of checking the result of pj_pool_alloc() everytime). If application doesn't like this, the default behavior can be changed on global basis by supplying different policy to the pool factory.
  • any memory allocation backend allocator/deallocator may be used. By default, the policy uses malloc() and free() to manage the pool's block, but application may use different strategy, for example to allocate memory blocks from a globally static memory location.

Performance

The result of PJLIB's memory design and careful implementation is a memory allocation strategy that can speed-up the memory allocations and deallocations by up to 30 times compared to standard malloc()/free() (more than 150 million allocations per second on a P4/3.0GHz Linux machine).

(Note: your mileage may vary, of course. You can see how much PJLIB's pool improves the performance over malloc()/free() in your target system by running pjlib-test application).

Caveats

There are some caveats though!

When creating pool, PJLIB requires applications to specify the initial pool size, and as soon as the pool is created, PJLIB allocates memory from the system by that size. Application designers MUST choose the initial pool size carefully, since choosing too big value will result in wasting system's memory.

But the pool can grow. Application designer can specify how the pool will grow in size, by specifying the size increment when creating the pool.

The pool, however, cannot shrink! Since there is no function to deallocate memory chunks, there is no way for the pool to release back unused memory to the system. Application designers must be aware that constant memory allocations from pool that has infinite life-time may cause the memory usage of the application to grow over time.

Using Memory Pool

This section describes how to use PJLIB's memory pool framework. As we hope the readers will witness, PJLIB's memory pool API is quite straightforward.

Create Pool Factory

First, application needs to initialize a pool factory (this normally only needs to be done once in one application). PJLIB provides a pool factory implementation called caching pool (see Caching Pool Factory), and it is initialized by calling pj_caching_pool_init().

Create The Pool

Then application creates the pool object itself with pj_pool_create(), specifying among other thing the pool factory where the pool should be created from, the pool name, initial size, and increment/expansion size.

Allocate Memory as Required

Then whenever application needs to allocate dynamic memory, it would call pj_pool_alloc(), pj_pool_calloc(), or pj_pool_zalloc() to allocate memory chunks from the pool.

Destroy the Pool

When application has finished with the pool, it should call pj_pool_release() to release the pool object back to the factory. Depending on the types of the factory, this may release the memory back to the operating system.

Destroy the Pool Factory

And finally, before application quites, it should deinitialize the pool factory, to make sure that all memory blocks allocated by the factory are released back to the operating system. After this, of course no more memory pool allocation can be requested.

Example

Below is a sample complete program that utilizes PJLIB's memory pool.

#include <pjlib.h>
#define THIS_FILE "pool_sample.c"
static void my_perror(const char *title, pj_status_t status)
{
PJ_PERROR(1,(THIS_FILE, status, title));
}
static void pool_demo_1(pj_pool_factory *pfactory)
{
unsigned i;
pj_pool_t *pool;
// Must create pool before we can allocate anything
pool = pj_pool_create(pfactory, // the factory
"pool1", // pool's name
4000, // initial size
4000, // increment size
NULL); // use default callback.
if (pool == NULL) {
my_perror("Error creating pool", PJ_ENOMEM);
return;
}
// Demo: allocate some memory chunks
for (i=0; i<1000; ++i) {
void *p;
p = pj_pool_alloc(pool, (pj_rand()+1) % 512);
// Do something with p
...
// Look! No need to free p!!
}
// Done with silly demo, must free pool to release all memory.
}
int main()
{
pj_status_t status;
// Must init PJLIB before anything else
status = pj_init();
if (status != PJ_SUCCESS) {
my_perror("Error initializing PJLIB", status);
return 1;
}
// Create the pool factory, in this case, a caching pool,
// using default pool policy.
pj_caching_pool_init(&cp, NULL, 1024*1024 );
// Do a demo
pool_demo_1(&cp.factory);
// Done with demos, destroy caching pool before exiting app.
return 0;
}
pj_status_t pj_init(void)
int pj_status_t
Definition: types.h:68
@ PJ_SUCCESS
Definition: types.h:93
void pj_caching_pool_destroy(pj_caching_pool *ch_pool)
void pj_caching_pool_init(pj_caching_pool *ch_pool, const pj_pool_factory_policy *policy, pj_size_t max_capacity)
void * pj_pool_alloc(pj_pool_t *pool, pj_size_t size)
pj_pool_t * pj_pool_create(pj_pool_factory *factory, const char *name, pj_size_t initial_size, pj_size_t increment_size, pj_pool_callback *callback)
void pj_pool_release(pj_pool_t *pool)
int pj_rand(void)
#define PJ_PERROR(level, arg)
Definition: errno.h:175
#define PJ_ENOMEM
Definition: errno.h:347
Definition: pool.h:824
pj_pool_factory factory
Definition: pool.h:826
Definition: pool.h:667
Definition: pool.h:310

More information about pool factory, the pool object, and caching pool can be found on the Module Links below.

 


PJLIB Open Source, high performance, small footprint, and very very portable framework
Copyright (C) 2006-2009 Teluu Inc.