Efficient C Programming Under ARM: A Comprehensive Guide

Efficient C Programming Under ARM: A Comprehensive GuideArticle Word Count: 3900 Content Index: ⭐⭐⭐⭐⭐By writing C programs in a certain style, you can help the C compiler generate faster executing ARM code. Below are some key points related to performance:1. Use signed and unsigned int types for local variables, function parameters, and return values. This can avoid type conversion and efficiently utilize ARM’s 32-bit data operation instructions.2. The most efficient loop structure is a do-while loop that counts down to zero.3. Unroll important loops to reduce loop overhead.4. Do not rely on the compiler to optimize out repeated memory accesses. Pointer aliasing can prevent this optimization by the compiler.5. Limit the number of function parameters to 4 or fewer whenever possible. If function parameters are stored in registers, function calls will be much faster.6. Arrange structures in ascending order of element size, especially when compiling in thumb mode.7. Avoid using bit fields; use masks and logical operations instead.8. Avoid division; use multiplication by the reciprocal instead.9. Avoid data that is misaligned. If data may be misaligned, use char * pointer type for access.10. Using inline assembly in C compilers can leverage instructions or optimizations that the C compiler does not natively support.Efficient C Programming Under ARM: A Comprehensive GuideOptimization of Data Type Usage1. Local VariablesA char type variable occupies smaller register space or smaller ARM stack space than an int type variable. Both assumptions are incorrect for ARM. All ARM registers are 32 bits, and all stack entries are at least 32 bits. When executing i++, to utilize the condition when i=255, i++=0, it can be defined as a char type. 2. Function ParametersWhile wide and narrow function calling conventions have their advantages, using char or short type function parameters and return values incurs additional overhead, leading to performance degradation and increased code size. Therefore, even when transmitting an 8-bit data, using int type for function parameters and return values is more efficient. Summary: 1) For local variables stored in registers, avoid using char and short types except for 8-bit or 16-bit arithmetic modulo operations, and use signed or unsigned int types instead. Division operations are faster when using unsigned numbers. 2) For arrays and global variables stored in main memory, use smaller data types as much as possible, provided the data size requirement is met, to save storage space. The ARMv4 architecture can effectively load and store data of all widths and can access arrays efficiently using incremented array pointers. For short type arrays, avoid using offsets from the base address of the array, as the LDRH instruction does not support offset addressing. 3) When reading arrays or global variables and assigning them to local variables of different types, or writing local variables to different types of arrays or global variables, explicit data type conversion should be performed. This conversion allows the compiler to clearly and quickly handle the expansion of narrower data types in memory to wider types in registers. 4) Since implicit or explicit data type conversions usually incur extra instruction cycle overhead, they should be avoided in expressions as much as possible. Load and store instructions generally do not produce additional conversion overhead, as they automatically handle data type conversions. 5) For function parameters and return values, avoid using char and short types as much as possible. Even if the parameter range is small, int type should be used to prevent unnecessary type conversions by the compiler.Efficient C Programming Under ARM: A Comprehensive GuideC Loop StructuresOn ARM, a loop can be executed with just 2 instructions:

  • One subtraction instruction to perform loop decrement counting while setting the condition flags;

  • One conditional branch instruction.

