Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

DarkBASIC Professional Discussion / Simple Ways to Optimize Code

Author
Message
Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 30th Aug 2009 11:55 Edited at: 2nd Sep 2009 14:44
There are a lot of coding practices that will optimize your code.
Some have only a small performance gain, but if the code is repeated a lot then the optimizations can add up.

Faster Object Loading


TIPS & LITTLE TRICKS
Loop Unroll


Pull out continually computated quantities


Use For/Next instead of Do/Loop for your main loop.


Loop Unswitching


Partial Redundancy Elimination


Common Subexpression Elimination


General Tips:


===================================================

Contributions by IanM:

How to really optimize
1. Pick a better algorithm.
Yes, it's difficult - sometimes you actually have to think about the problem and it's not always easy to come up with new ideas, but it will give you better results than micro-optimisations will almost 100% of the time.

2. Make it right before making it faster.
Debugging code is a harder job than writing code. Optimisation usually makes code less clear to read, and unclear code is harder to debug.

3. Measure.
Become religious about it. Measure several times, PROVE where the problem is, make the changes, then measure it again. PROVE that it is faster in the expected way. In any place that you've made the code less readable, put a comment in to explain what you've done and why, so that if you need to debug in future, you'll at least have half a chance.

Array access is relatively slow.
If you are accessing an array element more than twice in a loop, put it into a variable temporarily while you are working on it.



The win gets bigger the more array accesses you remove in this way.


Shortcut evaluation
DBPro has not implemented short-cut evaluation. However, for an IF statement, and where you are carrying out AND/&& evaluations, you can fairly easily simulate it.




In the first IF statement, every part of the expression is evaluated before the result is checked - that's 7 operations.

In the second IF statement, the first part is evaluated, and only if true does it go on to evaluate the second, and only if that's true does it evaluate the third, and only if that's true does it evaluate the fourth - that's a minimum of 1 operation and a maximum of 4 operations. Even in the worst case where every evaluation matches, it provides better results than the first IF statement.




Change the values of x and y to check it out (change them to zero for example).


FOR loop evaluations

Don't use functions (either your own or plug-ins) to provide the value for either the top of the loop, or the step - if you must do something like this, always put the results into a variable.
The reason for doing this is that DBPro can call these functions multiple times each time around the loop.

Run this and see how many times each of the 'Get' functions is called:



If your functions are 'expensive' then a lot of time could be lost by them being called multiple times.


Don't code it yourself
If it's already in a plug-in that you have or can afford, use it.

Obviously, code it if you want to figure out how to do something, but once you've done that, put your code to one side and use the plug-in. Unless the plug-in code is especially inefficient, there's no way you can match its speed, even for a simple function. (As a simple example, see the recent 'Highest of two values' in the Code Snippets board)

Avoid type conversions, especially the hidden ones
Passing an integer to a function or command that accepts a float value will cause the compiler to introduce a hidden conversion. In fact, passing a value of any type to a function/command that expects another type will introduce a conversion.

This can happen almost anywhere. For example, all of the object positions, rotation and scaling commands accept float arguments. If you are repositioning or rotating these objects a lot, and using integers to do so, then you are wasting cycles.

In addition, DBPro does not do any type conversion during runtime. For example, if you have a command 'XAngle# = XAngle# + 1', the '1' is an integer value that will be converted at runtime and then added to the variable. This will be marginally slower than the correct 'XAngle# = XAngle# + 1.0'.

So you now know that the compiler treats numbers without a decimal point as an integer, and those with one as a float - basically that 1.0 <> 1.

Did you also know that if you use a hex number (0x12345678), that it will be treated as a dword, and that conversion from an integer to a dword and vice versa will also cost you cycles?



Apart from the four basic types (integer, float, dword and string) which can be populated without type conversions, the remaining types (boolean, byte, word, double integer and double float) cannot be populated without a type conversion or other indirect means, and because of this, any constant values used in calculation alongside these types will be inherently a little less efficient.


Avoid repeated memory allocation
One more that BatVink raised with arrays, but where he could actually have gone much further:

There are lots of places where either you or DBPro will allocate chunks of memory to carry out the actions you require. The more you can minimise this within your game 'action' loops the faster your game will run.

- Don't load new media when you can clone it.
- Don't clone an object when you can instance it.
- Don't grow arrays when you can presize them.
- Don't grow arrays by 1's when you can grow them by 10's, 100's or 1000's.
- Don't create memblocks when you can reuse an existing one.
- And less obvious, when manipulating strings, do it in as few steps as possible - every new or temporary string is a memory allocation.

eg:
a$ = mid$(x$,3) + mid$(x$,4) + mid$(x$,5)

The above statement allocates 4 temporary strings and one final string.
mid$(x$,3) ==> T1
mid$(x$,4) ==> T2
T1 + T2 ==> T3
mid$(x$,5) => T4
T3 + T4 => a$

For strings in particular: Always use plug-ins where you can.

As an exception to this advice, always use the TEXT command when joining or outputting multiple strings on a single line - for some reason, DBPro is especially inefficient when using PRINT for this:



Cache effects when accessing arrays
The cache in your processor can have a significant effect on the speed of your array access if care is not taken. For the cache, accessing memory locations in order is more efficient that accessing them out of order or randomly.

However, in DBPro, even if you think you're accessing them in order on a multi-dimensional array, you may not be. If you have a 2D array, the array item a(1,1) is NOT next to a(1,2) like it would be in most languages, but is next to a(2,1).

For instance, the following array defined as ARRAY(4,3):
A11 A12 A13
A21 A22 A23
A31 A32 A33
A41 A42 A43

Would be stored in a DBPro array in the following order:
A11 A21 A31 A41 A12 A22 A32 A42 A13 A23 A33 A43

(yes, I know I excluded the 0 item in each index)

