How to optimize the embedded code and make efficient coding - Page 1
Forum section: electronics / general

author: amar
author_rank: Contributor
timestamp: April 02, 2013, 04:37:20 am
content: Hican someone suggest me how to make good code and optimize codeso that it can helps for efficient response of the system

author: smashedProton
author_rank: Frequent Contributor
timestamp: April 02, 2013, 05:42:22 am
content: This is a really wide topic to ask... may I suggest that you ask for a specific answer on the microcontroller thread? That should get you what you need. Thanks

author: westfw
author_rank: Super Contributor
timestamp: April 02, 2013, 08:06:44 am
content: Oh, no one does THAT anymore. Just buy a faster chip!

author: grumpydoc
author_rank: Super Contributor
timestamp: April 02, 2013, 08:09:12 am
content: QuoteOr faster JVM

author: digsys
author_rank: Unknown
timestamp: April 02, 2013, 11:34:36 am
content: There's not much chance you can learn this on a forum. You seriously will need to do a class, and get face to face tuition.There should be plenty of schools, colleges, technical places that offer courses. IF you believe you're a quick adapter,then try an on-line course, or search for free coding lessons to see if you catch on.As far as "no one does this any more" comments .. don't listen to them, plenty still do, just that most are lazy now :-)

author: ddavidebor
author_rank: Super Contributor
timestamp: April 02, 2013, 12:08:30 pm
content: Quote from: amar on April 02, 2013, 04:37:20 amIt's simple, time, no girls, and a lot of coffee

author: Ax_6
author_rank: Contributor
timestamp: April 02, 2013, 12:25:17 pm
content: Quote from: westfw on April 02, 2013, 08:06:44 amI'm not really of the same opinion... some times you can easily improve your code and save some money.For example if you want 8 clock pulses as fast as you can (and you don't have problem of memory space) instead of writingCode:for(int a =0;a<8;a++){out = 1;out = 0;}you can writeCode:out = 1;out = 0;out = 1;out = 0;out = 1;out = 0;out = 1;......out = 0;The code would be ugly! But, on the other hand It would be faster, in this case it could even double the speed of your clock.That's just an example but I hope that you get what I'm trying to say.

author: grumpydoc
author_rank: Super Contributor
timestamp: April 02, 2013, 01:04:04 pm
content: QuoteA good compiler should be able to do that for you (the technique is known as "loop unrolling" by the way.My JVM comment above was meant to be a little sarcastic, people are moving further and further away from low level coding and while this is a good thing in many ways it does partly explain why desktop operating systems and word processors today don't feel much faster than they were 10 or 15 years ago despite processors being 10's or 100's of times faster (a big chunk of the rest is that everything has to be accompanied with fancy graphics but I digress).The best optimisation is to choose the correct algorithm in the first place. Understanding different data structures and their trade-offs is also important. Micro-optimisations like loop unrolling can be important but can be automated - choosing an O(NlogM) algorithm over an O(N2) one can't.That said one sometimes needs to have low level skills - I once worked on an embedded system where I needed to grab about a megabyte of RAM (out of 4 IIRC) for an enhancement and was told by the main team that I couldn't as there was only about half a meg unused. It turned out that no-one in the main team understood structure packing (or rather the implications of getting it wrong). Once I'd re-ordered their data structures they were about half the original size so I had my 1 meg and the system had more free RAM when I finished than it did when I started.

author: andersm
author_rank: Super Contributor
timestamp: April 02, 2013, 01:24:12 pm
content: Pick the appropriate algorithms, learn to take advantage of the hardware and peripheral features. Adapt your coding style to the hardware. Learn the tools, figure out how to measure things - optimizations are useless if they're not applied in the right places. Learn about interrupts, tasks, threads and how to structure your application to best take advantage of them. The time spent waiting on eg. a slow peripheral can be better used doing something else.

author: kfitch42
author_rank: Frequent Contributor
timestamp: April 02, 2013, 02:42:38 pm
content: "Premature optimization is the root of all evil" - D. KnuthOptimization without a goal is really obfuscation.All optimization needs to be guided by a goal and by measurements.Potential goals: decreased code size, increased throughput, decreased latency, increased maintainability ...Optimization is about tradeoffs, so you need to make sure you are measuring more than one thing ... e.g. is a 1000% increase in code size worth a 0.1% increase in throughput?

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: April 02, 2013, 02:54:13 pm
content: Learn assembler first. that way you get a better understanding of the hardware and its limitations, and can look at the code that a compiler generates and be able to judge if it is doing things efficiently.

author: AlfBaz
author_rank: Super Contributor
timestamp: April 02, 2013, 03:21:45 pm
content: Just be careful when optimising that you don't get carried away with opaque constructsQuote

author: andyturk
author_rank: Frequent Contributor
timestamp: April 02, 2013, 03:37:37 pm
content: Quote from: amar on April 02, 2013, 04:37:20 am1) Get the code to work first. Doesn't matter if it's inefficient.2) Measure the parts you care about. Interrupt routines taking too long? Screen updates too slow? Using too much RAM? Whatever it is, measure where you are and figure out how much you need to save.3) If you're lucky, 50+% of the resource you're trying to optimize will be absorbed by one part of the system. Fixing that will make a big dent in your overall picture. This is known as "low hanging fruit". If you're not lucky, you'll have lots of modules each of which uses a small piece of the resource. In this case, there's no obvious place to start.4) Repeat steps 2 and 3 until your code is efficient "enough" or there's no more low hanging fruit to optimize.5) If it's still not efficient enough, go read Knuth.

