Page History: Some Hints & Tips for writing efficient code.
Compare Page Revisions
Page Revision: 2020/06/30 11:34
Although there is merit in the argument that if you need speed, don't use an interpreted language, any code you write that is intended to be long-lived should be honed to be efficient.
Much of the below is not terribly important but it scratches my pedant itch. I come from a Z80/68000 background and older CPUs were starved of memory & power and it taught me to be frugal. None of it really applies to compiled code and modern CPUs but there are some elements of "sanitary" code development that will make you feel warm and fuzzy all over. It is worth mentioning that modern code suffers from a phenomenon colloquially known as "bloat" - this is largely the effect of not writing compact because the developer doesn't have to care in these days of immensely fast multi-core CPUs and gigabytes of memory. Old habits and all that...
There are plenty of methods - some open to discussion, that can help. These range from the simple to downright cryptic. And it's not a new concept. Simple tweaks in the way you do things can shave quite a bit off the cycle times for your code.
This isn't an exhaustive list bit more a collection of hints and suggestions.
Discuss, contribute.
Don't calculate things twice - use a variableIf you make the processor do some work that you are going to need later, save the result and don't repeat the effort. The following forces some string slicing and evaluation to be done twice.
If Val(Mid$(a$,2,2))=17 Then
Print "The value is ";Val(Mid$(a$,2,2)); " in that position
EndIf
Variables are much faster than evals. Better to have
x=Val(Mid$(a$,2,2))
If x=17 Then
Print "The value is ";x; " in that position
EndIf
Keep your variable names relevant and punchySelf documenting code is greatly helped by having variable names that describe what they are used for and it's a great idea... but... it can be over used, no-one will mind if you call a general purpose counter "x". Consider the following:
For MyBigLoopCountVariable = 1 To 10
Print MyBigLoopCountVariable
Next
doesn't improve clarity over
For x= 1 To 10
Print x
Next
Not to mention the smaller code footprint.
Short variable names are fine where applicable (and a tiny bit faster to parse too, so will shave off a few mS in tight loop with thousands of iterations), but the following is crying out for good variable names.
a(m,j)=c/d+1
looks a lot better as
ColorMap(current,pointer)=OldColVal/daynum+1
you stand a good chance of understanding what that line is doing just by looking at it.
Multiple tests in IF statementsThe If statement could be the arguably most used construct. It makes a decision and allows code to "look" intelligent by reacting to different situations. The statement evaluates an argument (just some data passed to something else) and boils it down to a 1 or 0... yes or no. Often time, the AND and OR operators are used to make the tests deeper but they can complicate a simple test and slow down the decision making. Try to make the arguments "aware" of what they are working towards. Consider the example we have to test the condition of a switch but only if it is daytime. We might write something like:
If DayTime=1 and SwitchPressed=1 then...
but remember the whole argument has to be evaluated. The switch is tested as well as seeing if it is daytime. In reality, if it isn't daytime, we don't give a hoot about the switch so you can help the parser by splitting things down. Consider:
If DayTime Then
If SwitchPressed Then
Yes it takes more space in your code, but it will be a little faster. If this is executed hundreds of times a minute, all those saving add up. This method allows the parser to immediately skip the whole testing of the switch state if it is night time. Note also, we don't need to test for =1. The parser is a trusting soul and it believes everything is true unless you say otherwise, so "If DayTime Then" is functionally identical to "If DayTime<>0 Then" but a little bit faster because no comparison (to 1) is made. Worth mentioning that every number except zero is true so you can use this trick for any non-zero condition.
Testing for permitted characters in a stringSo you are asking for the user to make a choice... Yes, No or Cancel... you might prompt them to enter only Y, N or C. The following code is fine right (By the way, don't use Input for this type of thing. It's two key-presses instead of one and your program loses control while waiting inside the Input statement.)?
Do
a$=Inkey$
Loop Until a$="Y" Or a$="y" Or a$="N" Or a$="n" Or a$="C" Or a$="c"
OK, that is a bit contrived and we all know how to use UCase$ so you might write it as
Do
a$=UCase$(Inkey$)
Loop Until a$="Y" Or a$="N" Or a$="C"
Happy with that? Remember what we said above about all the factors in an argument having to be evaluated. True if you are waiting for the user, the processor is likely twiddling its thumbs so the delay in processing is unimportant. But we are looking for efficiency. How about?
Do
a$=UCase$(Inkey$)
Loop Until Instr(" YNC",a$)>1
This is ultra efficient, tiny code and only a single evaluation. It is also dead easy to add other options... you just put them in the test string in the Instr() function. It works by testing any returned character against a list of allowed ones. "" (i.e. no key pressed) is always position one, always. So we put a dummy character in that position of our test string - the space, the real meat starts at position two. So if we return a value greater than 1, we have at least character two typed on the keyboard.
Don't put comments in the execution path unless you have toConsider the following
Sub DoSomething
'does something
a=a+1
EndSub
looks innocuous enough, but each time that sub is executed, the comment must be skipped over and that takes time. This is better but functionally identical
'does something
Sub DoSomething
a=a+1
EndSub
On the subject of comments, keep then succinct. Only where necessary - they take up space and slow code wherever they are. try to avoid white-space before or after a ' unless it helps in some way.
Don't make Subs or Functions for things you do only once unless it helps in some wayEach time you branch to a Sub program or Function, it takes some time to set up the working environment - despite any parsing of the code to go there, do something and come back.
If you only use a piece of code only once, put it in the main stream of the code unless it severely depreciates the readability - it is easy to isolate it and make it into a Sub later if you need to e.g. you end up calling it from somewhere else too.
Consider if you are writing a sort algorithm that needs to swap two variables. This will likely be called thousands, if not tens of thousands of times on a reasonably sized list. The swap is only performed in one place. Yes it looks nice to have a Swap function in your code (MMBasic has no native Swap), if each time you call it, if you waste just 500uS with all the house-keeping, over 2 thousand swaps that whole sort routine is now running a second slower than it might. Do you think that 2000 is a lot of swaps? In a bubble sort, a list of 100 items in worst case order will take 5000+ swaps to sort - just 100 items! At 500uS per swap, you have just added 2
1/
2 seconds to the sort time by branching out to a nice Swap function - and that doesn't include *any* computation towards the task.
Stare at your code, re-read it, polish it, streamline it. Be proud that it is as efficient as you can get it but don't be resentful when someone shows you a better way. That is free learning! Remember: efficiency is what you determine it to be, not just small, not just fast - it has to be a balance of the two that you like and can give an approving nod to.
Consider a Cosine calculation. You could write a tiny routine that uses Chebyshev polynomials to calculate the Cos of any angle. It will be small - but it will be slooooow (comparatively speaking). If you analyse the requirement, you might find that you only need to calculate to a tenth of a degree... Now you could make up a table of 3600 (10 steps for each of 360 degrees) with all the values fro the relevant Cosine and simply look the result up by jumping to the right point in the table: the input degrees, multiplied by 40 (10 for each degree and 4 for each floating point number) and added to the place where your table starts and you have your answer. No calculation at all. Super fast... but also super big; 14.4K of memory taken up. You could reduce this by 3/4 by performing some massaging of the input angle based on its quadrant but now you are complicating things so the efficiency of speed might no longer justify the inefficiency of memory.
This, incidentally, is the eternal conundrum of computing; you can have it big and fast or small and slow.
Lots more to come...