Repeated function calls in statement - Bad practice? - Page 1
Forum section: electronics / general

author: Psi
author_rank: Super Contributor
timestamp: March 17, 2016, 06:57:04 am
content: Quick question.Do you think this is bad practice?Code:uint32 BattADC_AVG = ( (MeasureBattADC() + MeasureBattADC() + MeasureBattADC() + MeasureBattADC() ) >> 2);My reasoning is that some odd compiler might optimize out the repeated calls and use the same value?Or should that never happen?

author: Boomerang
author_rank: Regular Contributor
timestamp: March 17, 2016, 07:02:16 am
content: This may happen if you have some way to declare the function as "deterministic".

author: Ian.M
author_rank: Super Contributor
timestamp: March 17, 2016, 07:23:33 am
content: As each function call is a sequence point, assumingMeasureBattADC()accesses at least one volatile object, any compiler that reorders or eliminates them is broken.There's a nasty trap here if the actual ADC read is interrupt driven and the function doesn't use volatile variables or other thread-safe method of receiving the data from the ISR, as if it doesn't access a volatile, write memory or produce other side-effects, the compiler cannot determine that the function's execution could produce a different result to that the abstract machine would produce executing it literally the first time, so would be free to re-order or eliminate the repeated function calls. That wouldn't be because your expression above invoking the function is dangerous, it would be because you have written a broken function.

author: Brutte
author_rank: Frequent Contributor
timestamp: March 17, 2016, 07:26:50 am
content: Quote from: Psi on March 17, 2016, 06:57:04 amIt is impossible to debug because the debugger would have stepped over this line in single step. Besides, it unrolls a loop which is a bad practice by itself. I'd use do{}while() and #define ADC_AVG_FILTER_EXP2 2.

author: mikerj
author_rank: Super Contributor
timestamp: March 17, 2016, 07:54:19 am
content: Why not put the averaging into the MeasureBattADC() function? If required you could pass an argument to set the number of readings to be averaged.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: March 17, 2016, 08:02:13 am
content: There is no such thing as good or bad practice, only more or less appropriate for a particular situation.A compiler would never optimise multiple calls like that, as at some point the function would access variables declared volatile (typically the ADC's hardware registers).It would break if the function simply returned the last known ADC value from a free-running continuous conversion process without waiting for a new reading, but that's not a compiler issue.

author: tszaboo
author_rank: Super Contributor
timestamp: March 17, 2016, 08:16:32 am
content: But why no a for loop. Makes it debuggable and shorter.

author: bitseeker
author_rank: Super Contributor
timestamp: March 17, 2016, 08:44:08 am
content: Agreed. Unless the overhead of a do or for loop is unacceptably high for your particular use case, unrolling it like that is unusual.

author: legacy
author_rank: Super Contributor
timestamp: March 17, 2016, 09:51:38 am
content: Quote from: Boomerang on March 17, 2016, 07:02:16 amthat is the first feature I have implemented in my "safeC" language

author: andyturk
author_rank: Frequent Contributor
timestamp: March 17, 2016, 02:56:59 pm
content: Quote from: Psi on March 17, 2016, 06:57:04 amSure it could happen. Depends on the definition of MeasureBattADC().If your function simply returns a constant and the definition is "seen" in the current compilation unit, then any compiler worth it's salt would replace all the calls with the constant and evaluate the whole thing at compile time.OTOH, if the function accesses volatile memory (or has volatile inline assembly) which prevents the compiler from optimizing, it'll do what you intend.

author: suicidaleggroll
author_rank: Super Contributor
timestamp: March 17, 2016, 03:27:19 pm
content: Quote from: mikerj on March 17, 2016, 07:54:19 amAgreed. Add an input to the function to control the number of iterations, and then stick a for loop inside.

author: AndyC_772
author_rank: Super Contributor
timestamp: March 17, 2016, 03:32:57 pm
content: IMHO, doing (or not doing) something in code for no better reason than someone says it's good (or bad) practice, is bad practice.

author: djacobow
author_rank: Super Contributor
timestamp: March 17, 2016, 04:14:41 pm
content: Certainly, no C or C++ compiler would optimize out calls like that, but I wonder if there are other languages that can identify if a function is idempotent and automatically memoize it. Of course, such a language would not be popular with system programmers. :-)Note that you can have functions that are idempotent, even though they touch hardware or volatile state. Imagine a missile class with methods: fuel(), arm(), and launch(). Obviously you can call launch() as many times as you like, but only the first time will do something interesting. The others methods could be coded that way, too.

