6 Memory Usage¶
The NDK requires RAM memory. There are several ways to reduce the amount of RAM memory used. The following sections give an overview of each type of memory and link to ways to reduce its usage.
6.1 Static Network Buffers¶
The NDK statically defines network (packet) buffers. The Packet Buffer Manager (PBM) component is responsible for managing these buffers.
You can configure the size and placement of these buffers with SysConfig as detailed in section 2.2.3 of this guide.
6.1.1 About Ethernet Frame Buffers¶
Each frame buffer is used to store a single Ethernet frame.
Typically the size needed for an Ethernet frame is 1514 octets (bytes). This size allows for the 1500-byte Ethernet payload plus the 14-byte Ethernet header (6-byte destination MAC address + 6-byte source MAC address + 2-byte protocol type field). On devices with the data cache enabled, the value of 1536 bytes is generally used to allow for proper cache line alignment.
There are two main arrays:
#define PKT_NUM_FRAMEBUF /* Value of "Number of PBM Frames" field in SysConfig */
#define PKT_SIZE_FRAMEBUF /* Value of "PBM Frame Buffer Size" field in SysConfig */
/* Data space for packet buffers */
unsigned char ti_ndk_config_Global_pBufMem[PKT_NUM_FRAMEBUF * PKT_SIZE_FRAMEBUF];
unsigned char ti_ndk_config_Global_pHdrMem[PKT_NUM_FRAMEBUF * sizeof(PBM_Pkt)];
During startup, each of the PBM_Pkt
entries in the ti_ndk_config_Global_pHdrMem
array is initialized. Part of that initialization is to assign it a single packet buffer from ti_ndk_config_Global_pBufMem
. The size of the buffer is PKT_SIZE_FRAMEBUF
.
NOTE: Each packet is the same size. The NDK does not support chaining multiple packet buffers to hold a single Ethernet packet.
When a PBM_alloc()
occurs, a PBM_Pkt entry is dequeued from a linked list and given to the requester. PBM_alloc()
is called for the following reasons: * NIMU Ethernet driver to receive incoming packets. * Stack to transmit an application or service (such as ICMP) packet.
NOTE: The NDK does support zero-copy and jumbo packets. These topics are not part of this discussion.
6.1.2 How to Reduce Static Network Buffer RAM Usage¶
Reduce the number of packets: The number of packets can be reduced to gain a significant RAM savings. However, if you reduce the number of packets too much, packets may be dropped when an incoming burst occurs. If this occurs with a TCP packet, the protocol will correct this via retransmission. For UDP, the packet is lost.
Decrease the size of a packet: The size of a packet can be reduced and significant RAM savings can occur. If you know there is a maximum size for the packets that will be received and transmitted, you can reduce the size. There are two risks to consider if you reduce the size of the packet:
An incoming Ethernet packet may be larger than
PKT_SIZE_FRAMEBUF
.An outgoing packet may be larger than
PKT_NUM_FRAMEBUF
. In both cases, the PBM_alloc() uses the Memory Manager (mmAlloc()
) to allocate aPBM_Pkt
entry and a large enough buffer to hold the packet. WhenPBM_free()
is called, the memory is returned to the Memory Manager (mmFree()
). If no memory is available in the Memory Manager, the allocation fails. For most driver implementations, the incoming packet will be dropped as a result.
Unfortunately, there is currently no way to query usage of the packet buffers (for example, to see the high-water mark) or the maximum size requested during the current run.
6.2 Static Internal Memory Manager¶
The NDK has a component called the Memory Manager. This component’s buffers are used as a scratchpad memory resource for many components within the NDK. For example, the NDK_bind()
function needs a little memory or if the DHCP client needs to store the lease option.
You can configure the page count, page size, and placement of this manager with SysConfig as detailed in section 2.2.3 of this guide.
6.2.1 About Memory Manager buffers, buckets, and blocks¶
To reduce external fragmentation and improve performance, there are N buckets in the memory manager. N is determined by the Number of Pages
field in SysConfig. Each bucket manages a buffer of size M (where M is determined by the Page Size
field in SysConfig).
The following arrays hold the bucket information and the large buffer that will be divided between the buckets:
#define RAW_PAGE_SIZE /* Value of "Page Size" field in SysConfig */
#define RAW_PAGE_COUNT /* Value of "Number of Pages" field in SysConfig */
/* P.I.T. (page information table) */
PITENTRY ti_ndk_config_Global_pit[RAW_PAGE_COUNT];
unsigned char ti_ndk_config_Global_pitBuffer[RAW_PAGE_SIZE * RAW_PAGE_COUNT];
During startup, the ti_ndk_config_Global_pitBuffer
buffer is sliced up into buckets. Each bucket in ti_ndk_config_Global_pit
is given a buffer of size RAW_PAGE_SIZE
. Each bucket will slice up its buffer into same-sized blocks at run-time. To reduce internal fragmentation, the valid block sizes are (in MAUs): 48, 96, 128, 256, 512, 1536, and RAW_PAGE_SIZE
.
RAW_PAGE_SIZE
should be a multiple of these sizes to minimize wasted memory, since the buffer of size RAW_PAGE_SIZE
is sliced up into block-size pieces. For example, if RAW_PAGE_SIZE
is 3072, in a 96-MAU bucket there will be 3072/96 = 32 blocks.
When the internal components of the NDK need memory, they call mmAlloc()
. Within mmAlloc()
, each bucket is examined to determine which one matches the requested size the best. This approach reduces the amount of internal fragmentation.
The fixed block-size for a bucket is not determined during start-up. All N buckets are “un-initialized” at start-up and do not have a block size associated with them. When mmAlloc()
is called at run-time, the list of valid block-sizes (48, 96, etc.) is examined to determine the best size to use. The buckets are examined to see if one that is already managing blocks of that size has free blocks available. If such a bucket exists, that bucket is used for this allocation. Otherwise, an uninitialized bucket is used and becomes associated with that block size.
If you look at the ti_ndk_config_Global_pit array
, you might see that it has multiple buckets allocating blocks of the same size. This impacts performance somewhat, but allows a wide variety of memory allocation scenarios to be used with the NDK.
When mmFree()
is called and a bucket has no more allocated blocks, the bucket is uninitialized so that it no longer has an associated block size.
6.2.2 Memory Manager Example¶
Suppose there are 6 buckets (RAW_PAGE_COUNT
= 6), and the size of each bucket is 3072 (RAW_PAGE_SIZE
= 3072). At start-up all buckets are “uninitialized”.
The 6 buckets would have the allocations shown below if mmAlloc()
is called by the NDK with the following sizes (in this order): 62, 1536, 1536, 48, and 1536.
bucket 0: size = 96; number of total blocks: 32 (3072/96); number of free blocks: 31
bucket 1: size = 1536; number of total blocks: 2 (3072/1536); number of free blocks: 0
bucket 2: size = 48; number of total blocks: 64 (3072/48); number of free blocks: 63
bucket 3: size = 1536; number of total blocks: 2 (3072/1536); number of free blocks: 1
bucket 4: un-initialized
bucket 5: un-initialized
If the 62-MAU block and all the 1536-MAU blocks are then freed (and no more allocation occurs), the buckets would have the following allocations:
bucket 0: un-initialized
bucket 1: un-initialized
bucket 2: size = 48; number of total blocks: 64 (3072/48); number of free blocks: 63
bucket 3: un-initialized
bucket 4: un-initialized
bucket 5: un-initialized
A graphical view of the memory manager can be seen with ROV
6.2.3 How to Reduce Memory Manager RAM Usage¶
If is difficult to give concrete guidelines for reducing RAM usages since the Memory Manager is very dynamic and application-dependent. You can call the _mmCheck()
API to display current usage, but it is just a single snapshot.
The Memory Manager never uses a bucket with a larger size than is needed to fulfill a request. For example, suppose you have 6 buckets and their sizes are currently 48, 48, 512, 512, 256, and 1536. If a request for 94 MAUs is made, an allocation error will occur. A 256 MAU block will not be allocated for the 94 MAU request.
However, suppose you had 6 buckets and the current sizes were 48, 48, 96, 512, 1536, and uninitialized. The allocation for 94 MAUs would succeed because either the third bucket would be used, or if it had already allocated all of its blocks, the last bucket would be initialized to manage block sizes of 96.
If you reduce the Global.memRawPageCount too much, the likelihood of an allocation error increases. If RAW_PAGE_SIZE
is greater than 1536, we recommend that you set RAW_PAGE_COUNT
to 6 or higher. Note that there are 7 block sizes the NDK can use–48, 96, 128, 256, 512, 1536, and RAW_PAGE_SIZE
You can reduce the RAW_PAGE_SIZE
setting. The best choice depends on the NDK components being used. For example the HTTP Server calls mmAlloc()
with a size of 1,676 bytes.
Unfortunately, determining the optimal page size and count is currently done by trial and error.
6.3 Dynamic Memory Manager¶
The NDK uses the OS Adaptation Layer to allocate dynamic memory for task stacks and malloc()
calls.
6.3.1 Tasks¶
The NDK creates the following Task objects:
Main NDK Task: This is the main NDK task.
Boot Task: This task is responsible from some initialization activities and is terminated once it is completed. The memory allocated for this Task object is freed when the task terminates.
DHCP Client Task (if the DHCP Client is used): This task makes the initial DHCP request and subsequent lease renewals.
DaemonNew() calls: This task is created as a result of the application calling DaemonNew(). The NDK’s HTTP and Telnet servers also call this function.
dchild Tasks: A daemon creates a dchild task to handle new activity on a socket.
DHCP Server Task (if the DHCP Server is used): This task processes DHCP requests.
DNS Server Task (if the DNS Server is used): This task processes DNS requests.
Tools Services: Example code in the
ti/ndk/tools
directory creates tasks also.
All the above task objects and task stacks are allocated via the TaskCreate()
call in the OS Adaptation Layer. The size of these task stacks can be configured in SysConfig under the “NDK Created Threads” section.
6.3.2 Sockets¶
When a socket is created, memory for the socket’s TX and RX buffers is allocated from malloc()
by the mmBulkAlloc()
function. The default buffer sizes for UDP and TCP sockets can be modified in SysConfig under the “TCP & UDP Options” section.
A graphical view of all sockets’ buffer usage can be seen with ROV