That means that it's actually faster to run through every item in a multidimensional array by incrementing the indexes at the start of the list first rather than the end of the list as is normally done.

Note that you'll only see this effect if the array plus whatever other stuff your program is doing is large enough that it swamps your cache.



If you don't see a difference, increase the ARRAY_SIZE constant.

===================================================

Contributions by Diggsey:
Use IanM's matrix1 plugins whenever you can

When comparing the distances of objects, compare the squared distance instead of the actual distance (removes the need for a costly square root).

When you do need to calculate the actual distance, create a vector of the desired dimensions, and use the vector length commands to find its length instead of calling 'sqrt' directly (which for some reason is slow beyond belief...)

Precalculate random numbers, the 'rnd' command is very slow.

Whenever you need to store arrays of multiple values, use a single array and a UDT, instead of multiple arrays. Especially when you are going to be resizing these arrays.

If there is a very costly operation, store the result in a variable, and then keep a flag as to whether the result is valid. Whenever you know that the result will be invalid, unset the flag. To get the result, check if the flag is set, and only recalculate the result if it is not, otherwise you can use the aready-calculated value. I'm using this a lot in my game engine for the calculations of inverse matrices and world matrices for nodes in the scene graph.

Certain code formations which can cause the compiler to use literally hundreds of ASM instructions where only a few are used with only minor changes to the code. Just look in the ASM dump to see examples of this.

===================================================================

Contribution by Van B
If you're done with a sprite, delete it - it doesn't have the same performance impact as when deleting objects, and a lot of hidden sprites will eat your frame rate like it's candy. So don't hide, just delete, and rely on the SPRITE Spr,x,y,imb command to place the sprites you do need.

===================================================================
If anyone has any other code optimizations that they know of that I didn't cover (WHICH I KNOW THERE IS, SO SPEAK UP), it would be most helpful for you to post them, then I will add them to the list with credit going to you. (Special thanks to IanM)
Green Gandalf
VIP Member
19
Years of Service
User Offline
Joined: 3rd Jan 2005
Playing: Malevolence:Sword of Ahkranox, Skyrim, Civ6.
Posted: 30th Aug 2009 14:48
Some interesting results there - but also some surprising ones, for example, why should an unrolled make object loop be so much faster? I'd have thought the processing saving was negligible compared to the effort involved in making an object. Perhaps there's something else going on? Or perhaps the timing function has been misused?
Van B
Moderator
21
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 30th Aug 2009 14:55
That has to be the most surprising one, the unrolled object creation loop.

Some really nice tips, excellently presented .


Health, Ammo, and bacon and eggs!
Pincho Paxton
21
Years of Service
User Offline
Joined: 8th Dec 2002
Location:
Posted: 30th Aug 2009 16:17 Edited at: 30th Aug 2009 16:47
Well I think that the For Next loop is the most surprising one. Do Loop does no check, so there must be something wrong with the C code there.

EDIT: Just wondering now if Goto might be faster, you don't have to reset it.

IanM
Retired Moderator
21
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 30th Aug 2009 16:53
Argh! I hate these kind of threads - post a list of micro-optimisations that are difficult to prove either true or false.
Then someone has to come along and be Mr Nasty to set things right.

So I guess that I'll be Mr Nasty for today

Quote: "Faster Object Loading"

Phew! This one is true. I'm not going to bother backing it up with a code proof, though the OP should have.


Quote: "Loop Unroll"

This is a 'sometimes true' optimisation. Unfortunately the OP picked object creation and deletion for the example...

Quote: "why should an unrolled make object loop be so much faster?
Perhaps there's something else going on?"

It isn't, and yes there is.

Loop unrolling for objects is not true - it only appears to be true if you don't take certain precautions. I'll let the comments in the code speak for themselves:


... when the playing field is flat, there is almost no difference.
However, as it can be true sometimes, read on to some of the notes below.


Quote: "Pull out continually computated quantities"