author: Hypernova
author_rank: Unknown
timestamp: April 03, 2013, 01:07:56 am
content: Look at the math equation you are trying to convert to code and look for places where you can simplify.Eg If some symbolic math solver gives you A*B*C+A*B*D, use A*B*(C+D), Now you only have to do A*B once, for large equations that can make all the difference.General trade off to optimization is size vs speed like in Ax_6's example, identify any constants that can be precomputed.Eg, for fixed point math where 0~360 degree is represented by a 10 bit unsigned int you can have a x1024 array storing precomputed sin(x) values.

author: kfitch42
author_rank: Frequent Contributor
timestamp: April 03, 2013, 02:50:18 am
content: Quote from: andyturk on April 02, 2013, 03:37:37 pmI agree that is a good description of how to optimize ... but I would say step 4 should say repeat steps 1,2 and 3. A good automated test suite is a great help here. Code that computes the wrong answer really fast doesn't help anyone

author: IanB
author_rank: Super Contributor
timestamp: April 03, 2013, 03:15:34 am
content: I'm really surprised that no-one has recommended using code that has no I/O overhead and no side effects, like this:Code:NOPNOPNOPNOPNOPNOPNOPNOPNOPNOPThis code will run about as fast as it is possible for any code to run.

author: pyroesp
author_rank: Regular Contributor
timestamp: April 03, 2013, 04:14:36 am
content: I've done a 256-FFT on a 32 bit mcu. The samples where read from the ADC and sent to my transform function.In my first version, the transform function used to read the samples like this:Code:for (i = 0; i < fft_point; i++){something_fft = samples;// something// something}The compiler could have compiled it to something like this (pseudo code):Code:for_loop:ADD samples, i, reg1 // add i to address sample and save to reg1GETDATA reg1, reg2 // get the data at address reg1, write it to reg2MOV something, reg2 // move value of reg2 into somethingINC i // increase iCHECKLOWER i, fft_point, for_loop // check if i < fftpoint, if true, jump to 'for_loop'In the optimized version I changed the way I read my data from the buffers like this :Code:for (i = 0; i < fft_point; i++){++samples;something_fft = *(samples);// something// something}Which could get compiled to something like this(pseudo code):Code:for_loop:INC sample // increase sample pointerGETDATA sample, something // get the data at address sample, write it to reg2INC i // increase iCHECKLOWER i, fft_point, for_loop // check if i < fftpoint, if true, jump to 'for_loop'I gained 1 instruction in this example.Do this for most of your code and you'll gain some time.Most of this optimization is through experience and google (reading other people's code).PS: "samples" is a pointer/copy of the address of the samples buffer.P-PS: Try to set the compiler to optimize your code with "-OX", X being a number from 0 to 5 if I remember correctly (Might be different from compiler to compiler).

author: AlfBaz
author_rank: Super Contributor
timestamp: April 03, 2013, 04:21:54 am
content: Quote from: IanB on April 03, 2013, 03:15:34 amLOLIN this day and age it won't be so optimal as the java VM running on the embedded rtos running oop constructs will undoubtedly run each NOP thousands of cycles apart

author: grumpydoc
author_rank: Super Contributor
timestamp: April 03, 2013, 07:34:25 am
content: QuoteIf your <insert favourite language> compiler doesn't do the above for you throw it away and get a better one.The important steps for the human being in the equation are1) understand the problem2) understand your system3) choose the right data structures and algorithms4) measure the performance before "optimising" the code.QuoteYes, that sort of technique can be useful if you don't have a FPU.

author: Ed.Kloonk
author_rank: Super Contributor
timestamp: April 03, 2013, 08:46:32 am
content: A wise guru informed me once that the code you waste too much time on by trying to optimise it as you write it will be worse than the actual bottleneck or bloat that really impedes your program.Just code it. Code utill you are exahusted. Then try to comment it as well as you can before you fall asleep.

author: westfw
author_rank: Super Contributor
timestamp: April 03, 2013, 09:16:50 am
content: Code:NOPNOPNOPAh, but which NOP should you use? I mean, on the KL and KI CPUs, "JFCL" (Jump and Clear Flags, but don't clear any flags and don't jump) is the fastest nop, but on the KL CPU, you should should use "TRN" (Test Right half with nothing, do Nothing to those bits, and don't ever skip.) (I guess modern RISC CPUs don't have quite the variety of alternatives of the old CISC mainframes, but you might have to worry about whether a tiny loop of nop that fits in cache and matches the HW branch prediction expectations is faster than a long chain of inline nops...)I suspect that the OP, if he wasn't a troll, is more at the level of "don't use floating point on a cpu that doesn't have floating point hardware", than "don't use sprintf when strcat would work", or "ARMs have a 32-bit ALU, so using smaller datatypes can actually slow down computations as they are expanded/compressed from their memory locations to the forms the ALU can deal with, unlike most 8-bit CPUs where larger datatypes are nearly always slower."

author: andersm
author_rank: Super Contributor
timestamp: April 03, 2013, 11:22:41 am
content: Quote from: pyroesp on April 03, 2013, 04:14:36 amYou have a terrible compiler.QuoteVery much so. For example for GCC-based compilers only levels 0-3 are valid.

author: mikeselectricstuff
author_rank: Super Contributor
timestamp: April 03, 2013, 01:19:48 pm
content: Although there are all sorts of clever things a good (expensive) compiler can potentially do, in practice many don't, so it is well worth learning how to write code that even a dumb compiler will do a reasonable job of.But even the best compiler can't help a fundamentally flawed code design.For example it won't tell you that you don't need floating point just to display a number that happens to have a decimal point in it, and the best compiler will still take more code and cycles than a poor one given a better approach for the same task.A good compiler will also not tell you that you are using unnecessarily large data types.

author: grumpydoc
author_rank: Super Contributor
timestamp: April 03, 2013, 02:10:36 pm
content: Quotethis is typically the sort of micro-optimisation that is a waste of time unless youknowthat you need to shave a processor cycle or a byte of code,knowthat playing games like this with the high level code yields better assembler.This latter point will depend on the compiler - once optimisation is on the output from your two examples might be similar or even identical. It will also be dependent upon the platform which you need to know well.For example the AVR instruction set* has lousy indexing so I'd expect the code from your first example to be along the lines of your pseudo-code. Except that you ignore operand size issues so that is likely to introduce more actual assembler.* slight disclaimer, I've never hand coded AVR, just had a quick look at the instruction set a minute ago so might be wrong but you get the point, I'm sure.Arm, however has decent indexing so the whole "something_fft = samples[i ];" statement should be a single instruction and it might be that version will be better on that platform.AVR does have pre-decrement and post-increment so the compiler should be able to shangeCode:something_fft = *(samples);++samples;into a single instruction which does the assembler equivalent ofCode:something_fft = *(samples++);Now, you wrote that the other way round. That would be OK** if the initialisation of "samples" (outside the loop) was correct starting one before the data, then incrementing, then dereferencing but if you wrote it that way you'd probably deny the compiler an optimisation.** Apart from being invalid C - you're not supposed to generate addresses before the start of an array because on some segmented architectures address arithmetic might go AWOL at that boundary. You are allowed to generate the address one past the end of an array for comparison but not to dereference it.Even if you do "win" by playing these games move platforms and the rules change. It's much better to write code in as clear and un-convoluted way as possible and let the compiler take care of the rest at this level. If you really (after measuring) want to shave a cycle, or shave a byte then actually write the assembler code.

author: amar
author_rank: Contributor
timestamp: April 03, 2013, 05:19:37 pm
content: yes1st understand problem.2nd make algorithm/flow chartthen make a good working code.after that read code 3-4 time & try to optimize the code so that it can run fast and take less flash memory

author: pyroesp
author_rank: Regular Contributor
timestamp: April 03, 2013, 05:42:16 pm
content: @grumpydoc:Code:for (i = 0; i < fft_point; i++){something_fft = *(samples);++samples;// something// something}There, fixed it. Happy now?(Just a bit tired when writing that and I swapped those lines :/)@andersm: I "gained" an instruction in my pseudo-code example, I'm not talking about the exact compiled assembler code. That's why I said "I gained 1 instructionin this example.".I know little about asm so I did those changes to my code, measured the time the mcu is in the transform function by setting an output high when entering the function and low when exiting the function.The result is it's executed faster than without the changes to the code.The program is clear enough for me to understand and it's full of comments, something everybody should do but no one does.

author: andersm
author_rank: Super Contributor
timestamp: April 03, 2013, 05:50:24 pm
content: Quote from: pyroesp on April 03, 2013, 05:42:16 pmSure, but it still belongs to the class of transformations the compiler should be able to perform automatically. Unfortunately many embedded compilers are pretty bad, especially for lesser-known or single-vendor architectures.

author: hans
author_rank: Super Contributor
timestamp: April 03, 2013, 08:27:49 pm
content: Quote from: amar on April 02, 2013, 04:37:20 amFigure out what the key requirements are for your system.For e.g. , if you want to respond to an incoming pulse and you need to switch off a relay quickly. Are you going to write this?Code:main(){while(1){ReadDataFromSlowInterface();UpdateDisplay();if (PULSE == HIGH){RelayOff(); // quick quick quick!}}}You could go into straight 'micro management' and write the if {} in assembler. Is it going to help you a lot if the other 2 methods in your main loop are taking 100ms on average to complete? Not really, although the if loop may be very quick, there is other stuff in your way.Measure things if you don't know for sure what's slow or not efficient. This can be by single stepping code, seeing the RAM usage closely in your linker map, measuring timings with scopes, etc.If there is an issue, like I just described, see what may be interfering and what must be changed to resolve this. This can either be the interfering code or what you were testing.For e.g. , in the last example, I could also have thought. Let's put the slow code in a timer interrupt! (OUCH):Code:void tmr0_interrupt(){ReadDataFromSlowInterface();UpdateDisplay();}void main(){setupTmr0();while(1){//check it all the time.if (PULSE == HIGH){RelayOff();}}}This is bad. If the timer fires, the CPU is stuck in the interrupt for quite some time (say 100ms). In that time, the CPU is unresponsive. Although this code has been contributing to slow responses before, making changes like this isn't going to help at all.Instead, use something like a capture, ext interrupt, change notification pin to fire an interrupt directly when the digital pin changes. This way when a pulse comes, you can react with an interrupt..Code:void pulse_interrupt(){if (PULSE==HIGH){RelayOff();}}void main(){setupExternalPinInterrupt();while(1){ReadDataFromSlowInterface();UpdateDisplay();}}This way the interrupt is fired when an external pin changes (I'm assuming), and so you react instantly.Just a small example though. In the end, I don't think writing good programs is achieved by buying big books and reading lots of tutorials. Instead, read them anyway, but do a lot of projects and programming yourself. Hands on experience counts.Moreover if code works and is fast enough, there is no need to optimize it further. Unless you're being constrained by some value (memory or execution time constraints).. (manual) optimizations take a lot of effort.I've actually optimized a software SPI to achieve pretty good bitrates with an ENC28J60 chip with software SPI on a dsPIC33Fj128GP804. Hardware SPI seemed to yield 3.2Mbit, where software SPI (ASM) was 3Mbit. Software SPI in C was about 1 Mbit. All I did was unroll the loop and use some bset and bclr commands...Measurements were taken by receiving UDP packets (1.5kB each) as fast as possible.In this case I've 'optimized' a piece of code that slowing the system down, by rewriting the routine in ASM (which is usually quicker, though, sometimes it yields almost no difference), or using a hardware module instead. Why didn't I use the hardware SPI much earlier? Because I first wrote it in ASM and I was being really lazy to set up the hardware SPI module..... (which I eventually did for the sake of seeing what was faster)

author: andyturk
author_rank: Frequent Contributor
timestamp: April 03, 2013, 08:51:47 pm
content: Quote from: mikeselectricstuff on April 03, 2013, 01:19:48 pmLet's flip that around and say that immature compilers often generate naive code. And if you're seriously trying to squeeze performance out of a system based on a crap-tastic compiler, one of your first steps ought to be finding a better compiler (assuming that's an option).It seems that many of the OEM offerings for 8- and 16-bit mcus fall into the crap-tastic category. That's one of the reasons I like reaching for 32-bit ARM chips because GCC is very mature and costs $0.

author: grumpydoc
author_rank: Super Contributor
timestamp: April 03, 2013, 08:57:33 pm
content: QuoteThere is no need and I do not wish you to "make me happy".Actually swapping those lines around was useful for discussion (I suspected you had meant it the other way).I do want to try to illustrate that you might be surprised at which C code yields the "best" assembler and that this is a fragile concept depending on several variables (platform, C compiler, optimisation switches....). It is thus best avoided in production code.

author: nctnico
author_rank: Super Contributor
timestamp: April 03, 2013, 11:06:32 pm
content: At some point you may need to get the most of a compiler for a particular platform. In that case your still better off shuffling C code around an do manual loop unrolling than programming the algorithm in assembler.However, the best way to optimise is to start with a smart algorithm which doesn't need a lot of processing to start with.