MEMCLUSTER(3)

NAME
meminit, memget, memput, memstats - memory allocation/deallocation system

SYNOPSIS
#include <isc/memcluster.h>
void *
memget(size_t size);
void
memput(void *mem, size_t size);
void
memstats(FILE *out);

DESCRIPTION
These functions access a memory management system which allows callers to not fragment memory to the extent which can ordinarily occur through many random calls to malloc(3). Instead, memget() gets a large contiguous chunk of blocks of the requested size and parcels out these blocks as requested. The symmetric call is memput(), which callers use to return a piece of memory obtained from memget(). Statistics about memory usage are returned by memstats(), which prints a report on the stream out.

  INTERNALS
Internally, linked lists of free memory blocks are stored in an array. The size of this array is determined by the value MEM_FREECOUNT, currently set to 1100. In general, for any requested blocksize “size”, any free blocks will be stored on the linked list at that index. No free lists are managed for blocks greater than or equal to MEM_FREECOUNT bytes; instead, calls to malloc(3) or free(3) are made, directly.

Since the blocks are actually stored as linked lists, they must at least be large enough to hold a pointer to the next block. This size, which is SMALL_SIZE_LIMIT, is currently defined as

    #define SMALL_SIZE_LIMIT sizeof(struct { void *next; })

Both memget() and memput() enforce this limit; for example, any call to memget() requesting a block smaller than SMALL_SIZE_LIMIT bytes will actually be considered to be of size SMALL_SIZE_LIMIT internally. (Such a caller request will be logged for memstats() purposes using the caller-requested size; see the discussion of memstats(), below, for more information.)

Additionally, the requested size will be adjusted so that when a large malloc(3)-d chunk of memory is broken up into a linked list, the blocks will all fall on the correct memory alignment boundaries. Thus, one can conceptualize a call which mentions size as resulting in a new_size which is used internally.

In order to more efficiently allocate memory, there is a “target” size for calls to malloc(3). It is given by the pre-defined value MEM_TARGET, which is currently 4096 bytes. For any requested block size, enough memory is malloc(3)-d in order to fill up a block of about MEM_TARGET bytes. [NOTE: For allocations larger than MEM_TARGET/2 bytes, there is a “fudge factor” introduced which boosts the target size by 25% of MEM_TARGET. This means that enough memory for two blocks will actually be allocated for any size such that (MEM_TARGET/ 3) < size < (MEM_TARGET*5/8), provided that the value of MEM_FREECOUNT is at least as large as the upper limit shown above.]

  FUNCTION DESCRIPTIONS

The function memget() returns a pointer to a block of memory of at least the requested size. After adjusting size to the value new_size as mentioned above in the INTERNALS subsection, the internal array of free lists is checked. If there is no block of the needed new_size, then memget() will malloc(3) a chunk of memory which is as many times as new_size will fit into the target size. This memory is then turned into a linked list of new_size-sized blocks which are given out as requested; the last such block is the first one returned by memget(). If the requested size is zero or negative, then NULL is returned and errno is set to EINVAL; if size is larger than or equal to the pre-defined maximum size MEM_FREECOUNT, then only a single block of exactly size will be malloc(3)-d and returned.

The memput() call is used to return memory once the caller is finished with it. After adjusting size the the value new_size as mentioned in the INTERNALS subsection, above, the block is placed at the head of the free list of new_size-sized blocks. If the given size is zero or negative, then errno is set to EINVAL, as for memget(). If size is larger than or equal to the pre-defined maximum size MEM_FREECOUNT, then the block is immediately free(3)-d.

NOTE: It is important that callers give memput() only blocks of memory which were previously obtained from memget() if the block is actually less than SMALL_SIZE_LIMIT bytes in size. Since all blocks will be added to a free list, any block which is not at least SMALL_SIZE_LIMIT bytes long will not be able to hold a pointer to the next block in the free list.

The memstats() function will summarize the number of calls to memget() and memput() for any block size from 1 byte up to (MEM_FREECOUNT - 1) bytes, followed by a single line for any calls using a size greater than or equal to MEM_FREECOUNT; a brief header with shell-style comment lines prefaces the report and explains the information. The FILE pointer out identifies the stream which is used for this report. Currently, memstat() reports the number of calls to memget() and memput() using the caller-supplied value size; the percentage of outstanding blocks of a given size (i.e., the percentage by which calls to memget() exceed memput()) are also reported on the line for blocks of the given size. However, the percent of blocks used is computed using the number of blocks allocated according to the internal parameter new_size; it is the percentage of blocks used to those available at a given new_size, and is computed using the total number of caller “gets” for any caller size-s which map to the internally-computed new_size. Keep in mind that new_size is generally not equal to size, which has these implications:

1.  For size smaller than SMALL_SIZE_LIMIT, memstat() will show statistics for caller requests under size, but "percent used" information about such blocks will be reported under SMALL_SIZE_LIMIT-sized blocks.

2.  As a general case of point 1, internal statistics are reported on the the line corresponding to new_size, so that, for a given caller-supplied size, the associated internal information will appear on that line or on the next line which shows "percent used" information.

NOTE: If the caller returns blocks of a given size and requests others of size-s which map to the same internal new_size, it is possible for memstats() to report usage of greater than 100% for blocks of size new_size. This should be viewed as A Good Thing.

RETURN VALUES
The function memget() returns a non-NULL pointer to a block of memory of the requested size. It returns NULL if either the size is invalid (less than or equal to zero) or a malloc(3) of a new block of memory fails. In the former case, errno is set to EINVAL; in the latter, it is set to ENOMEM.

Neither memput() nor memstats() return a value.

ERRORS
errno is set as follows:

EINVAL
    set by both memget() and memput() if the size is zero or negative

ENOMEM
    set by memget() if a call to malloc(3) fails

SEE ALSO
free(3), malloc(3).

AUTHORS
Steven J. Richardson and Paul Vixie, Vixie Enterprises.