What are some guidelines for reducing memory usage in C?

What is the best practice for programming with C memory? Mainly for an integrated / mobile device, what should be the recommendations for reducing memory consumption?

I suggest that there should be a separate guide for: a) code memory; b) data memory

+33
optimization c memory-management
Jan 01 '09 at 6:05
source share
15 answers

In C, at a much simpler level, consider the following:

  • Use #pragma pack (1) for byte align structures
  • Use joins in which a structure may contain different data types
  • Use bit fields instead of int to store flags and small integers.
  • Avoid using fixed-length character arrays to store strings, implement a string pool, and use pointers.
  • Where links to a list of numbered lines are stored, for example. font name, keep index in list, not string
  • When using dynamic memory allocation, calculate the number of elements needed in advance to avoid reuse.
+23
Jan 01 '09 at 9:56
source share

A few suggestions that I found useful when working with embedded systems:

  • Make sure that any lookup tables or other persistent data is actually declared using const . If const used, the data can be stored in read-only memory (for example, flash memory or EEPROM), otherwise the data must be copied to RAM at startup, and this takes up both flash space and RAM. Set the linker options so that it creates a map file and examines this file to see exactly where your data is allocated on the memory card.

  • Make sure that you use all the memory areas available to you. For example, microcontrollers often have built-in memory that you can use (which can also be faster for access than external RAM). You should be able to manage the memory areas in which code and data are allocated using the parameters of the compiler and linker options.

  • To reduce code size, check compiler optimization settings. Most compilers have switches to optimize the speed or size of the code. It might be worth experimenting with these options to see if you can reduce the size of the compiled code. And, obviously, remove duplicate code where possible.

  • Check how much stack memory your system requires and adjust the linker memory allocation accordingly (see answers to this question ). To reduce stack usage, avoid placing large data structures on the stack (for any value, β€œlarge” refers to you).

+18
Jan 01 '09 at 14:40
source share

Make sure you use fixed point / integer maths where possible. Many developers use floating point math (along with slow performance and large libraries and memory usage) when there is a fairly simple scaled integer math.

+14
Jan 01 '09 at 19:05
source share

All good recommendations. Here are some development approaches that I have found useful.

  • Byte encoding

Write an interpreter for a set of special-purpose instructions for byte code and write as much of the program as possible in this set of instructions. If certain operations require high performance, make them native code and call them from the interpreter.

  • Code generation

If part of the input changes very rarely, you may have an external code generator that creates a special program. This will be less than a more general program, and also faster and will not allocate memory for rarely changing input.

  • Be a data hater

Be prepared to spend many cycles if this allows you to keep an absolutely minimal data structure. You will usually find that performance is very small.

+9
Jan 27 '09 at 22:36
source share

Avoid memory fragmentation using your own memory allocator (or carefully using a system allocator).

One way is to use the "slab allocator" (see article ) and several memory pools for objects of different sizes.

+8
Jan 01 '09 at 7:52
source share

Preallocating all of the memory at the top (i.e. no malloc calls other than initializing the launch) is definitely useful for using deterministic memory. Otherwise, various architectures provide methods that will help. For example, some ARM processors provide an alternative set of instructions (Thumb), which almost doubles the size of the code using 16-bit instructions instead of the usual 32 bits. Of course, speed is sacrificed in doing so ...

+7
Jan 01 '09 at 6:10
source share

Most likely, you will need to carefully choose your algorithms. The purpose of algorithms using memory is O (1) or O (log n) (i.e., Low). For example, continuous scalable arrays (e.g. std::vector ) in most cases require less memory than linked lists.

Using lookup tables can sometimes be more useful for code size and speed. If you only need 64 entries in the LUT, this is 16 * 4 bytes for sin / cos / tan (use symmetry!) Compared to the large sin / cos / tan function.

Sometimes compression helps. Simple algorithms, such as RLE, are easily compressed / decompressed during sequential reads.

If you are dealing with graphics or audio, consider different formats. A palette or bitmap * can be a good compromise for quality, and palettes can be divided into many images, which further reduces the size of the data. Audio can be reduced from 16-bit to 8-bit or even 4-bit, and stereo can be converted to mono. The sampling rate can be reduced from 44.1 to 22 kHz or 11 kHz. These sound transformations significantly reduce the size of their data (and, unfortunately, the quality) and are trivial (with the exception of resampling, but that for audio programs for =]).

* I think you could put this under compression. Bitpacking for graphics usually refers to reducing the number of bits per channel, so each pixel can fit in two bytes (e.g. RGB565 or ARGB155) or one (ARGB232 or RGB332) from three or four originals (respectively RGB888 or ARGB8888).

+7
Jan 01 '09 at 6:41
source share

Some parsing operations can be performed on threads when bytes arrive, rather than being copied to the buffer and parsed.

Some examples of this:

  • Disassemble the NMEA pairs with the state machine, collecting only the required fields into a much more efficient structure.
  • Parse XML using SAX instead of DOM.
+6
Jan 01 '09 at 14:08
source share

1) Before starting a project, create a way to measure the amount of memory used, preferably based on each component. Thus, every time you make changes, you can see its effects on memory usage. You cannot optimize what you cannot measure.