Yes, this is true. However if you are in the situation of pulling parts of calculations out of your loops, perhaps the time would be better spent looking at the algorithm instead. A recent example was in this thread (http://forum.thegamecreators.com/?m=forum_view&t=156974&b=1), where the calculations were removed althogether in my post, and made entirely redundent in Green Gandalfs last post.


Quote: "Use For/Next instead of Do/Loop for your main loop."

Sorry, but the code posted is nonsense.
If you use WHILE/ENDWHILE or REPEAT/UNTIL or DO/LOOP to simulate a FOR/NEXT loop, of course the FOR/NEXT loop is going to be faster - it's designed for that purpose, while the other loop types aren't!

If you're going to use the structured loops commands for purposes they weren't designed for then you may as well rip the whole lot out and replace them with GOTO's, as you've already missed the point.


Quote: "Loop Unswitching"

Yes, this is true, however, it's one of those optimisations that you should do at the end once your code is working correctly, with timings of 'before' and 'after', and it'll only have a reasonable effect on large loops anyway.
Basically, you'll get more gain by spending your time picking a better algorithm, instead of making your code a little less readable.


Quote: "Partial Redundancy Elimination"

Yes, this is true and related to 'Pull out ...' above. Also see 'Loop unswitching'.


Quote: "Common Subexpression Elimination"

Again, see above.


Quote: "Try to use numbers instead of variables where possible"

Minimal gain.
There is a school of thought that says you should not have any numerical constant in your code except for 1's and 0's - I'm not sure I'd go that far, but I'd certainly lean in that direction. DBPro has the #CONSTANT command anyway, so use named constants instead of magic numbers.


Quote: "Only initialize & give values to variables when you need them, not at the beginning of the program. It saves memory (RAM)."

For anything except strings and arrays, this is meaningless as far as memory is concerned. Forgetting to initialise variables causes a large number of the bugs you will ever see - Deliberately doing this is tantamount to inviting those bugs in the door.

For arrays and strings, the reasons are different, but the answer is the same - you aren't worried so much about memory usage in a game, but speed. If you force windows to go away to locate a new chunk of memory for your game process mid-game, you are going to spoil the flow of your program and potentially introduce large noticable delays. Basically, you should only worry about it when memory usage actually becomes a real problem.



How to really optimise.
1. Pick a better algorithm.
Yes, it's difficult - sometimes you actually have to think about the problem and it's not always easy to come up with new ideas, but it will give you better results than micro-optimisations will almost 100% of the time.

2. Make it right before making it faster.
Debugging code is a harder job than writing code. Optimisation usually makes code less clear to read, and unclear code is harder to debug.

3. Measure.
Become religious about it. Measure several times, PROVE where the problem is, make the changes, then measure it again. PROVE that it is faster in the expected way. In any place that you've made the code less readable, put a comment in to explain what you've done and why, so that if you need to debug in future, you'll at least have half a chance.


Finally, if someone offers optimisation tips, GET PROOF, GET THE CODE. Don't believe them until they prove it, while trying not to be Mr Nasty if you can.

Green Gandalf
VIP Member
19
Years of Service
User Offline
Joined: 3rd Jan 2005
Playing: Malevolence:Sword of Ahkranox, Skyrim, Civ6.
Posted: 30th Aug 2009 19:19
For someone who hates these threads he certainly spent a lot of time on it didn't he?

Ah! That's why he hates them of course.

Thanks IanM for bringing us all down to earth (as usual ).

The one thing I'm not sure about is the named constant thing. The MS DX9 SDK docs are a good example of what I mean. You often find things like "D3DFiltering can be set to DX3DthingyONE or DX3DthingyTWO, etc". You then have to wade through pages and pages, first to find that DX3DthingyONE means use DX3DthingyFILTERINGA, and yet more pages to find that the thingyFILTERING you actually wanted was DX3DthingyFILTERINGZ which is obtained by setting D3DFiltering to 26. Phew!

Plain numbers are simpler sometimes - but I can see you then can have the reverse problem since "26" can mean different things in different contexts.
IanM
Retired Moderator
21
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 30th Aug 2009 19:47
Quote: "For someone who hates these threads he certainly spent a lot of time on it didn't he?"

On the positive side though, I get to be Mr Nasty for a day

Quote: "Plain numbers are simpler sometimes - but I can see you then can have the reverse problem since "26" can mean different things in different contexts."

Yep, you see '8' in your code a few weeks after entering it ... what does it mean? Or, you see D3DBLEND_INVDESTALPHA ... you see immediately what that means.

As it's a constant, it is replaced during compile with '8', making it no slower at runtime, and only marginally slower during compilation, and a heck of a lot faster to understand.

My point in this post is that the 'entity' that is going to spend the most time on the code is you, which means that making the code more readable and understandable to you is more important than almost any other part of the coding process.

Green Gandalf
VIP Member
19
Years of Service
User Offline
Joined: 3rd Jan 2005
Playing: Malevolence:Sword of Ahkranox, Skyrim, Civ6.
Posted: 30th Aug 2009 20:05
I agree about the readability thing and something along those lines is essential.

The problem is when constants like D3DBLEND_INVDESTALPHA are not pre-defined in your own code - you have to spend ages wading through a manual or something just to find that you need to define that variable to be 6 or whatever. Of course, the DX SDK is designed for professionals who probably use software which defines all these constants for you.

On the other side of the coin I've often tried to debug someone else's code which contains things with "meaningful" names like TERRAIN_X_SIZE. To track down the bug you might need to know what the actual value is. This can involve a lot of to-ing and fro-ing between different parts of the code. Getting the balance right isn't always easy.

Quote: "My point in this post is that the 'entity' that is going to spend the most time on the code is you, which means that making the code more readable and understandable to you is more important than almost any other part of the coding process."


Agree wholeheartedly.
Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 30th Aug 2009 20:37 Edited at: 30th Aug 2009 20:37
Quote: "Yep, you see '8' in your code a few weeks after entering it ... what does it mean? Or, you see D3DBLEND_INVDESTALPHA ... you see immediately what that means."


Ironic really, seeing as the small amount of DBPro's code I have seen used numbers for all the directx commands instead of the named values

@GG
Many of those values are in enums, so you can simply go to the functions definition, right click on the enum name, and click 'go to definition' on that too, and you will have the list of values.

For the #defines you can go to the function's definition, and in the comment above the function it will tell you the name of the define, and may even give you a possible value. You can go to that value's definition to see the list.

Alternatively, you can click inside the function name, and press F1 and it will tell you the parameters in the help files, and give you links to the lists of possible values for each one, with a short explanation of what each means

Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 30th Aug 2009 22:06 Edited at: 3rd Sep 2009 15:05
@GG - IanM showed why that happened, I didn't know about that . Apparently, my rolled loops assigned the memory, then the unrolled ones didn't have to assign the memory when it came to them. Anyways, at the end of this post I'll add the code that I used for each test.

@Van B - Thanks, but I think that IanM brought us back to reality on that one.

@Pincho - I don't know, but I wouldn't go so far as to use goto in relation to the main loop, spaghetti code is much worse than extended code

@ IanM - You have a right to be Mr Nasty, you know more about how DBPro works than I do by far.

Quote: "Argh! I hate these kind of threads - post a list of micro-optimisations that are difficult to prove either true or false."


I can prove each one of them true, except for the loop unrolling one, since you told me about the Windows memory assignment, and certain cases on PRE.

Quote: "Yes, this is true. However if you are in the situation of pulling parts of calculations out of your loops, perhaps the time would be better spent looking at the algorithm instead. A recent example was in this thread (http://forum.thegamecreators.com/?m=forum_view&t=156974&b=1), where the calculations were removed althogether in my post, and made entirely redundent in Green Gandalfs last post."


Yes, but if it is possible to pull constant calculations out, if they are used, then it would be a better alternative compared to leaving them in. It wouldn't have the same performance gains as writing a better algorithm, but it does improve the code performance.

Quote: "Sorry, but the code posted is nonsense."


The code that I posted was the benchmark that I used to test each loop against one another. It wasn't a supplement to use unlike the other optimizations. The performance gains were the results of those loops in that benchmark.

Quote: "Basically, you'll get more gain by spending your time picking a better algorithm, instead of making your code a little less readable."


I agree that you'll see a bigger gain with a better algorithm, this is just a smaller step in a faster direction. Also, if people commented their codes thoroughly (which I didn't here), their codes would be just as readable.