The key here is that the loop termination condition should be a countdown to zero rather than counting up to a specific limit. Since the countdown structure has stored the condition flags, the instruction comparing with zero can be omitted. Since i is not used as the array index, there is no problem with using countdown.In summary, regardless of whether the signed loop counter value is used, i!=0 should be used as the loop termination condition. For signed i, this is one instruction less than using the condition i>0.Summary:1) Use a countdown to zero loop structure, so the compiler does not need to allocate a register to store the loop termination value, and the instruction comparing with 0 can be omitted.2) Use an unsigned loop counter, with the loop continuation condition being i!=0 rather than i>0, ensuring the loop overhead is only two instructions.3) If it is known in advance that the loop body will execute at least once, using a do-while loop is better than a for loop, as it allows the compiler to skip checking if the loop count is zero.4) Unrolling important loop bodies can reduce loop overhead, but do not over-unroll; if the loop overhead is small compared to the entire program, unrolling the loop may increase code size and degrade cache performance.5) Try to make the size of the array a multiple of 4 or 8, so that it is easy to unroll the loop by 2, 4, 8 times, etc., without worrying about leftover array elements.Efficient C Programming Under ARM: A Comprehensive GuideRegister AllocationEfficient register allocation: The number of local variables used in function internal loops should be limited as much as possible, ideally not exceeding 12, so the compiler can allocate all these variables to ARM registers.Efficient C Programming Under ARM: A Comprehensive GuideFunction CallsRegister rules: Functions with 4 or fewer parameters are much more efficient than those with more than 4 parameters.For functions with fewer than 4 parameters, the compiler can pass all parameters using registers;whereas for functions with more than 4 parameters, the caller and callee must pass some parameters via the stack.If the function body is small and uses very few registers, there are other methods to reduce function call overhead. Placing the calling function and the called function in the same C file allows the compiler to know the code generated for the called function and optimize the calling function accordingly.Summary:1) Limit the number of function parameters to no more than 4, which will improve function call efficiency. You can also group several related parameters into a structure and pass a pointer to the structure instead of multiple parameters.2) Place smaller called functions and calling functions in the same source file and define them before calling them so the compiler can optimize the function call or inline smaller functions.3) For important functions that significantly impact performance, use the inline keyword for inlining.Efficient C Programming Under ARM: A Comprehensive GuidePointer AliasingDefinition: When two pointers point to the same address object, these two pointers are called aliases for that object. If one pointer is written to, it will affect the reading from the other pointer. In a function, the compiler usually does not know which pointer is an alias and which is not; or which pointer has an alias and which does not.Avoiding Pointer Aliasing:1) Do not rely on the compiler to eliminate common sub-expressions that involve memory access; instead, create a new local variable to store the value of this expression, ensuring that this expression is evaluated only once;2) Avoid using the address of local variables, as accessing these variables will be less efficient.Efficient C Programming Under ARM: A Comprehensive GuideStructure ArrangementUsing structures on ARM involves two considerations: structure address boundary alignment and the total size of the structure.Principles for obtaining efficient structures:1) Arrange all 8-bit size elements at the front of the structure;2) Arrange 16-bit, 32-bit, and 64-bit elements in that order;3) Place all arrays and larger elements at the end of the structure;4) If a structure is too large to access all elements in a single instruction, organize elements into a sub-structure. The compiler can maintain a pointer to the separate sub-structure.Summary:Structure elements should be arranged in order of element size, with the smallest elements at the start and the largest elements at the end; avoid using very large structures, and use hierarchical smaller structures instead; to improve portability, manually add padding to API structures so that their arrangement does not depend on the compiler; be cautious when using enumeration types in API structures, as the size of an enumeration type is compiler-dependent.Efficient C Programming Under ARM: A Comprehensive GuideBit FieldsPoints to Note:1) Avoid using bit fields and use #define or enum to define masks instead;2) Use integer logical operations AND, OR, XOR, and masks to test, negate, and set bit fields. These operations are efficient in compilation and can test, negate, and set multiple bit fields simultaneously.Efficient C Programming Under ARM: A Comprehensive GuideMisaligned Data and Byte Order (Big/Little Endian)Misaligned data and byte order can complicate memory access and portability issues. Consider whether array pointers are boundary-aligned and whether the ARM configuration is big-endian or little-endian.Summary:1) Avoid using misaligned data as much as possible;2) Use char * to point to data at any byte boundary. Access data by reading bytes and using logical operations to combine data, so that the code does not depend on alignment or the ARM’s byte order configuration;3) To quickly access misaligned structures, write different program variants based on pointer alignment and the processor’s byte order.Efficient C Programming Under ARM: A Comprehensive GuideDivisionARM hardware does not support division instructions. When division operations appear in code, the ARM compiler will call C library functions (signed division calls _rt_sdiv, unsigned calls _rt_udiv) to perform the division operation. There are many different types of division routines to accommodate different divisors and dividends.Summary:1) Avoid using division whenever possible. Processing circular buffers can be done without division.2) If division cannot be avoided, consider using division routines that simultaneously produce both the quotient n/d and the remainder n%d.3) For repeated division by the same divisor d, precompute s=(2k-1)/d. You can use multiplication by s to replace division by d for k-bit unsigned integer division.4) Use powers of 2 as divisors. When powers of 2 are used as divisors, the compiler automatically converts division operations into shift operations. Therefore, when writing program algorithms, try to use powers of 2 as divisors.5) Remainder operations. Some typical remainder operations can be transformed to avoid using division in the program. For example:

uint counter1(uint count){return (++count%60);} // Transformed to: uint counter2(uint count){if (++count >=60) count=0; return (count);}

Efficient C Programming Under ARM: A Comprehensive GuideFloating Point OperationsMost ARM processors do not support floating point operations in hardware. This can save space and reduce power consumption in price-sensitive embedded applications. Besides hardware vector floating point accumulators (VFP) and floating point accumulators (FPA) on ARM7500FE, the C compiler must provide floating point support in software.Efficient C Programming Under ARM: A Comprehensive GuideInline Functions and Inline AssemblyTo efficiently call functions, using inline functions can completely eliminate the overhead of function calls. Additionally, many compilers allow inline assembly to be used in C source programs. Using inline functions that include assembly can enable the compiler to support ARM instructions and optimizations that are typically not effectively utilized.The greatest benefit of inline functions and inline assembly is that they can achieve some operations that are usually difficult to accomplish in the C language portion. Using inline functions is better than using #define macros, as the latter does not check the types of function parameters and return values.Disclaimer: The content of this article comes from the internet, and the copyright belongs to the original author. If there are any copyright issues, please contact for deletion.-END-

Efficient C Programming Under ARM: A Comprehensive Guide

1

“Thought-Provoking! How Should Embedded Professionals Plan Their Careers Amidst Technological Changes, and What Are the Challenges?”

2

“So Classic! 14 Common C Language Algorithms for Microcontrollers (with Detailed Code)”

3

“Must-Read! A Guru Summarized 10,000 Words on Essential Knowledge Points of Embedded C Language…”

Efficient C Programming Under ARM: A Comprehensive GuideEfficient C Programming Under ARM: A Comprehensive Guide

This Week’s Live Broadcast | Click to View👇

Efficient C Programming Under ARM: A Comprehensive Guide

Monday | What is Artificial Intelligence Deep Learning?

1. What is deep learning?

2. How much do you know about artificial neural networks?

3. Do you understand the underlying mechanisms of perceptrons?

Efficient C Programming Under ARM: A Comprehensive Guide

Tuesday | What is the Compiler Really Doing?

1. Does the CPU not recognize any language?

2. Are you unaware of the development of programming languages?

3. Do you not understand how compilers work?

Efficient C Programming Under ARM: A Comprehensive Guide

Tuesday | The Relationship Between Pointers and Arrays

1. What can pointers be used for?

2. What is an array?

3. Are you clear about the relationship between pointers and arrays?

Efficient C Programming Under ARM: A Comprehensive GuideEvery day, hardworking little creators,[Share, Like, Follow]Can we have a duck?Efficient C Programming Under ARM: A Comprehensive Guide

Leave a Comment