author: westfw
author_rank: Super Contributor
timestamp: March 17, 2016, 05:31:43 pm
content: QuoteI also agree. Or a separate function "MeasureAvgBattV()"I don't see anything wrong with the repeated function calls. Well, aside from it probably being annoyingly slow - I'd prefer ADCs to be sampled by ISRs and/or DMA with running-averages maintained automatically, so that the actual "getADC" call is essentially instantaneous. In which case calling it four times in a row wouldn't be helpful.

author: grumpydoc
author_rank: Super Contributor
timestamp: March 17, 2016, 07:55:47 pm
content: Quote from: Psi on March 17, 2016, 06:57:04 amI suppose the question is - why do you want to do it that way and what is really wrong withuint32 BattADC_AVG = MeasureBattADC();BattADC_AVG += MeasureBattADC();BattADC_AVG += MeasureBattADC();BattADC_AVG += MeasureBattADC();BattADC_AVG /= 4;Which will probably generate the same code and is easier to read.Thought 1 - is uint32 the correct type for MeasureBattADC(), AtoD's generally produce signed values don't they?Thought 2 - using >> 2 instead of / 4 might be slightly faster but it's probably the worst bit of bad practice in your original statement. Unless you have proven that you need to save the execution time go for clarity. And if you do need to save the execution time say why in a comment. In fact there is at least some chance that the compiler will optimise a "divide by constant power of 2" to a shift for you anyway.

author: djacobow
author_rank: Super Contributor
timestamp: March 17, 2016, 08:19:16 pm
content: Quote from: grumpydoc on March 17, 2016, 07:55:47 pmNot even. gcc is clever enough to convert division of integers by power-of-two literals with shifts. However, for signed numbers >> 2 and / 4 may not give the same answer.Code:#include <stdio.h>int foo(int a) {return a / 4;}int bar(int a) {return a >> 2;}int main(int argc, char *argv[]){printf("%d\n",foo(-1));printf("%d\n",bar(-1));return 0;}On myLLVM 7.0.3 gives:Code:0-1You can see that in the assembly, they both use shifts, but for the division there is more work to make sure that shifting -1 to the right gets you zero, rather than just perpetual negative ones.Code:.section__TEXT,__text,regular,pure_instructions.macosx_version_min 10, 10.globl_foo.align4, 0x90_foo: ## @foo.cfi_startproc## BB#0:pushq%rbpLtmp0:.cfi_def_cfa_offset 16Ltmp1:.cfi_offset %rbp, -16movq%rsp, %rbpLtmp2:.cfi_def_cfa_register %rbpmovl%edi, %eaxsarl$31, %eaxshrl$30, %eaxaddl%edi, %eaxsarl$2, %eaxpopq%rbpretq.cfi_endproc.globl_bar.align4, 0x90_bar: ## @bar.cfi_startproc## BB#0:pushq%rbpLtmp3:.cfi_def_cfa_offset 16Ltmp4:.cfi_offset %rbp, -16movq%rsp, %rbpLtmp5:.cfi_def_cfa_register %rbpsarl$2, %edimovl%edi, %eaxpopq%rbpretq.cfi_endprocNot pasting it here, but for unsigneds you get the identical code.

author: grumpydoc
author_rank: Super Contributor
timestamp: March 17, 2016, 08:46:17 pm
content: Quote from: djacobow on March 17, 2016, 08:19:16 pmI'm having slight difficulty parsing your comment so I'm not sure whether you are agreeing with me or notYour posted example shows that gcc will happily convert divide by constant power of 2 to a shift. With ints code is added to the divide case to handle the sign but the actual division is a shift.QuoteIndeed, it produces completely identical code:unsigned int t1(unsigned int a){return a / 4;}unsigned int t2(unsigned int a){return a >> 2;}Compiled -O2 yields.file "test.c".text.p2align 4,,15.globl t1.type t1, @functiont1:pushl %ebpmovl %esp, %ebpmovl 8(%ebp), %eaxpopl %ebpshrl $2, %eaxret.size t1, .-t1.p2align 4,,15.globl t2.type t2, @functiont2:pushl %ebpmovl %esp, %ebpmovl 8(%ebp), %eaxpopl %ebpshrl $2, %eaxret.size t2, .-t2.ident "GCC: (GNU) 4.4.7 20120313 (Red Hat 4.4.7-3)".section .note.GNU-stack,"",@progbitsWhich is the same code, using a shift, for both functionsQuoteWhich is why gcc generates slightly different code for the two cases.MISRA bans (or advises against) shifts with integers doesn't it?Which, by the way, is another reason to write divide when you mean divide - suppose the code starts out with uint but someone later changes it (lets assume with good reason) to int - the shift might be a long way from the declaration so could be missed, introducing a subtle bug. If the code had used divide no such bug would get introduced.