Quote: "Minimal gain.
There is a school of thought that says you should not have any numerical constant in your code except for 1's and 0's - I'm not sure I'd go that far, but I'd certainly lean in that direction. DBPro has the #CONSTANT command anyway, so use named constants instead of magic numbers."


It is still an optimization, if only minimal. Most of the optimizations that I posted are just simple, little things that you can do to squeeze out another millisecond or two. Also, do you mind if I add the #CONSTANT to my first post, under General Tips?

Quote: "For anything except strings and arrays, this is meaningless as far as memory is concerned. Forgetting to initialise variables causes a large number of the bugs you will ever see - Deliberately doing this is tantamount to inviting those bugs in the door."


Okay, seeing that it doesn't matter where you initialise variables, I'll take that one off of the General Tips.

Also, IanM, would you mind if I added your ways to optimize up at the top? I'd like to be able to have a comprehensive list one day.

My Benchmark Codes:

Faster Object Loading:


Loop Unroll (though IanM has found why this one was so fast optimized):


Pull out continually computated quantities


Use For/Next instead of Do/Loop for your main loop.


Loop Unswitching


Partial Redundancy Elimination


Common Subexpression Elimination
Pincho Paxton
21
Years of Service
User Offline
Joined: 8th Dec 2002
Location:
Posted: 30th Aug 2009 22:36
Quote: "@Pincho - I don't know, but I wouldn't go so far as to use goto in relation to the main loop, spaghetti code is much worse than extended code "


A Goto as a Do Loop is not spaghetti code. You only have a single instance of it, the same as a single instance of a Do Loop. But using code to take over from the Do Loop is improper anyway, so I don't know why you used the odd smiley at the end.

Green Gandalf
VIP Member
19
Years of Service
User Offline
Joined: 3rd Jan 2005
Playing: Malevolence:Sword of Ahkranox, Skyrim, Civ6.
Posted: 30th Aug 2009 22:39
Quote: "@GG
Many of those values are in enums, so you can simply go to the functions definition, right click on the enum name, and click 'go to definition' on that too, and you will have the list of values.

For the #defines you can go to the function's definition, and in the comment above the function it will tell you the name of the define, and may even give you a possible value. You can go to that value's definition to see the list.

Alternatively, you can click inside the function name, and press F1 and it will tell you the parameters in the help files, and give you links to the lists of possible values for each one, with a short explanation of what each means"


I know - but it can still be a lot of work to find out something very simple. In some cases it has taken me half an hour to find out what I want to know - and in others the docs are incomplete. Try finding out what values all the bytes, dwords, etc, need to be in a volume or cubemap DDS file for example. I resorted to taking apart some images byte by byte to get the last few right.

I think my pet hate is the MS documentation - everything you need to know is there (usually ) but isn't easily located unless you've been using it 24 hours a day for 6 months.
IanM
Retired Moderator
21
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 30th Aug 2009 23:34
For the amount of stuff in MSDN, I find it amazing that you can actually (eventually) find what you need. TBH, I think MS should be congratulated on actually making it navigable.

@Digger412,
Most of the problems I have with your optimisations is that if you have to go there, you've already lost the war. Just from a simple cost/benefit measure, the amount of time you have to spend doing those small optimisations against the minimal speedups you get from them, you have to do an awful lot of work, when you could spend the same amount of time elsewhere (on algorithms, or optimising media into DBO format etc) and get a much bigger payoff.

I guess it can't hurt too much though, so I will contribute.


Array access is relatively slow.
If you are accessing an array element more than twice in a loop, put it into a variable temporarily while you are working on it.



The win gets bigger the more array accesses you remove in this way.


Shortcut evaluation
DBPro has not implemented short-cut evaluation. However, for an IF statement, and where you are carrying out AND/&& evaluations, you can fairly easily simulate it.



In the first IF statement, every part of the expression is evaluated before the result is checked - that's 7 operations.

In the second IF statement, the first part is evaluated, and only if true does it go on to evaluate the second, and only if that's true does it evaluate the third, and only if that's true does it evaluate the fourth - that's a minimum of 1 operation and a maximum of 4 operations. Even in the worst case where every evaluation matches, it provides better results than the first IF statement.



Change the values of x and y to check it out (change them to zero for example).


FOR loop evaluations
Don't use functions (either your own or plug-ins) to provide the value for either the top of the loop, or the step - if you must do something like this, always put the results into a variable.
The reason for doing this is that DBPro can call these functions multiple times each time around the loop.

Run this and see how many times each of the 'Get' functions is called:


If your functions are 'expensive' then a lot of time could be lost by them being called multiple times.


Don't code it yourself
If it's already in a plug-in that you have or can afford, use it.

Obviously, code it if you want to figure out how to do something, but once you've done that, put your code to one side and use the plug-in. Unless the plug-in code is especially inefficient, there's no way you can match its speed, even for a simple function. (As a simple example, see the recent 'Highest of two values' in the Code Snippets board)

sladeiw
15
Years of Service
User Offline
Joined: 16th May 2009
Location: UK
Posted: 31st Aug 2009 00:00 Edited at: 31st Aug 2009 00:01
Never thought of doing the shortcut evaluation like that, much better. I was doing multiple lines something like

