|
Forum Index : Microcontroller and PC projects : CMM2: MMBasic optimization tips (noob 2 noob)
| Author | Message | ||||
| abraxas Regular Member Joined: 16/06/2020 Location: CanadaPosts: 99 |
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 KingdomPosts: 10578 |
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: AustraliaPosts: 1116 |
@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: CanadaPosts: 99 |
Feel free to copy anything I wrote. Not even an attribution is required. |
||||
| MauroXavier Guru Joined: 06/03/2016 Location: BrazilPosts: 303 |
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: CanadaPosts: 99 |
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! |
||||
| The Back Shed's forum code is written, and hosted, in Australia. | © JAQ Software 2025 |