Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 03:07 14 Nov 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : CMM2: MMBasic optimization tips (noob 2 noob)

Author Message
abraxas
Regular Member

Joined: 16/06/2020
Location: Canada
Posts: 99
Posted: 02:43pm 08 Jul 2020
Copy link to clipboard 
Print this post

If you followed my previous posts since I joined here a few weeks ago you might know I've been wrestling with MMBasic to get it to perform well enough to eventually make a polished, faithful clone/remake of Boulder Dash. It's a bit more challenging than I thought it would be due to the performance limits of the system. '

I attached my latest iteration which can likely be optimized a bit more but already runs at very playable speeds. The viewport "swimming" still makes it feel a bit laggy but it isn't. The effect is caused by the way that I slide the viewport into place to make it more faithful to the original game. I will probably add an optional setting where the viewport gets pushed by Rockford one tile at a time. That will make viewport adjustments feel a lot more snappy though it deviates from the original game. Some other ports of the game (e.g. MSX) already do that. As does Mauro's demo. Anyway, have a "play" and tell me what you think.

Below I want to share my findings trying to optimize my naive implementation that you'll find in game_orig.bas file. The one you want to use though is called game.bas. The difference is dramatic with the former taking over 80ms to draw a frame plus whatever the cost of the game logic. The latter takes 10-12ms on average to draw a frame with about the same amount of time being consumed by the game logic (so far).

This language is pretty fast on CMM2 but it's NOT like programming on a multi-GHz x86 core. You are running on a 400MHz ARM core and you incur the penalty of MMBasic interpretation. In practice the performance limits make it behave roughly like a 16MHz x286 running compiled Pascal or C or ASM on a solid 8 bit system. It's not nothing but it's not limitless either. However, even this analogy is flawed as this BASIC contains really fast graphics routines that would not have been available on a 286. You have stuff like sub-millisecond BLIT commands and that will flip a lot of your intuitions on their head because sometimes a draw command might be quicker than any logic performed to avoid drawing!

Consider a tile game like BDash. It was my intuition to optimize the engine by avoiding drawing tiles that haven't changed from one game frame to the next. Seems logical right? Instead of doing this:


FOR n=0 TO rowCount
 FOR n=0 TO columnCount
   BLIT blitMyTile
 NEXT
NEXT


You will be tempted to do this:


FOR n=0 TO rowCount
 FOR n=0 TO columnCount
   IF needsToRefreshExp THEN
     BLIT blitMyTile
   ENDIF
 NEXT
NEXT


The problem with the above is that evaluating the needsToRefreshExp may well cost more cycles than doing the BLIT in the first place thus making this optimization actually harmful. Even if you only BLIT one tile per game frame you will evaluate the expression on EVERY tile. Don't ever forget what happens in your tightest loops. They will be where you have to be the most frugal.

An optimization that actually helped in my case was to figure out if there are any rows that can be skipped. Thus my render and game loops ended up looking more like this:


FOR n=0 TO rowCount
 IF rowNeedsToRefreshExp THEN
   FOR n=0 TO columnCount
     BLIT blitMyTile
   NEXT
 ENDIF
NEXT


Here the difference is that the expression in the IF statement is only evaluated per row making it worth the expense of the extra interpreter work.

This segways to my key point. Be aware of the cost of interpretation. Do some back of the napkin evaluations of key constructs. For example: an empty FOR loop over 1000 elements will take at least 8ms. An IF statement evaluated a thousand times in that loop may add many milliseconds to that depending on the complexity of the expression itself. Keep expressions very simple in your tightest loops. Ideally just evaluate if a value is 0 or not. You can often do this because the memory is plentiful so you can create lookup tables in the form of arrays with near impunity. This changes the optimizations you write. Instead of bit fiddling to compress things into single ints you might want to have auxiliary arrays that store flags even if that will waste  memory. Your CPU cycles will be a scarcer commodity. After all you have megs of RAM to work with which even on early 16 bit machines was hardly the case (Amiga 500 shipped with 512K of RAM).