which is much more messy!

A nice feature for an IDE would be an option to automatically optimise all the `and` evaluations into `then if`. Maybe this would cause problems though?

I know from experience that this particular optimization can make a big difference, especially when you're checking lots of slow variables like point() values etc.

As for optimizations, it's true that a better algorithm can give you a much bigger boost than tricks. You stand a much better chance of a major optimization (Generally a complete re-write!) if you haven't made your code more confusing by using strange structuring.
Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 31st Aug 2009 00:24 Edited at: 31st Aug 2009 01:05
@ IanM - Thanks. I know that most of them are like little "last minute" add-ins to squeeze out that little bit more. My goal in making this thread was to get community contributions. Everyone could benefit from knowing a more efficient way of doing something. Also, I hope that this isn't limited to just coding practices (or the Core Commands), but for it to extend into things like more efficient sprite usage, how to get faster file access, or even faster 3d math functions. It should be contained to optimization topics, and not drift into other things such as "Look what I can do! *shows an optimized drawing program*".

On the array access code, you gain by storing a(j) into x in the alternative method so you wouldn't have to call it 3 times, compared to calling a(j) 3 different times in the first, right?

@Pincho - Oh, okay, you meant like:
main_loop:
(other code)
goto main_loop

Hmm...it would definitely bear scrutiny...I'm experimenting...now.

EDIT:
@Pincho - I wonder if gosub would be faster?:
main_loop:
(other code)
gosub main_loop

EDIT2:
Okay, gosub is just SLIGHTLY slower than for/next, but it would most definitely be more stable, since it doesn't have to have an ending value, or one you'd have to change to insure that the loop wouldn't end.

In this code:


The for/next finishes in 9 milliseconds, gosub in 10 milliseconds. I didn't even test the other loops, goto (faster than all except f/n and gosub) gave out at only 50k iterations.
Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 31st Aug 2009 01:12
My speed tips:
Use IanM's matrix1 plugins whenever you can

When comparing the distances of objects, compare the squared distance instead of the actual distance (removes the need for a costly square root).

When you do need to calculate the actual distance, create a vector of the desired dimensions, and use the vector length commands to find its length instead of calling 'sqrt' directly (which for some reason is slow beyond belief...)

Precalculate random numbers, the 'rnd' command is very slow.

Whenever you need to store arrays of multiple values, use a single array and a UDT, instead of multiple arrays. Especially when you are going to be resizing these arrays.

If there is a very costly operation, store the result in a variable, and then keep a flag as to whether the result is valid. Whenever you know that the result will be invalid, unset the flag. To get the result, check if the flag is set, and only recalculate the result if it is not, otherwise you can use the aready-calculated value. I'm using this a lot in my game engine for the calculations of inverse matrices and world matrices for nodes in the scene graph.

I can't think of any off the top of my head, but there are certain code formations which can cause the compiler to use literally hundreds of ASM instructions where only a few are used with only minor changes to the code. Just look in the ASM dump to see examples of this.

BatVink
Moderator
21
Years of Service
User Offline
Joined: 4th Apr 2003
Location: Gods own County, UK
Posted: 31st Aug 2009 01:22 Edited at: 31st Aug 2009 01:26
Quote: "Shortcut evaluation"


Thank You! I knew about the problem ,didn't realise this was the solution.

Quote: "Use only types that you need, for instance use integer instead of floats if you aren't going to be dealing with decimals."


I thought integers were cast into floats before being used. I may be wrong, just a rumour I heard.

Quote: "Use numbers instead of variables"


As far as evidence goes, I don't have any on the PC platform. However, in midrange and mainframe terms, this isn't the best way to go. Each hard-coded value requires memory and the access overhead associated with it. But using a variable to represent common numbers reduces this significantly. For example, if you use the number 1, one thousand times, it requires that many memory locations. If you use a variable representing 1, it needs one location, and can be held in a more accessible entity. This is common practise in business applications.


I must admit, I have fallen foul of erroneous results a few times. It's easily done, and I'm always aware that there are many other factors that could be skewing the results. When benchmarking my own stuff, I tend to repeat the test 10 times or so alternately, to level out the playing field. So that would be performing a test 1 million times, for each method, ten times over, for example - 20 million repetitions.

Quote: "Whenever you need to store arrays of multiple values, use a single array and a UDT, instead of multiple arrays. Especially when you are going to be resizing these arrays."


...although avoiding resizing is a much better way to go. I set my arrays to a size that I think will be adequate. I only ever increase their size by 10% at a time if I break their capacity, never by single elements.

Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 31st Aug 2009 01:33
Quote: "I thought integers were cast into floats before being used. I may be wrong, just a rumour I heard."


That is true when integer variables are passed to functions which only accept floats, but for everything else, integers are faster.

Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 31st Aug 2009 01:39 Edited at: 31st Aug 2009 01:59
Quote: "I thought integers were cast into floats before being used. I may be wrong, just a rumour I heard."


I don't know, but I do know that if you divide 2 integers, you get an integer, and not a float.

Quote: "As far as evidence goes, I don't have any on the PC platform. However, in midrange and mainframe terms, this isn't the best way to go. Each hard-coded value requires memory and the access overhead associated with it. But using a variable to represent common numbers reduces this significantly. For example, if you use the number 1, one thousand times, it requires that many memory locations. If you use a variable representing 1, it needs one location, and can be held in a more accessible entity. This is common practise in business applications.
"


Hmm....I'll test that one, I have been wondering about the access times for variables versus hardcoded numbers, too...I should also be able to find the memory allocation, also..

What I normally do is code in a for avg=1 to 10/50/500 that'll repeat everything, so I can do all of my averaging in one big go...Also, since I have Texture Maker running making a huge skybox, it's easier just to do it all in the program at once.

