Copyright (c) Hyperion Entertainment and contributors.

Exec Memory Pools

From AmigaOS Documentation Wiki
Revision as of 19:24, 23 August 2015 by Daniel Jedlicka (talk | contribs) (Memory Pool Organization: fixed a typo.)
Jump to navigation Jump to search

Introduction

A memory pool is a block of memory that an application allocates for all its memory needs. The application draws from this pool rather than the system's free memory list whenever it requires memory.

Memory pools add to the efficiency of the system and applications in two ways. The first is a potential decrease in memory fragmentation. Memory fragmentation is a condition where system memory is made up of blocks of allocated memory interspersed with blocks of free memory. The second benefit of memory pools is faster memory allocations and deallocations.

By having an application take a pool of memory and allocate from it, fragmentation of system memory is decreased. An application may require six allocations ranging from 20 to 678 bytes in size which can be scattered throughout system memory. However, if those same six allocations are taken from a pool, the fragmentation occurs within the pool, but as far as the system is concerned, it's missing one large block instead of six small ones.

An application using a memory pool will have faster memory allocations because the memory list of a pool is smaller, and therefore easier to traverse than the memory list of the system memory. For deallocations, it's even faster because deleting the pool itself deletes all the individual allocations made from it instead of having to deallocate each piece that was allocated.

If you know that your memory pool allocations will always be of the same size, prefer using an item pool.

Memory Pool Organization

A memory pool consists of units called puddles. Other than that, it has no formal organization. There is no pool structure defined anywhere. All you know about a pool is its address. Exec's pool manager handles the rest of the details.

When you create a pool, you specify the size of its puddles and a threshold value, not the size of the pool. This is because memory pools are dynamic, they have no fixed size. The pool manager takes the memory for your application from the puddles. As you require memory, the pool manager expands the pool by adding puddles, and as you free memory, the pool manager shrinks the pool by deleting puddles.

The size of the puddles is important because the pool manager will try to use as much of a puddle as possible before adding a new puddle. Each time you allocate memory from the pool, the pool manager takes the memory from a puddle unless an allocation exceeds the amount of memory remaining in a puddle. If the allocation request exceeds the memory remaining in a puddle, the pool manager adds a new puddle. If the allocation exceeds the pool's puddle size, the pool manager will create a special over-sized puddle to accommodate the allocation.

The key is to use all the drops in a puddle. If you create a pool with puddles of 200 bytes and allocate 150 bytes at a time, you'll waste 50 bytes in every puddle. Set the puddle size in accordance with your memory requirements.

The threshold value is the size of the largest allocation to use within a single puddle. An allocation request larger than the threshold value causes the pool manager to add a new puddle. Ideally, the threshold value should be the size of the largest allocation you will make.

The relationship between puddle size and threshold can be tuned for optimal utilization of a pool. Threshold sizes cannot exceed puddle sizes and are recommended to be one half the puddle size, so that you can fit two of your largest allocations in a single puddle. However, if you are going to have a mix of large and small allocations, you might want to set a threshold value that allocates a majority of a puddle for a large allocation, and then uses up the remainder of the puddle with the smaller allocations.

Creating a Memory Pool

You create a memory pool by calling AllocSysObject() with the ASOT_MEMPOOL type and specifying the puddle size, threshold size and memory type. The memory type must be of type MEMF_PRIVATE or MEMF_SHARED. The MEMF_ANY type may be used to indicate that either of these memory types is suitable. The MEMF_CLEARED flag may also be specified to automatically clear memory allocated from the pool.

If successful, AllocSysObject() returns the address of the memory pool.

APTR mem_pool = IExec->AllocSysObjectTags(ASOT_MEMPOOL,
  ASOPOOL_Puddle, 4096,
  ASOPOOL_Threshold, 2048,
  TAG_END);
 
if (mem_pool != NULL)
{
  // ...
}
else
  IDOS->Printf("Pool could not be created.\n");

The example above attempts to create a pool of memory with a puddle size of 4096 bytes and a threshold size of 2048 bytes.

If your application requires memory of different types (for example, private memory and shared memory), it must create a pool for each type.

Take note, again, that all you know about a pool is its address. Do not poke around it trying to figure out its organization. It's private for a reason!

Allocating Memory from a Pool's Puddles

Memory is obtained from a pool's puddles by calling AllocPooled(). AllocPooled() requires a pointer to the memory pool and the size of the memory block needed. If successful, AllocPooled() returns a pointer to the memory.

struct Rectangle *rect = IExec->AllocPooled(mem_pool, sizeof(struct Rectangle);
 
if (rect != NULL)
{
  // ...
}
else
  IDOS->Printf("Memory could not be allocated.\n");

Freeing Memory from a Pool's Puddles

An application can free a block of memory it allocated from a pool by calling FreePooled():

IExec->FreePooled(mem_pool, mem_drop, mem_size);

This function requires a pointer to the memory pool (mem_pool), a pointer to the block of memory being freed (mem_drop), and the size of the memory being freed (mem_size).

Deleting a Memory Pool

A memory pool is deleted by calling FreeSysObject() with the ASOT_MEMPOOL type and a pointer to the memory pool you wish to delete.

IExec->FreeSysObject(ASOT_MEMPOOL, mem_pool);

Deleting a pool also frees the puddles in it. This is quicker than doing individual FreePooled() calls. For example, a text editor will allocate memory for each line of the file being edited. When the editor is exited, each of the allocations has to be freed. With FreeSysObject(), it would take only one call instead of many.

Concurrent Access

Memory pools may be protected from concurrent access by specifying the ASOPOOL_Protected tag when creating the pool.

APTR mem_pool = IExec->AllocSysObjectTags(ASOT_MEMPOOL,
  ASOPOOL_MFlags, MEMF_SHARED,
  ASOPOOL_Puddle, 4096,
  ASOPOOL_Threshold, 2048,
  ASOPOOL_Protected, TRUE,
  TAG_END);

In this case, the memory is being shared between two Tasks or Processes so it must be of type MEMF_SHARED. By specifying that the memory pool is protected we are guaranteed all operations performed on the memory pool are thread safe.

Function Reference

The following table gives a brief description of the Exec functions that control memory pools. See the SDK/Autodocs for more details about each call.

Function Description
AllocPooled() Allocate memory with the pool manager.
AllocSysObject(ASOT_MEMPOOL) Allocate and initialize a new memory pool.
AllocVecPooled() Allocate memory with the pool manager and track size.
FreePooled() Free pooled memory.
FreeSysObject(ASOT_MEMPOOL) Free a memory pool.
FreeVecPooled() Free pooled memory allocated with AllocVecPooled().

These Exec functions are now practically obsolete and not recommended for use in new code:

Function Description
CreatePool() Use AllocSysObject(ASOT_MEMPOOL) instead.
DeletePool() Use FreeSysObject(ASOT_MEMPOOL) instead.