Another thing to note. If you do have complex rendering code and it's separate from your game loop make sure your rendering loop can yield to the game logic when necessary. Use the TICK timer functions to record when you're due for the next game state computation and bail out of any rendering. At least in my case it did wonders to improve the interactivity of the game.  

Finally the cost of calling to SUBs and FUNCTIONs is non-zero. MMBasic does not appear to inline much. I noticed a boost in performance not just by replacing FUNCTION calls with code blocks but even using hardcoded values instead of CONST variables offered a bit of a performance gain. that's a bit unfortunate but if you want every last drop of performance that's what you'll have to do. My advice is to always create a separate .bas file with small snippets where you constantly test your assumptions. Use the timer to time things and always ask yourself "how much will this extra line cost" for stuff you put in the innermost game or rendering loop. Sometimes doing the "dumb" thing such as blitting or sprite painting may turn out more efficient.

Always be calculating (ABC) in your head how many times you execute some line of code and then test in a small example whether any optimizations are worthwhile or just doing the work anyway runs quicker because the cost of testing dwarfs the benefit of  not doing stuff. Remember you're not on a preemptive OS. All the CPU is yours while your program runs. You get it all but that's all you get :)

Hope this helps some of you and makes your MMBasic experience a bit better. Maybe the veterans here will want to pipe in with their hints and tips?
BDASH.zip
Edited 2020-07-09 00:50 by abraxas
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10578
Posted: 03:09pm 08 Jul 2020
Copy link to clipboard 
Print this post

Congrats on the game and also a very useful post - thanks.

One big overhead in functions and subroutines is local variables as they get created and deleted each time the subroutine is called. Obviously one of the big benefits of subroutines and functions is precisely that they have local variables though  

Setting variables as STATIC may improve this (untested)  but you then need to be careful that you don't assume they are set to zero when used. Local strings and arrays are worst case as they have to get memory off the MMBasic heap as well as allocating the variable header.

Making sure variable names are unique in the first 4 characters will offer minor advantages.

Finally check the new MATH command for simple things like setting arrays, copying arrays and finding MINs and MAXs in arrays. This will operate much faster than for loops in Basic
 
panky

Guru

Joined: 02/10/2012
Location: Australia
Posts: 1116
Posted: 11:36pm 08 Jul 2020
Copy link to clipboard 
Print this post

@abraxas and matherp,

Gentlemen, can I have your permission to add your very valuable observations/recommendations above into a chapter of Peter's "Graphics Programming the Colour Maximite 2" that I have been working on?

For Mauro - if you have any words of wisdom that you would like to add, I would be very pleased to include them also.

I hope to include a final chapter called Optimisation.

Doug.

Edit: Obviously, with full attribution to the authors. Thanks.
Edited 2020-07-09 09:37 by panky
... almost all of the Maximites, the MicromMites, the MM Extremes, the ArmMites, the PicoMite and loving it!
 
abraxas
Regular Member

Joined: 16/06/2020
Location: Canada
Posts: 99
Posted: 12:10am 09 Jul 2020
Copy link to clipboard 
Print this post

Feel free to copy anything I wrote. Not even an attribution is required.
 
MauroXavier
Guru

Joined: 06/03/2016
Location: Brazil
Posts: 303
Posted: 10:15pm 09 Jul 2020
Copy link to clipboard 
Print this post

Abraxas,

You are an excellent programmer and explain very well about optimization. I tested your Boulder Dash and I believe you will surpass any difficult and can do a perfect port or even better than the original. Congratulations!

PS.: Your code is MUCH more organized and indented than mine. My sources sometimes are weird even to me  
Edited 2020-07-10 08:18 by MauroXavier
 
abraxas
Regular Member

Joined: 16/06/2020
Location: Canada
Posts: 99
Posted: 01:18am 10 Jul 2020
Copy link to clipboard 
Print this post

Thanks Mauro. Seeing these words from someone of your caliber means a lot to me.

I do strive to be organized with indentation and capitalization. But game development is something I haven't done since childhood and learning a bunch of stuff that probably comes to someone like you in a split second. I already spent many hours working on this engine and it's barely started!
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025