EDIT: Updated the "Use Gosub instead of Do/Loop for your main loop" Section, Gosub and F/N beat out Do/Loop, Repeat/Until, While/Endwhile, and Goto by an EXTREMELY long shot. Here is the code, can others confirm?:

Green Gandalf
VIP Member
19
Years of Service
User Offline
Joined: 3rd Jan 2005
Playing: Malevolence:Sword of Ahkranox, Skyrim, Civ6.
Posted: 31st Aug 2009 03:01
Don't you run out of stack with your gosub method? Your code crashes if you run the loop too many times.

I still have a vague recollection that there is something else going on that IanM hasn't mentioned - something to do with Windows I/O perhaps, or for/next loops don't include certain windows checks? Can't recall what they were though.
Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 31st Aug 2009 03:15
Oh, didn't know that...Well, I have plenty of things to test tonight then.
Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 31st Aug 2009 03:29 Edited at: 31st Aug 2009 03:29
@GG
For..Next loops don't check for the escape key being pressed (so as long as you call 'sync' which also does the check (I think!) it's OK to use a For..Next loop as the main loop)

Libervurto
17
Years of Service
User Offline
Joined: 30th Jun 2006
Location: On Toast
Posted: 31st Aug 2009 03:31
Interesting stuff.
Do you guys still use memblocks?
are they faster than arrays?

TGC Forum - converting error messages into sarcasm since 2002.
Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 31st Aug 2009 03:33
I prefer IanM's 'banks', or raw memory myself

Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 31st Aug 2009 04:16
@Diggsey - If you put in "if escapekey()=1 then end" it provides no problem =)
Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 31st Aug 2009 13:21
Yes, but then it is probably about the same speed as the other loops!

Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 31st Aug 2009 14:33
Nope, F/N and Gosub still hold their own, and Gosub actually comes out on top!

Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 31st Aug 2009 14:37
@Digger412

GG has already told you that you SHOULD NOT use gosub to make a loop! Try taking out the 'if y<1000 then goto main' line and see what happens! The program will just crash with a stack overflow error...

Benjamin
21
Years of Service
User Offline
Joined: 24th Nov 2002
Location: France
Posted: 31st Aug 2009 14:50
When a GOSUB is performed the return address is placed on the stack, so if you are constantly using it without returning you are constantly adding to the stack. Consequently your program will go BOOM! if you do this enough.
Green Gandalf
VIP Member
19
Years of Service
User Offline
Joined: 3rd Jan 2005
Playing: Malevolence:Sword of Ahkranox, Skyrim, Civ6.
Posted: 1st Sep 2009 01:31
Quote: "Consequently your program will go BOOM! if you do this enough."


Have I missed an important update? When I tried that I merely got a crash and no BOOM!

Dammit! Just realised - I had sound switched off.
Benjamin
21
Years of Service
User Offline
Joined: 24th Nov 2002
Location: France
Posted: 1st Sep 2009 02:07
Quote: "Have I missed an important update? When I tried that I merely got a crash and no BOOM!"


It's only supported on NVIDIA hardware.
Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 1st Sep 2009 03:20
@GG and Diggsey and Benjamin - I know, I wasn't going to use it in a program, I just wanted to test it for benchmarking purposes. For fun, I guess one could say. However, would it be safe for one to use the F/N loops? I know that it wouldn't be correct coding procedure, but I'm having fun testing these out.
Van B
Moderator
21
Years of Service
User Offline
Joined: 8th Oct 2002
Location: Sunnyvale
Posted: 1st Sep 2009 16:27
Just a little one from me - if your done with a sprite, delete it - it doesn't have the same performance impact as when deleting objects, and a lot of hidden sprites will eat your frame rate like it's candy. So don't hide, just delete, and rely on the SPRITE Spr,x,y,imb command to place the sprites you do need.


Health, Ammo, and bacon and eggs!
IanM
Retired Moderator
21
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 1st Sep 2009 20:32
Can I just say that if I see anyone in these forums replace loop commands with a GOTO or a GOSUB, that I will never ever speak to them again In fact, I may just make it a condition of using my plug-ins!


Here's another one which reiterates what BatVink was saying and Diggsey supported, but seems to have been passed by:

Avoid type conversions, especially the hidden ones
Passing an integer to a function or command that accepts a float value will cause the compiler to introduce a hidden conversion. In fact, passing a value of any type to a function/command that expects another type will introduce a conversion.

This can happen almost anywhere. For example, all of the object positions, rotation and scaling commands accept float arguments. If you are repositioning or rotating these objects a lot, and using integers to do so, then you are wasting cycles.

In addition, DBPro does not do any type conversion during runtime. For example, if you have a command 'XAngle# = XAngle# + 1', the '1' is an integer value that will be converted at runtime and then added to the variable. This will be marginally slower than the correct 'XAngle# = XAngle# + 1.0'.

So you now know that the compiler treats numbers without a decimal point as an integer, and those with one as a float - basically that 1.0 <> 1.

Did you also know that if you use a hex number (0x12345678), that it will be treated as a dword, and that conversion from an integer to a dword and vice versa will also cost you cycles?



Apart from the four basic types (integer, float, dword and string) which can be populated without type conversions, the remaining types (boolean, byte, word, double integer and double float) cannot be populated without a type conversion or other indirect means, and because of this, any constant values used in calculation alongside these types will be inherently a little less efficient.


Avoid repeated memory allocation
One more that BatVink raised with arrays, but where he could actually have gone much further:

There are lots of places where either you or DBPro will allocate chunks of memory to carry out the actions you require. The more you can minimise this within your game 'action' loops the faster your game will run.

- Don't load new media when you can clone it.
- Don't clone an object when you can instance it.
- Don't grow arrays when you can presize them.
- Don't grow arrays by 1's when you can grow them by 10's, 100's or 1000's.
- Don't create memblocks when you can reuse an existing one.
- And less obvious, when manipulating strings, do it in as few steps as possible - every new or temporary string is a memory allocation.

