-2

I am working with embedded systems(Mostly ARM Cortex M3-M4) using C language.And was wondering what are the advantages/ disadvantages of dividing the task into many functions such as;

   void Handle_Something(void)
{  
// do Task-1
// do Task-2
// do Task-3
//etc.   
}

to

void Hanlde_Something(void)
{
// Handle_Task1();
// Handle_Tasl2();
//etc.
}

How are these two approaches can be examined with respect to stack usage and overall processing speed, and which is safer/better for what reason ? (You can assume this is outside of ISR)

From what I know, Memory in the stack is allocated/deallocated for local variables in each call/return cycle, thus dividing the task seems reasonable in terms of memory usage but when doing this, I sometimes get Hardfaults from different sources(mostly Bus or Undefined Instruction errors.) that I couldn't figured out why.

Also, working speed is very crucial for many applications in my field, so ı do need to know which methode provides faster responses.

I would appreciate an enlightment. Thanks everybody in advance

artless noise
  • 21,212
  • 6
  • 68
  • 105
İlkerK
  • 76
  • 4
  • 1
    Part of your job as a coder is to not just produce code, but readable code. You can use both comments and functions to make the code more readable. – Adder May 08 '18 at 07:33
  • 1
    In my opion a function should only do one job. So it's easier to read and debug. Also unit-testing is much easier. One more advatage ist the reusability. – Mike May 08 '18 at 07:36
  • You will have slightly higher stack use, and more branch instructions if you split your code into functions, however, it will be much more readable and easily maintainable. – Colin May 08 '18 at 08:09
  • @Colin__s Why on earth would functions result in more branch instructions? (Assuming the compiler is bad and fails to inline) – Lundin May 08 '18 at 10:02
  • @Lundin surely you summed it up in your comment, if they're not inlined then you will be branching, it should be negligible, but the branches will be there. – Colin May 08 '18 at 10:04
  • @Lundin And, even with a modern compiler, and trivial code, the functions aren't inlined if -Os is chosen, whereas they are with -O3, see https://godbolt.org/g/Qi1Q4N – Colin May 08 '18 at 10:15
  • Please go read the chapter on functions in _any_ book on software construction. For example, chapter 7 in _Code Complete_ by Steve McConnell, or chapter 3 in _Clean Code_ by Robert C. Martin. – kkrambo May 08 '18 at 12:45
  • At the end of the day there is no fixed answer to this question it depends heavily on your code, compiler, compiler options, target, etc. there is no one rule. It should be pretty easy to create benchmarks to demonstrate each case is better than the other. – old_timer May 08 '18 at 21:15

4 Answers4

2

This is what's known as "pre-mature optimization".

In the old days when compilers where horrible, they couldn't inline functions by themselves. So in the old days a keyword inline was added to C - similar non-standard versions also existed before the year 1999. This was used to tell a bad compiler how it should generate code better.

Nowadays this is mostly history. Compilers are better than programmers at determining what and when to inline. They may however struggle when a called function is located in a different "translation unit" (basically, in a different .c file). But in your case I take it this is not the case, but Handle_Task1() etc can be regarded as functions in the same file.

With the above in mind:

How are these two approaches can be examined with respect to stack usage and overall processing speed

They are to be regarded as identical. They use the same stack space and take the same time to execute.

Unless you have a bad, older compiler - in which case function calls always take extra space and execution time. Since you are working with modern MCU:s, this should not be the case, or you desperately need to get a better compiler.

As a rule of thumb, it is always better practice to split up larger functions in several smaller, for the sake of readability and maintenance. Even in hard real-time systems, there exist very few cases where function call overhead is an actual bottleneck even when bad compilers are used.

Lundin
  • 195,001
  • 40
  • 254
  • 396
1

Memory on the stack isn't allocated/reallocated from some complex memory pool. The stack pointer is simply increased/decreased. An operation that is basically free in all but the tightest/smallest loops imaginable (and those will probably be optimized by the compiler).

Don't group together functions because they could reuse variables e.g. don't create a bunch of int tempInt; long tempLong; variables you use throughout your entire program. A variable should serve only a single purpose and its scope should be kept as tight as possible. Also see: is it good or bad to reuse the variables?

Expanding on that, keeping the scope of all variables as local as possible might even cause your compiler to keep the variables in a cpu register only. A shortly used variable might actually never be allocated!

Try to limit functions to only a singly purpose and try avoiding side effects: if you avoid global variables a function becomes easier to test, optimize and understand as each time you call it with the exact same set of arguments it will preform the exact same action. Have a look at: Why are global variables bad, in a single threaded, non-os, embedded application

Lanting
  • 3,060
  • 12
  • 28
1

Each solution has advantages and disadvantages.

The first approach allows to execute the code (a priori) faster, because the asm code won't have instructions related to jumps. However, you have to take the readability into account, in terms of mixing different kind of functionalities in the same function (or creating large functions, which is not a good idea from the guidelines point of view).

The second solution could be easier to understand, because each function contains a simple task, furthermore it is easier for documenting (that is, you dont have to explain different "purposes" in the same function). As I said, this solution is slower, because your "scheduler" contains jumps, nevertheless you could declare the simple tasks as inline, given that you can split the code in several simple tasks with a proper documentation and the compiler will generate an assembler as the first approach, that is, avoiding the jumps.

Another point is the use of memory. If your simple tasks are being called from different parts of the code, the first solution and the second solution with inline are worse (in terms of memory) than the second solution without inline, because the function is added as many times as it is called from different parts of your code.

Jose
  • 3,306
  • 1
  • 17
  • 22
0

Working with modules is always more efficient in terms of error handling, debugging and re-reading. Considering some heavy working libraries (SLAM, PCL etc.) as functions, they are used as external functions and they don't cause a significant loss of performance(tbh sometimes it's almost impossible to embed such large functions into your code). You may face slightly higher stack use as @Colin commented.