Small function overhead - Page 1
Forum section: electronics / general

author: Harvs
author_rank: Super Contributor
timestamp: February 17, 2013, 12:54:30 am
content: I've been reading Clean Code: A Handbook of Agile Software Craftsmanship by Robert C. Martin, and have been wondering about the principles for breaking down functions in the context of small embedded systems coded in C++.He talks about breaking down functions to very small sizes, which makes a lot of sense as he describes it, but the book is generally set in the context of the Java programmer.I've been trying to work out if splitting a function into multiple functions, that only get called in the one place, has a performance penalty? Or will the GNU G++ compiler recognise that the function is only called in the one location, and therefore directly insert it as part of the main function?e.g. If I had a functionCode:int someFunction(int input){int something;// Say 20 lines of codereturn something;}And I broke it into multiple functions which are only ever called by the first function within the same class:Code:int someFunction(int input){int something;something = aBrokenOutFuction1(input);something = aBrokenOutFuction2(something);....return something;}int aBrokenOutFunction1(int input){// Do somethingreturn result;}....Will I wear a performance penalty of calling a each of these functions and passing in the variables to them?

author: c4757p
author_rank: Super Contributor
timestamp: February 17, 2013, 01:49:33 am
content: I don't remember for sure whether it will by default - and the behavior may differ with embedded compilers - but if you declare the function "inline" it should be inserted into the parent function directly. Declare it "static inline" to also omit the function from the function tables, which may save some space depending on the binary architecture.

author: TerminalJack505
author_rank: Super Contributor
timestamp: February 17, 2013, 02:54:45 am
content: The GNU compiler is pretty smart when it comes to code optimization so it will likely do the right thing. I've seen it inline functions that weren't even declared asinline.The best way to tell what's going on is to have the compiler generate an assembly listing when you build. You can then peek at this file to see what the compiler has done.

author: westfw
author_rank: Super Contributor
timestamp: February 17, 2013, 05:51:15 am
content: QuoteSometimes bloating the code in the process. Here's a case where stopping this behavior reduced program size by about 30%:http://code.google.com/p/arduino/issues/detail?id=822

author: c4757p
author_rank: Super Contributor
timestamp: February 17, 2013, 05:55:37 am
content: An important consideration there would be whether they are compiling it by default with the-Os(optimize for size) flag. If so, it is idiotic for GCC to inline those functions. If not, it is perfectly acceptable - GCC's default options are more targeted at PCs, where a few extra kB are nothing.

author: Mechatrommer
author_rank: Super Contributor
timestamp: February 17, 2013, 06:07:18 am
content: Quotemaybe he's talking about code "manage'ability", but as general rule... breaking down into smaller function only for "reusability" purpose. sometime many smaller functions is not managable at all. extra line break and decent commenting in one single "function body" is much more sensible. trying to chase compiler specific optimization is not really a wise move from "SW eng" point of view. my 2cnts, YMMV.

author: Harvs
author_rank: Super Contributor
timestamp: February 17, 2013, 06:40:27 am
content: I tried it out using a clean project and Sourcery ARM G++ EABI compiler.With -Os it doesn't want to inline anything unless the inline keyword is used. Even if it results in larger binaries and slower execution.With -O3 it automatically inlines all the very small functions I tried.

author: Harvs
author_rank: Super Contributor
timestamp: February 17, 2013, 06:55:40 am
content: Quote from: Mechatrommer on February 17, 2013, 06:07:18 amWell he talks about a lot of stuff, far too much for me to provide any justice to here, after all he's written an entire book on it.But I can understand that it's a debatable topic among programmers. I've seen a number of debates among programmers spouting different languages and coding styles.

author: AlfBaz
author_rank: Super Contributor
timestamp: February 17, 2013, 06:58:36 am
content: There must be a break point where function overhead exceeds or comes close to the actual function code. This, the number of calls to it and the type of optimisation called for, you can probably start to see situations where inlining a function might be prudent

author: TerminalJack505
author_rank: Super Contributor
timestamp: February 17, 2013, 08:04:33 am
content: I come from the PC-side of software development where memory and CPU cycles are cheap so I'm in the small-functions-are-'cleaner' camp. If you've ever spent some time as a software maintenance programmer than you'll know that there really is such a thing as functions that are too large.Breaking large functions up helps with readability (and thus maintainability) tremendously. I cringe when I see functions that are more than a couple of hundreds lines long. 100 lines would probably be the biggest I'd like to see.I only do MCU development as a hobby so I can't really say that it matters one way or the other with regards to code generation. I focus first on code maintainability and worry about code optimization only if necessary.There's a saying in software development that was coined by Donald Knuth: Premature optimization is the root of all evil. That is to say, don't complicate things unnecessarily in the name of optimization.

author: andersm
author_rank: Super Contributor
timestamp: February 17, 2013, 08:34:36 am
content: Quote from: Harvs on February 17, 2013, 06:40:27 amThe cost model is constantly tweaked, but GCC will certainly inline when using -Os. But you have to write the code with this in mind. For example, if your function isn't static, the compiler must assume it can be called from some other source file, and therefore include an "outline" copy. If the function body takes less space than a function call, calls in the same source file may still be inlined, but otherwise function a function call will be emitted.Link-time optimization should be able to handle this, but I've never tried using it.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: February 17, 2013, 10:15:27 am
content: QuoteIn terms of code size, it should be easy for a linker to figure out if inlining would save space based on number of calls and overhead per call.Requirements for small embedded systems are somewhat different than for PCs, so GCC is probably not going to make the best decisions unless it can be sufficiently tweaked.My only experience with GCC is as part of the Microchip PIC24 compiler, which (until I think quite recently) couldn't even be told to omit unused functions, which is pretty pathetic.

author: firewalker
author_rank: Super Contributor
timestamp: February 17, 2013, 11:40:55 am
content: Quote from: mikeselectricstuff on February 17, 2013, 10:15:27 amCouldn't someone use-ffunction-sectionsduring compiling in addition with-gc-sectionsduring linking?Alexander.

author: amyk
author_rank: Super Contributor
timestamp: February 17, 2013, 12:38:29 pm
content: I think what he's saying is the defaults are not sensible and there is no easy way to change them. GCC is a "big system" compiler that just barely works for most of the architectures it supports. Even on the PC where I've used it, the default is to insert tons of useless crap in the binary and it is difficult to figure out how to get rid of it. I can see this being a huge problem for embedded systems work.Linus has much to say about GCC's inlining here:http://yarchive.net/comp/linux/gcc_inline.htmlFor embedded systems the default should be "inline-if-smaller".

author: Unknown
author_rank: Unknown
timestamp: February 17, 2013, 12:53:22 pm
content: Quote from: amyk on February 17, 2013, 12:38:29 pmavr-gcc is quite competitive with the commercial boys like IAR and CV in both size and performance. The commercial boys will usually perform slightly better, but usually something like 20% gain. The Keil (ARM) compiler doesn't appear to be an order of magnitude better than GCC for ARM either, several commercial offerings (eg. Rowley and the several packaged gcc+eclipse distributions like Codered and Attolic) use it with good results.Quote from: amyk on February 17, 2013, 12:38:29 pmThis was written in 2000. I think this was just after the FSF gcc/Cygnus egcs split got resolved. That's quite ancient in gcc history. Did it even support embedded architectures like AVR and ARM back then?Quote from: amyk on February 17, 2013, 12:38:29 pmThe default for embedded systems is usually -Os, which is quite conservative.

author: Mechatrommer
author_rank: Super Contributor
timestamp: February 17, 2013, 01:30:21 pm
content: Quote from: Harvs on February 17, 2013, 12:54:30 ami downloaded the book. well, practically he's quite right. you loosely define what is "small" and showing 20 lines of code... 20 lines is "small", its relative to anybody you talking to.but when it comes to hundreds or thousands of lines, then i agree with him, regardless if it is "one function only" or "many small functions combined". let me add to him, he said...Quotei say...Quotehow small? its relative though based on personal taste, for a function, it should be around 20 lines average, but for a file, maybe around few hundreds or few thousands lines? before it can easily out of control. otoh, with commenting and line breaking, one can increase the size meaning of what is "small".edit: btw, coding thousands of lines in one single function is a very very old practice when no subroutine other than "main" existed. i'm yet to see any such code from the beginning of my programming life. and i never practice it myself, when it gets too big, it will get splitted... naturally. the greatest rule imho is.. use your own flavours and senses to define what is "big" or "small"

author: nctnico
author_rank: Super Contributor
timestamp: February 17, 2013, 04:40:53 pm
content: Quote from: westfw on February 17, 2013, 05:51:15 amJust read GCC's manual. What -Os and -O3 do is well explained in there. Basically you want to use -Os to get the smallest code with reasonable speed and -O3 for the fastest execution without being bothered with size. Another thing -O3 does is loop unrolling which may be faster.Sometimes I mix them by compiling some piece with -O3 and other parts with -Os. To reduce function call overhead I usually declare functions inline if they are very small and are called many times from several places. There is a twilight zone between defining a macro or a small function. In the end it is always good not to duplicate code but put it in a macro or a (small) function.

author: Harvs
author_rank: Super Contributor
timestamp: February 17, 2013, 09:36:51 pm
content: Thanks for all the idea's which I've gone off and tried in ARM G++, and reviewed the assembly listings.In summary to answer my own question at the start, it's not a wise idea count on G++ to inline functions that you are counting on it doing it for.Some compiler options result in them being inline. -Os I couldn't get to inline despite trying everything that has been said earlier in this thread. Whilst I'm sure someone with a better grasp on the specific compiler implementation could come up with how, it just makes sense to explicitly code them inline so it doesn't end up sub-optimal if the code gets reused by someone else without the knowledge that a specific compiler optimization setting must or must not be used. Or heaven forbid, a completely different compiler is used.Cheers

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: February 17, 2013, 10:06:37 pm
content: http://www.amazon.co.uk/Code-Complete-Practical-Handbook-Construction/dp/0735619670I found Code complete to be a great book, though it is centred around high powered computing and not resource constrained embedded computing. I think it broadly agrees with what TerminalJack505 said.Could you tell me what Processor you're targeting and what Development tools you are using.I am looking at moving into Arm Cortex M3-4 and dropping 8 bit processors, and I like to hear other peoples experiences of what works well.

author: Harvs
author_rank: Super Contributor
timestamp: February 17, 2013, 10:09:34 pm
content: Quote from: Mechatrommer on February 17, 2013, 01:30:21 pmI'm not in a position where I want to defend one point of view from the other. I've come from largely coding in C in what would be classed as a fairly "traditional" approach, exactly what you're describing above. That you just break up functions where reuse is required, and where it just makes sense because they're becoming unwieldy or functionality isn’t related.In my quest to expand my knowledge and become a better programmer, I want to give his methodology a go. Whilst he doesn't talk much about specific function sizes (i.e. lines of code), he describes it in terms of a function should do one thing only, which will be described by the title of that function, and will be only at one level of abstraction.He then goes on to show many examples, generally in the 5-20 line size.In a later chapter he also talks about size of source files, which he defines a target size of maybe around 200-500 lines.But, as I was talking about earlier, I know this is contentious issue for programmers. I've been in a professional software testing course where the presenter was openly bagging Java developers for their very long function names being too similar and ever expanding number of classes. Then he went on to talk over some Ruby code that I thought was an absolute abomination of a mess.It’s all relative, but I just wanted to understand from an embedded context where cpu cycles, and more importantly for me with current processors, Flash memory size, is a precious commodity. Are there going to be any penalties for this style of coding.

author: Harvs
author_rank: Super Contributor
timestamp: February 17, 2013, 10:13:50 pm
content: Quote from: HackedFridgeMagnet on February 17, 2013, 10:06:37 pmI'm now using the STM32 range of ARMs, from their low cost M0 to M4F as the application dictates.Coding in a roll your own eclipse, Sourcery GCC/G++ and OpenOCD. Basically because it's free, though not the easiest to setup for the first time (albeit online tutorials can get you over the first humps.)I don't think you'll find many here that would state that moving from an 8bit range to ARMs is going to be a bad thing.

author: Mechatrommer
author_rank: Super Contributor
timestamp: February 17, 2013, 11:10:26 pm
content: Quote from: Harvs on February 17, 2013, 10:09:34 pmothers have covered that pretty well, such as inlining and orphan functions etc, specifically for the gcc. for that you need to dig more ide/compiler specific thing. from my experience, once you do this, you are ready to break any "clean coding practice". this is a matter of "compiler or system specific" which i believe the book wont bother to touch. you maybe lucky with gcc, but not neccesarily with other compiler. the sure fire to get the performance (speed and size) in any compiler is a "one big function". what the compiler do inside, is out of our reach. the easiest way to get what you want is change compiler setting and re-compile and analyze each output. you can change the code to satisfy the compiler and yourself, and re-compile. and you can do this again and again until you get the most perfect output. may as well the today's compilers are intelligent enough? well we need not care anymore, as usual. let me quote page 20... which caught my eye during "compiler" search...Quotei know its under "Make Meaningful Distinctions" topic, but i believe it also applies, generally, for the wellbeingness of a programmer. and another OT suggestion i've heard somewhere else, if its not enough, get bigger mcu or go for assembly, performance'wise. YMMV.

author: HackedFridgeMagnet
author_rank: Super Contributor
timestamp: February 18, 2013, 12:25:33 am
content: I like to write code with lots of function calls, the reasons are for "Procedural Abstraction" and also to help with code re-use.Naming the function and specifing the function definition are the key parts of Procedural Abstraction.Ideally somebody looking at your header file should be able to call your function from their code without actually looking at the implementation.ie. the function name should be meaningful, the parameter names should be meaningful and the types appropriate.In C++ The return value should be the obvious result of the function name, whereas in c the return result is often the error condition.In tightly constrained systems "Procedural Abstraction" wont always be possible.I got a lot of these ideas out of the book "Code Complete" and from doing O-O programming. I am sure many embedded developers approach it differently.

author: jerry507
author_rank: Regular Contributor
timestamp: February 18, 2013, 02:25:46 am
content: With the rise of ARM there is considerably less resource constraint then there used to be. At least for my typical use cases, it's never worth it to try and fit code into a small size, it just takes too long. My development time is typically much smaller than my budget, so I tend to follow the software practices of the PC folks more than the embedded folks. Their development cases more closely match what I usually see.

author: Harvs
author_rank: Super Contributor
timestamp: February 18, 2013, 07:37:57 am
content: To give a little bit more context, here's the test functions I was using:Code:#include "ledDriver.h"LedDriver::LedDriver(){enableLedGPIOClock();configPort();turnOffLeds();}//inlinevoid LedDriver::enableLedGPIOClock() {RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOC, ENABLE);}//inlinevoid LedDriver::configPort() {GPIO_InitTypeDef GPIO_init_struct;GPIO_init_struct.GPIO_Pin = LED2_pin | LED3_pin;GPIO_init_struct.GPIO_Mode = GPIO_Mode_OUT;GPIO_init_struct.GPIO_Speed = GPIO_Speed_Level_2;GPIO_init_struct.GPIO_OType = GPIO_OType_PP;GPIO_init_struct.GPIO_PuPd = GPIO_PuPd_NOPULL;GPIO_Init(GPIOC, &GPIO_init_struct);}//inlinevoid LedDriver::turnOffLeds() {GPIO_ResetBits(GPIOC, LED2_pin | LED3_pin);}Obviously it's not necessarily a reflection of what you'd do in reality, but it served the purposes by breaking down a function I already had.Everything except the constructor is declared private.The overhead for each function call by not inlining with -Os was 12 bytes (36 bytes total in this example). May not sound like a lot, but I think it's significant when it all adds up.

author: Mechatrommer
author_rank: Super Contributor
timestamp: February 18, 2013, 12:18:41 pm
content: its sensible to split function like that to smaller meaningfull tasks from OOP, reusability or what not point of view. but as you've figured it out, it can deviates from your "performance and space" modus operandi. its easy to show the overhead difference for that smaller code, not that so for the real application. and truly speaking from what i understand, you cant measure "overhead" by judging from code size alone, it is how much cpu cycle that is going to be wasted doing housekeeping work and function call. and then you need to look at how many times the "overhead" will be called in your application life or at a single event. the "compromise" will depends on your specific situation, there is no telling if inlining is necessary at all time vs code readability. for smaller application, pretty much you can inline without seeing too much space penalty. discussion of "inlined" functions and "code space" are pretty much trivial for a software of a significant complexity, imho. "algorithm optimization" is far superior than this, something like if you can make N.N operation into N.log(N). you code "clean", the performance should follows. YMMV.

author: Psi
author_rank: Super Contributor
timestamp: February 18, 2013, 12:37:45 pm
content: I would just make the functions as small as needed to make the code easy to read.Performance is very rarely an issue with modern computers/micros, also the compiler is often much smarter than you.Size-wise i guess there isn't much you can do but try it and see.

author: Mechatrommer
author_rank: Super Contributor
timestamp: February 18, 2013, 12:45:12 pm
content: Quote from: Psi on February 18, 2013, 12:37:45 pmperformance'wise... nothing is much "fun" than setting up a timer code or looking at your stopwatch to look how long it will take for a led to blink

author: andersm
author_rank: Super Contributor
timestamp: February 18, 2013, 02:06:04 pm
content: Quote from: Harvs on February 18, 2013, 07:37:57 amHave a look at theinlining sectionof GCC's manual. One thing immediately stands out: "calls that precede the function's definition cannot be integrated". Making the member functions private shouldn't make a difference, as a class implementation may be divided into several source files. Therefore when optimizing for size I'd expect that only function bodies cheaper than the function call would be inlined.

author: andyturk
author_rank: Frequent Contributor
timestamp: February 20, 2013, 06:08:59 am
content: Quote from: Harvs on February 17, 2013, 12:54:30 amIf you write something as a sequence of function calls, generally speaking, it's easiest for the compiler to translate those calls directly. So yes, you'll pay a little execution time for the function prolog/epilog, and perhaps a little extra code space to keep track of debugging info if you have that turned on, and don't forget about the stack space for pushing call-by-value arguments. Sounds bad, but does it matter?Unless you've done an analysis of the overall program and have identified that particular sequence of calls as absorbing a huge amount of your CPU resource (e.g., 90%) it's almost pointless to even worry about performance vs coding style. Just write it the way it makes the most sense.For blinking LEDs... why bother? But if your code is part of something whose complexity is polynomial or more (e.g., a sort routine), then you might gain something by tweaking the source."Make it right before you make it fast."Implicit in that statement is that you have some measurements to guide the optimization effort.

author: brainwash
author_rank: Frequent Contributor
timestamp: February 25, 2013, 10:49:32 pm
content: Quote from: Mechatrommer on February 18, 2013, 12:45:12 pmI know that at least MPLABX Simulator and CCS ICD have cycle-counting 'built-in'. In MPLABX if you set a breakpoint and 'resume' two times it will tell you the current cycle count at each break. In CCS you have to edit the breakpoint properties and modify something there. It's described in the basic tutorial.Regarding overhead: unless it involves critical timing I would just forget about it and deal with it later where you can see if you must optimize for size or for speed. It's always easy to go back and inline the code yourself or just copy it than it is to split it later. Plus the added productivity that is in between.