author: SL4P
author_rank: Super Contributor
timestamp: March 17, 2016, 11:34:47 pm
content: My only problem with any of these suggestions is the average is taken over the period of 'n'back-to-backsamples... hardly an average. I would expect any number of samples to be taken over a longer period, or rolling average methodology to eliminate short term variations.Just sayin' I know it's not the point of the OP question.

author: Sal Ammoniac
author_rank: Super Contributor
timestamp: March 18, 2016, 12:03:04 am
content: Quote from: grumpydoc on March 17, 2016, 08:46:17 pmThere's nothing in MISRA C:2004 that precludes the use of shifts. The only requirement related to shifts isQuote

author: djacobow
author_rank: Super Contributor
timestamp: March 18, 2016, 12:30:31 am
content: I was agreeing. I mean, it is not any slower to use the divide, so you might as well. It's clearer.

author: Psi
author_rank: Super Contributor
timestamp: March 18, 2016, 01:09:06 am
content: Thanks everyone, lots of useful comments.The function just starts an ADC conversion, waits for it to finish and returns the value. Not using interrupts, (yeah I know it should be).There is a long running average that also uses this read function periodically.But in this case I just needed a custom read at that exact time for a special case, and wanted to reduce error a little.Will change it to use repeated += and / for better debugging and clarity

author: hamster_nz
author_rank: Super Contributor
timestamp: March 18, 2016, 01:57:32 am
content: Quote from: Psi on March 17, 2016, 06:57:04 amIgnoring the context given by the choice of variable and function names, this code can't handle the entire range of values returned by the function (assuming it returned a uint32). It may be preferable to use:Code:uint32 a = f()/4 + f()/4 + f()/4 + f()/4;This would not fail when f() returns an average value greater than 0x40000000.If you were concerned about the loss of the low two bits they could be tracked separately. Something like:Code:uint32 a;unit32 low_bits;for(i = 0; i < 4; i++) {uint32 s = f();low_bits += s&3;a += s/4;}a += low_bits/4;If you are calling this function regularly to assess the battery, you could also do something like this to only perform one reading at a time;Code:uint32 s0;static uint32 s1, s2, s3=0;uint32 BattADC_AVGif(s3 == 0) {/* First time we have read the battery voltage *//* Assuming function never returns 0 */s0 = MeasureBattADC();s1 = MeasureBattADC();s2 = MeasureBattADC();} else {/* Move samples along */s0 = s1;s1 = s2;s2 = s3;}s3 = MeasureBattADC();BattADC_AVG = s0/4 + s1/4 + s2/4 + s3/4;

author: djacobow
author_rank: Super Contributor
timestamp: March 18, 2016, 04:11:03 am
content: Quote from: hamster_nz on March 18, 2016, 01:57:32 amHamster's reco is a good one.I also like the classic first-order low-pass IIR, which consumes no memory other than storing the average. This compares favorably with big window filters if you desire a lot of smoothing and space is tight -- isn't it always?y = a*x + (1-a)x[n-1]https://en.wikipedia.org/wiki/Exponential_smoothingHere's one implementation (C++) with an example use, coded somewhat carefully to be flexible and still get you code with no multiplies or divides in it -- if you pick your constants carefully.Code:#include <stdio.h>#include <stdint.h>#include <stdlib.h>// This class implements a simple EMA filter. That is//// y = a * x + (1-a) * y[n-1]//// Because we seek to be fixed-point friendly, we// define://// a = ALPHA / DENOM//// Where both ALPHA < DENOM so that 0 < a < 1.//// However, to get the compiler to emit efficient code,// and particularly, code completely free of multiplies,// and divides, choose ALPHA and DENOM that are// powers of two, such as 1 and 256.//// Also, to get the compiler to use shifts instead of multiplies,// it must see the filter constants as literals rather than// variables. That's why this is a template.//// The state variable of this filter is essentially always// left-shifted by DENOM, so the larger the DENOM you choose,// say, for more filtering, will also use up some upper bits// of the state variable, so your range will be reduced.//// One solution to this is to use a larger type for the// state variable. Of course, you could also use fp types,// too. The types are parameterized in this template for that// purpose.template <typename SAMP_TYPE, typename STORE_TYPE,uint32_t ALPHA, uint32_t DENOM>class ema_c {public:ema_c() { };// not strictly necessary, but you can 'prime' the// filter with the first samplevoid init(SAMP_TYPE sample) {state = sample * DENOM;}// call every time you have a new sample, returns the// current averageSAMP_TYPE update(SAMP_TYPE sample) {state = ((ALPHA * sample) +state -((ALPHA * state) / DENOM));return (state / DENOM);}private:STORE_TYPE state;};int main(int argc, char *argv[]) {typedef int32_t sample_t;const int32_t alpha = 1;const int32_t denom = 64;// instantiate filterema_c<sample_t, sample_t, alpha, denom> filter;// prime filter to 0 -- not really necessaryfilter.init(0);int32_t ct = 0;// create a noisy square wave, and watch the filter track it.while (ct < 10000) {int32_t r = ((ct/1000)%2) * 5000 + (10000 * rand() / RAND_MAX);int32_t avg = filter.update(r);printf("%d,%d\n", r, avg);ct++;}return 0;}I know folks don't like C++ much for low-level coding, but I've found that once you get the hang of using it carefully, it's actually pretty good for the task. Templates are just macros with more features.The output of this looks something like this:

author: andyturk
author_rank: Frequent Contributor
timestamp: March 18, 2016, 04:13:39 pm
content: Quote from: djacobow on March 18, 2016, 04:11:03 amIMO, C++ is actually *better* than straight C for low-level coding, but that's a topic for another fist-fight.I'm using a EWMA template quite similar to yours. The main difference is that my alpha is just a const member (not a template parameter) and that I pre-compute 1-alpha. Here's the operative part:Code:/*** @brief recalculates the average for a new input sample* @param sample the new value* @return new average including the new input sample*/T operator()(T sample) {accumulator_ =((alpha_ * sample) + (one_minus_alpha_ * accumulator_) / Scale);return (*this)();}EDIT: I think the moving average approach works better when you're constantly sampling something at a steady rate. Then you can set alpha to 1/(sample_rate + R*C + 1) and have the EWMA simulate a low-pass RC filter. However, if you're sampling at odd intervals (as you might for reading a battery level), the filtering will confuse things.

author: djacobow
author_rank: Super Contributor
timestamp: March 18, 2016, 05:35:14 pm
content: Quote from: andyturk on March 18, 2016, 04:13:39 pmTook me awhile to get there, but I'm on board.

author: dannyf
author_rank: Super Contributor
timestamp: March 18, 2016, 05:55:55 pm
content: QuoteIt is always possible that some buggy compilers may do that but that's not an issue with the code.Having said that, I tend to write out the statement one at a time to make it more readable to myself.Code:if(s3 == 0) {/* First time we have read the battery voltage *//* Assuming function never returns 0 */s0 = MeasureBattADC();s1 = MeasureBattADC();s2 = MeasureBattADC();} else {/* Move samples along */s0 = s1;s1 = s2;s2 = s3;}Much easier to initialize s0..2 so you don't have to do the test each time you call that routine.Code:curr_avg = ((ALPHA * sample) +curr_avg -((ALPHA * curr_avg) / DENOM));return (curr_avg / DENOM);Not an efficient / good implementation - there have been discussions about that in the past here.

author: djacobow
author_rank: Super Contributor
timestamp: March 18, 2016, 05:59:59 pm
content: Quote from: dannyf on March 18, 2016, 05:55:55 pmPlease elaborate or point, as I'm always willing to learn. This implementation results in no multiplies or divides. I know it suffers from from a loss of range due to the the state being shifted by DENOM's bits.What else is wrong?

author: dannyf
author_rank: Super Contributor
timestamp: March 18, 2016, 06:28:18 pm
content: when alpha*curr_avg is smaller than denom, ....instead, keep track of the sum (ie. upscale everything by denom).

author: djacobow
author_rank: Super Contributor
timestamp: March 18, 2016, 07:33:36 pm
content: Quote from: dannyf on March 18, 2016, 06:28:18 pmLook more closely at the code. Everythingisupscaled by denom, and in fact, it works just fine with small samples.I'm not saying this code doesn't have other problems, but it doesn't have that particular problem. I admit it's a bit tricky to see because of the way I named the variables.Here's the same thing run, but with the input samples limited to between 0 and 15, so much smaller than 64 in this case:

author: dannyf
author_rank: Super Contributor
timestamp: March 18, 2016, 07:37:09 pm
content: This is what I use:Code://calculate exponentially smoothed moving averageint ma0(int sample) {static int sum=0, avg=0;//sum and avgsum+=(sample-avg);//update sumavg=sum/MA_WGT;//calculate the moving averagereturn avg;}MA_WGT is the same as your DENOM. ALPHA is set to 1.It should be much faster than yours. And you may be able to prove mathematically that they are the same.

author: djacobow
author_rank: Super Contributor
timestamp: March 18, 2016, 08:34:44 pm
content: Quote from: dannyf on March 18, 2016, 07:37:09 pmFirst, yes, mathematically they are the same. And in fact, if you pass in ALPHA=1 the compiler correctly eliminates it entirely.However, yours probably is /not/ faster -- not that it matters on something so minimalist.The salient difference between implementations is that you store the sum and avg and I only store the sum. Storing the sum saves you a calculation on each iteration (a shift), but it costs you an extra load and store.I put your code into a class and compiled it with gcc-arm -Os -fno-inline -S to see what comes and they were just as you'd expect:Mine:Code:.fnstart.LFB15:ldrr3, lsrr2, r3, #6subr3, r3, r2addr1, r3, r1strr1, @ sp neededlsrr0, r1, #6bxlrYours:Code:dannyf_ema_c<long, long, 1ul, 64ul>::update(long):.fnstart.LFB16:ldrr3, ldrr2, [r0, #4]subr3, r3, r2addr1, r3, r1strr1, lsrr1, r1, #6strr1, [r0, #4]@ sp neededmovr0, r1bxlrIf you allow inlining, I suspect yours might do a bit better because the compiler might be able to leave sum/avg in registers.

author: wolf32d
author_rank: Contributor
timestamp: March 19, 2016, 06:10:49 pm
content: Quote from: Psi on March 17, 2016, 06:57:04 amSince you evaluate a sum which is commutative everything is fine.BUT if you perform some non-commutative operation involving nasty function calls (like in the following code), unexpected things can (and will) happenCode:#include <stdio.h>// global varfloat a;float func(){a += 1.0;return a;}int main(){a = 0;printf("%.1f\n", func() - func() ); //expression 1a = 0;printf("%.1f\n", -func() + func() ); //expression 2}The first printf gives -1 as expected:the first func() call gives 1 and the second gives 2 therefore the result is -1.At this point one might think that the second expression should give (-1 + 2 = +1) but it gives -1 instead. The compiler (gcc) optimizes the second expression and produces the same machine code for both expressions.

author: Boomerang
author_rank: Regular Contributor
timestamp: March 20, 2016, 09:08:30 am
content: I'm curious about the following - is there compiler that will optimize this liney = sin(x) + sin(x) + sin(x) + sin(x);to call sin() function only once?

author: obiwanjacobi
author_rank: Super Contributor
timestamp: March 20, 2016, 10:31:20 am
content: How would the compiler know it is a function (in math sense) and not a method (changing state)?

author: wolf32d
author_rank: Contributor
timestamp: March 20, 2016, 11:25:49 am
content: Quote from: obiwanjacobi on March 20, 2016, 10:31:20 amA good compiler can distinguish math functions and functions that modify some data or some global vars. Gcc can perform some optimizations on math expressions. For example x / y can be replaced with x * (1/y) which is useful if (1/y) is subject to common subexpression elimination.

author: Boomerang
author_rank: Regular Contributor
timestamp: March 20, 2016, 02:37:30 pm
content: OK, the question was poorly defined...My question is: is there any compiler that could be instructed that some function is deterministic (does not modify anything outside of it's scope and for a given set of parameters always returns the same result)?

author: grumpydoc
author_rank: Super Contributor
timestamp: March 20, 2016, 02:51:45 pm
content: Quote from: Boomerang on March 20, 2016, 02:37:30 pmYes, marking a function "pure" in gcc means that it only uses its arguments and global variables and is a candidate for common subexpression elimination. Marking a function "const" means it only uses its arguments and does not read global memory (ie "const" is a stricter form of "pure").

author: andyturk
author_rank: Frequent Contributor
timestamp: March 20, 2016, 03:44:47 pm
content: And in C++ (since C++11), there's a constexpr keyword that tells the compiler a function will return a constant result (when passed a constant argument). The main use of constexpr is to write functions that can be evaluated at compile-time.

author: boffin
author_rank: Unknown
timestamp: March 20, 2016, 04:42:13 pm
content: Quote from: Boomerang on March 20, 2016, 09:08:30 amYes, meCode:y = 4 * sin(x)