eg:
a$ = mid$(x$,3) + mid$(x$,4) + mid$(x$,5)

The above statement allocates 4 temporary strings and one final string.
mid$(x$,3) ==> T1
mid$(x$,4) ==> T2
T1 + T2 ==> T3
mid$(x$,5) => T4
T3 + T4 => a$

For strings in particular: Always use plug-ins where you can.

As an exception to this advice, always use the TEXT command when joining or outputting multiple strings on a single line - for some reason, DBPro is especially inefficient when using PRINT for this:




Cache effects when accessing arrays
The cache in your processor can have a significant effect on the speed of your array access if care is not taken. For the cache, accessing memory locations in order is more efficient that accessing them out of order or randomly.

However, in DBPro, even if you think you're accessing them in order on a multi-dimensional array, you may not be. If you have a 2D array, the array item a(1,1) is NOT next to a(1,2) like it would be in most languages, but is next to a(2,1).

For instance, the following array defined as ARRAY(4,3):
A11 A12 A13
A21 A22 A23
A31 A32 A33
A41 A42 A43

Would be stored in a DBPro array in the following order:
A11 A21 A31 A41 A12 A22 A32 A42 A13 A23 A33 A43

(yes, I know I excluded the 0 item in each index)

That means that it's actually faster to run through every item in a multidimensional array by incrementing the indexes at the start of the list first rather than the end of the list as is normally done.

Note that you'll only see this effect if the array plus whatever other stuff your program is doing is large enough that it swamps your cache.



If you don't see a difference, increase the ARRAY_SIZE constant.

Plotinus
15
Years of Service
User Offline
Joined: 28th Mar 2009
Location:
Posted: 1st Sep 2009 23:15
Quote: "This can happen almost anywhere. For example, all of the object positions, rotation and scaling commands accept float arguments. If you are repositioning or rotating these objects a lot, and using integers to do so, then you are wasting cycles."


Now I was under the impression that, as a rule, integers are quicker than floats. So any variable that is never going to anything other than a whole number should be made as an integer, not as a float.

So suppose I'm only ever using integers to position my objects, because it's quicker to use integers. It seems from what you're saying that the position commands themselves will go more quickly if I give them floats. So what should I do? Should I have something like this:



Or should I just give up on using integers to store these values, and use floats all the time, even though that will slow down other calculations involving them?

PEH
18
Years of Service
User Offline
Joined: 18th Dec 2005
Location: usa
Posted: 1st Sep 2009 23:59 Edited at: 2nd Sep 2009 00:06
So if I wanted to see the stack overrun how many times do I have to do it?

This is the code I am using:

so far I am over 2.5 million

edit: just found out! silently crashed around 2.8 million.
IanM
Retired Moderator
21
Years of Service
User Offline
Joined: 11th Sep 2002
Location: In my moon base
Posted: 1st Sep 2009 23:59 Edited at: 1st Sep 2009 23:59
@Plotinus,
I'm not giving anyone any rules that must be followed, because there are exceptions to every rule you've read in this thread, and sometimes you may have good reasons to simply ignore the rule given. All I will say is to be aware of how DBPro does things and take it into account.

In your first piece of code, all you have done is allocate an extra 4 bytes of storage (for the float variable), and made the conversion easier to read. Your second piece of code is more efficient.

However, you may also have many other calculations to determine the value of 'speed' in your code, and it may be more efficient to do those calculation using integer maths (which is marginally faster than float maths) and convert at the end.

Basically, I suggest you go back to my original post in this thread where I critiqued the whole idea of micro-optimisation and read the very last section on 'How to really optimise'. ANY optimisation carried out without measurement is useless - you won't know whether your changes have made the code better or worst and effectively you'll just be guessing and hoping that it's faster.

Valle
18
Years of Service
User Offline
Joined: 10th Mar 2006
Location: in your girlfriends bed
Posted: 2nd Sep 2009 00:07
thanks for the distance-tipps diggsey, they're awesome !


sladeiw
15
Years of Service
User Offline
Joined: 16th May 2009
Location: UK
Posted: 2nd Sep 2009 00:07
@peh

I cried when I saw that code....
try this (takes about 2 seconds to quit on mine)

PEH
18
Years of Service
User Offline
Joined: 18th Dec 2005
Location: usa
Posted: 2nd Sep 2009 00:20
hehe yeah I just did that myself. I was bored so I wanted to watch the numbers lol.

here's my new code but it looks like you use timers that is probably better:

try it with the goto command and I don't get the crash
Green Gandalf
VIP Member
19
Years of Service
User Offline
Joined: 3rd Jan 2005
Playing: Malevolence:Sword of Ahkranox, Skyrim, Civ6.
Posted: 2nd Sep 2009 01:24
Quote: "try it with the goto command and I don't get the crash "


Why should you?
Diggsey
18
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 2nd Sep 2009 01:33
The goto command is completely different from a gosub.

A gosub equates to a 'call' instruction. A goto equates to a 'jmp' instruction.