2) If the project has already matured and falls within the memory limits (or is transferred to a device with less memory), find out what you are already using the memory for.

My experience is that almost all significant optimization when fixing a large application depends on a small number of changes: it reduces the size of the cache, highlights some textures (of course, this is a functional change that requires agreement with interested parties, that is, meetings, so there may be ineffective in terms of your time), convert the sound, reduce the initial size of the heap allocated, find ways to free resources that are only used temporarily, and per unload them when required again. Sometimes you will find some structure, which is 64 bytes, which can be reduced to 16 or something else, but these are rarely the worst results. If you know what the largest lists and arrays in the application are, then you know what structures should look first.

Oh yes: find and fix memory leaks. Any memory that you can recover without sacrificing performance is a great start.

I spent a lot of time in the past worrying about code size. Key considerations (except: make sure you measure it during assembly so you can see it):

1) Find out which code is referenced, and by what. If you find that the whole XML library is bound to your application in order to parse a two-element configuration file, consider changing the format of the configuration file and / or writing your own trivial analyzer. If you can, use either source or binary analysis to draw a large dependency graph and look at the large components with a small number of users: perhaps this can be cut off with only minor code rewrites. Be prepared to play diplomat: if two different components in your application use XML, and you want to crop it, then you must convince these two people of the benefits of manually rewinding what is now a reliable, ready-made library.

2) Separate the compiler options. Consult your specific platform. For example, you can reduce the allowable default increase in code size due to nesting, and in GCC, at least you can tell the compiler to only apply optimizations that usually don't increase code size.

3) If possible, use libraries already on the target platform (s), even if this means writing an adapter layer. In the above XML example, you may find that your target platform always has an XML library in memory because the OS uses it, in which case it dynamically refers to it.

4) As already mentioned, thumb mode can help in ARM. If you use it only for code that is not performance critical and leave the critical procedures in ARM, you will not notice the difference.

Finally, there may be smart tricks that you can play if you have enough control over the device. Does the user interface let you run one application at a time? Unload all drivers and services that your application does not need. Double buffering screen, but does your application synchronize with the update cycle? You can restore the entire screen buffer.

+4
Jan 01 '09 at 14:18
source share
+3
May 18 '09 at 6:34 a.m.
source share
  • Reduce length and eliminate as many string constants as possible to reduce the number of codes

  • Carefully consider the tradeoffs of search algorithms and tables where necessary

  • Keep in mind how different types of variables are distributed.

    • Constants are probably in code space.
    • Static variables are probably in fixed memory locations. Avoid them if possible.
    • Parameters are probably stored on the stack or in registers.
    • Local variables can also be allocated from the stack. Do not declare large local arrays or strings if, in the worst case scenario, the code might end up from the stack space.
    • You may not have a heap - there may not be an OS to manage the heap for you. It is acceptable? Do you need the malloc () function?
+2
Sep 07 '09 at 16:31
source share

One trick useful in applications is to create a daily memory pool. Highlight one block at startup that is large enough to be enough for cleaning tasks. If malloc / new fails, release the rainy day fund and post a message letting the user know that the resources are hard, and they should be saved in the near future. This was the method used in many Mac applications around 1990.

0
Jan 01 '09 at 2:30 p.m.
source share

A great way to limit memory requirements is to rely as much as possible on libc or other standard libraries that can be linked dynamically. Each additional DLL or shared object that you must include in your project represents a significant portion of the memory that you can avoid.

Also, use connections and bit fields, where applicable, to load only a fraction of your data that your program runs in memory, and make sure that you compile with -Os (in gcc or the equivalent of your compiler) to optimize program size.

0
Jan 2 '09 at 7:27
source share

I have a presentation of the Embedded Systems Conference on this topic. This is from 2001, but still it is very applicable. See the document .

In addition, if you can choose the architecture of the target device, upgrading with something like a modern ARM with Thumb V2 or PowerPC with VLE or MIPS with MIPS16 or choosing well-known compact targets such as the Infineon TriCore or the SH family is a very good option. Not to mention the NEC V850E family, which is beautifully compact. Or go to AVR, which has excellent code compactness (but it is an 8-bit machine). Everything but the 32-bit fixed-length RISC is a good choice!

0
Jan 09 '09 at 8:18
source share

In addition to the suggestions provided by others, remember that local variables declared in your functions will usually be allocated on the stack.

If the stack memory is limited or you want to reduce the size of the stack to make room for more heap or global memory, consider the following:

  • Smooth the call tree to reduce the number of variables on the stack at any given time.
  • Converting large local variables to global ones (reduces the amount of stack used, but increases the amount of global memory used). Variables can be declared:

    • Global area: visible for the entire program
    • Static in file area: visible in the same file
    • Static function scope: visible inside the function
    • NOTE. Regardless if theses changes are made, you should be careful with reentrant problems if you have preemptive .

Many embedded systems do not have stack monitor diagnostics to ensure that stack overflows are caught, so some analysis is needed.

PS: Bonus for proper use?

0
Sep 07 '09 at 17:12
source share



All Articles