'call' pushes the current instruction pointer onto the stack and then jumps to the new address. 'jmp' simply jumps to the new address, and does not put anything on the stack. (So you won't run out of stack space!)

The return statement in DBPro equates to a 'ret' instruction. A 'ret' instruction pops a pointer off the stack, and then jumps to the address in that pointer (which would be just after the place in the code where the gosub was called). So as long as the gosubs and returns match up, the stack will stay level.

PEH
18
Years of Service
User Offline
Joined: 18th Dec 2005
Location: usa
Posted: 2nd Sep 2009 02:00
Quote: "Can I just say that if I see anyone in these forums replace loop commands with a GOTO or a GOSUB, that I will never ever speak to them again"

Just wondering why IanM said that then. I assumed that it was the same problem.
Also Mobiius made it sound like that too in this thread:
http://forum.thegamecreators.com/?m=forum_view&t=157177&b=1
Digger412
16
Years of Service
User Offline
Joined: 12th Jun 2007
Location:
Posted: 2nd Sep 2009 05:32
I'd like to say thanks to everyone who has contributed, and keep the tips coming! Everyone in the community can benefit from this.

@PEH - Goto doesn't cause stack overflow because, like Diggsey says, it doesn't store a returning destination, like gosub does. Eventually, after calling gosub enough, millions of returning destinations can reside in the stack, eventually causing a crash...

@IanM - Wow, never knew that DBPro stored arrays like that! It seems out of order yet logical at the same time..
Plotinus
15
Years of Service
User Offline
Joined: 28th Mar 2009
Location:
Posted: 2nd Sep 2009 10:08
Quote: "Basically, I suggest you go back to my original post in this thread where I critiqued the whole idea of micro-optimisation and read the very last section on 'How to really optimise'. ANY optimisation carried out without measurement is useless - you won't know whether your changes have made the code better or worst and effectively you'll just be guessing and hoping that it's faster."


Fair enough. I was just wondering how great the slowness associated with using integers in position commands was compared to the speed gained by using integers in calculations, but I suppose you're right that there is no straightforward answer to that.

Quote: "Just wondering why IanM said that then. I assumed that it was the same problem.
Also Mobiius made it sound like that too in this thread:
http://forum.thegamecreators.com/?m=forum_view&t=157177&b=1"


The reason why GOTO would cause a stack overflow in the code posted in that thread is that he is using GOTO to jump out of a DO-LOOP loop. So he is constantly encountering DO without a concluding LOOP. The stack overflow is caused by this concatenation of DOs, not by the GOTO itself. GOTO is a perfectly innocuous instruction that you can use as much as you like and it won't cause such problems; the reason its use is frowned upon is simply that it is inelegant and makes code difficult to read, not because of any technical issues.

Quote: "The goto command is completely different from a gosub."


It baffles me that people don't get this. The thought of using GOSUB as a substitute for GOTO sends shivers up my spine; I cannot imagine why anyone would do something so daft. And making a loop out of GOSUBs makes me feel physically ill. I honestly can't imagine a worse coding method than that. Even spaghetti code with GOTOs all over the place isn't that bad - at least it might work.
Benjamin
21
Years of Service
User Offline
Joined: 24th Nov 2002
Location: France
Posted: 2nd Sep 2009 10:27
Quote: "The reason why GOTO would cause a stack overflow in the code posted in that thread is that he is using GOTO to jump out of a DO-LOOP loop."


This wouldn't cause a crash; none of the looping constructs require altering the stack (although variables associated with the loop may be stored on the stack, but the stack pointer itself won't change).
sladeiw
15
Years of Service
User Offline
Joined: 16th May 2009
Location: UK
Posted: 2nd Sep 2009 11:43 Edited at: 2nd Sep 2009 11:44
@PEH

I think IanM said that because it's generally terrible programming practice. Not a problem if your coding a quick routine to try something out, but if you start writing programs with 1000's (or much more!) lines of code you will eventually come unstuck. Plus, 6 months later when you come back to that code to re-use a routine in your new project, it will be hard for even you to understand.

If you ever hope to get a job professionally programming or working commercially in a team, you need well designed structured code so everyone can understand or modify it.
Plotinus
15
Years of Service
User Offline
Joined: 28th Mar 2009
Location:
Posted: 2nd Sep 2009 14:49
Quote: "This wouldn't cause a crash; none of the looping constructs require altering the stack (although variables associated with the loop may be stored on the stack, but the stack pointer itself won't change)."


Ah. Well, there's just got to be something badly wrong with repeatedly jumping out of the same DO-LOOP with GOTO like that, other than untidiness. It just looks so fundamentally wrong.
PEH
18
Years of Service
User Offline
Joined: 18th Dec 2005
Location: usa
Posted: 2nd Sep 2009 16:14 Edited at: 2nd Sep 2009 16:31
Quote: "I think IanM said that because it's generally terrible programming practice. Not a problem if your coding a quick routine to try something out, but if you start writing programs with 1000's (or much more!) lines of code you will eventually come unstuck. Plus, 6 months later when you come back to that code to re-use a routine in your new project, it will be hard for even you to understand."


Fair enough and I agree on that point. I just wanted to know if it was something that would cause it to crash or otherwise fail that I should be watching for.
Quote: "
Ah. Well, there's just got to be something badly wrong with repeatedly jumping out of the same DO-LOOP with GOTO like that, other than untidiness. It just looks so fundamentally wrong."


In my head it makes perfect sense because setting the resolution erases all the objects and images so I have to send the program back to the start to load everything again. It is the same as if you just changed the resolution in any game from the options menu and the game had to load everything again. I don't see any better way to do that then a goto command.

We are straying from the topic somewhat so if you want to talk more I suggest that we move to my AA thread so we don't take away from this very informative thread.

http://forum.thegamecreators.com/?m=forum_view&t=157177&b=1
Mobiius
Valued Member
21
Years of Service
User Offline
Joined: 27th Feb 2003
Location: The Cold North
Posted: 2nd Sep 2009 17:48 Edited at: 2nd Sep 2009 17:53
Quote: "I don't see any better way to do that then a goto command."


Here's a VERY simple menu and game structure which does everything you want without a GoTo in sight. Quick, clean and most importantly, MODULAR!

I can write another game and slot each function into it quickly and simply, and I know where the code will be, and where it's going at any point during execution.



The use of goto denotes a sloppy and unskilled programmer. Just because you can, doesn't mean you should.

Your signature has been erased by a mod because we're sadistic losers with nothing better to do. (joke)

Login to post a reply

Server time is: 2024-05-23 20:20:15
Your offset time is: 2024-05-23 20:20:15