Coding

 Reply to this postStart new topic

Code Optimisation Methods

Deji
post Mar 8 2010, 02:00 PM
Post #1


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



Code Optimisation



Faster Opcodes


Remember: Just because the "Space Saved" is low for most things, the fastest opcodes aren't measured entirely by how many bytes they used, but mainly by how much work the game is doing behind the scenes to retrieve each parameter and process it.


004F vs. 00D7


SANNY
004F: create_thread @MS_BIKE_MISSIONS


Creates a thread with up to 35 extra parameters. The game would check to see if these parameters exist before moving on to the next opcode.

When extra params are defined, each value specified is carried to the specified thread and stored in it's local variables e.g:

SANNY
0006: 20@ = 13
0376: 18@ = create_random_actor_at -1576.88 55.26 8.57
00D7: create_thread @THREAD 412 20@ 18@
end_thread

:THREAD
// 0@ == 412
// 1@ == 13
// 2@ == an actor


After :THREAD is created, it has the values: 412, 13 and an actors handle stored in 0@, 1@ and 2@. As you may notice, the variables start from 0@ and go up per value.

SANNY
00D7: create_thread @NONAME_1 // without extra params


Exactly the same as 004F except that it does not pass any values to the new thread.


Guess which one is faster? 00D7 should always be used to create a thread unless you actually want to pass parameters to it... Rockstar seemed to use 004F and 00D7 in their first block of create_thread commands. It is unclear why, since none of them require any parameters passed.


03AF vs. 0000


Naming threads is useful for many things. It helps the game differentiate them as well as allowing for certain memory operations that could be used. Say there is a large CLEO Script... You don't own it but you want to perform operations on it (mod it) without editing the file itself... Naming the thread allows for the use of 0AAA to get it's pointer. There's also the use of the end_named_thread commands.

However... 03AF uses 9 more bytes than 0000, which is still allowed.

Overall, I'd recommend the use of 03AF for things that are more than just a simple, small script. If you are using this in a large script (or the main.scm), use it. If you are making a relatively simple mod, 0000 is the best option.


0662 & 0900 vs. HEX...END


0662 is a nopcode that seems to have been picked up by many modders for the purpose of adding messages in their scripts that can be decompiled. This is usually so they can send messages to anyone who decides to decompile their scripts.

0900 is not a nopcode (as far as I know). It is in fact an unknown opcode used to activate an unknown flag on objects, used a couple of times in the main.scm - However, it seems that modders like to use it as such a nopcode.


The hex...end structure is much more sophisticated. It inserts strings straight into the script without opcodes.

It's a much better way of performing these operations, though some still may want to use opcodes.

SANNY
hex "My Text" 00 end


The 00 byte at the end is almost certain to be needed and basically marks the end of the string (a null terminator). This saves about 4-6 bytes. Obviously, if you don't use/need the null terminator (you're reading the exact amount of bytes) then it saves 1 extra byte smile.gif


Coordinate Retrieval


Since they're easy to find, a lot of people use the following to retrieve coords of entities:
SANNY
0400: store_coords_to 1@ 2@ 3@ from_object 0@ with_offset 0.0 0.0 0.0
0407: store_coords_to 1@ 2@ 3@ from_car 0@ with_offset 0.0 0.0 0.0
04C4: store_coords_to 0@ 1@ 2@ from_actor $PLAYER_ACTOR with_offset 0.0 0.0 0.0


However if you're not setting an offset to retrieve, it's a big waste of 12 bytes and extra processing time.

Here are the lighter solutions:
SANNY
00A0: store_actor $PLAYER_ACTOR position_to 0@ 1@ 2@
00AA: store_car 0@ position_to 1@ 2@ 3@
01BB: store_object 0@ position_to 1@ 2@ 3@


Most people don't use them as they don't appear in the common search for "coords".

You'll want to use the other opcodes when you are getting the offset for more than 1 of the dimensions. If it's just the one dimension you want to get the offset off, use a math opcode (less bytes etc).


Bad Habits


  • No offence to any who think this is a good idea, but "declaring" variables is almost absolutely useless. It's fine if you want to give a variable a default value, but writing stuff like:
    SANNY
    0@ = 0.0


    And then later on writing:
    SANNY
    04C4: store_coords_to 0@ 1@ 2@ from_actor $PLAYER_ACTOR with_offset 0.0 0.0 0.0


    Is only good for wasting about 6 bytes. You don't need to "tell the game" what type of value you're assigning to the variable... That is done on a per-line basis. Every time you write a variable, it is compiled with a byte specifying the type of variable it is.

    If you really want a big list of variables that will be used in your script either write a block comment of them OR use Sanny's var...end structure, without assigning values to them (unless you really need to).



Ordering


Stuff to remember:

  • waits are only needed once per loop and only in a loop. Use them elsewhere, but the less, the better.
  • If you do have a loop, remember that you can use a higher value than wait 0 for better performance if the loop doesn't have to run instantly.
  • When performing a function, try not to repeat it.
  • Try not to do the opposite of a function, straight after it... Such as enabling then disabling something.
  • Keep similar opcodes together.
  • Remove all unnecessary jumps and waits.
  • If using high-level constructs, shortcuts or similar compiler features, decompile a script without them to check how Sanny converted things.
  • Remember you can use any one IF for more than one condition (depending on the conditions) - Try not to use extras.
  • Don't do the same things many times, use subs to repeat functions!
  • return_true and return_false are very useful tricks to replace masses of conditions.


--------------------
Go to the top of the page
 
+Quote Post
Deji
post Jul 17 2010, 01:57 PM
Post #2


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



Long strings always save more bytes than short strings.. Plus they can be used in almost every situation. wink.gif


--------------------
Go to the top of the page
 
+Quote Post
Silent
post Jul 23 2010, 12:14 PM
Post #3


The master of cut retort

Group Icon

Posts: 239
From: Warsaw, PL
Joined: 21-July 10



Deleting 'if' opcodes from script (but not if and & if or) saves 4 bytes per one 'if'.
Good way to reduce size of script if you are sick of optimisation biggrin.gif


Can I post here, or it's only Deji's topic?

This post has been edited by Silent: Jul 23 2010, 12:15 PM
Go to the top of the page
 
+Quote Post
Siggi
post Jul 23 2010, 03:01 PM
Post #4


User banned by request due to personal reasons. - SV

Posts: 24
From: 0xFFFFFFFF
Joined: 14-March 10



sure you can, what type of forums was it if you weren't allowed laugh.gif

Well I basicly use only one code optimization method regulary:

- swaping code out from the main code just as loading models an the check

- creating function-like gosubs for preventing code duplication which is a former rule in every programming/scripting language

I'm not sure why, but I realy love gosubs wub.gif they're just useful and if you give them proper names like
SANNY
gosub @ModelLoad
you don't realy have to check this part out (if you publish your sources of course)


btw Deji,

reserving vars by setting them either to 0 or 0.0 (which indeed is useless) ain't such a bad idea

if you want people learning from there code its better to initialise them with zero as reserving them with var - end
and it just makes the code longer, var - end does the same: it sets the vars to zero but they don't show up in the script which is common in most popular programing languages

This post has been edited by Siggi: Jul 23 2010, 03:04 PM


--------------------


User banned by request due to personal reasons. - SV
Go to the top of the page
 
+Quote Post
Deji
post Jul 23 2010, 11:26 PM
Post #5


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



var...end doesn't set them unless you tell it to. They're mainly for telling Sanny how to treat that particular variable. Useful for the for loops as otherwise Sanny can't tell the difference if you want to store the value of another variable.

If you wanted to teach, put the useless code that sets them to something they are in a block comment... That serves the need to visualize the initial state of the variables and also optimizes wink.gif


And yes, you can post here... Be sure to share your optimization knowledge, though smile.gif


--------------------
Go to the top of the page
 
+Quote Post
Siggi
post Jul 24 2010, 01:14 PM
Post #6


User banned by request due to personal reasons. - SV

Posts: 24
From: 0xFFFFFFFF
Joined: 14-March 10



It does, it reservs them for further calcuations, any script language will automaticly set them equal to 0, thats a golden rule in programming wink.gif

var - end tells the script engine to reserve some memory for storing variables,
even in languages which are written very close to the OS set vars to 0 if they weren't defined at the same time as they were initialised:

(C++/C#)
CODE
int  intMyVar;   //    intMyVar is zero now
float  fltMyVar; //    fltMyVar is either zero or 0.0

int intMyVar = 4;  //    intMyVar is now initialised and defined as 4
float fltMyvar = 4.0; //  fltMyVar is now initialised and set to 4.0


Skriptlanguages like LUA don't allow you to initialise a var only, it has to be defined at the same time:

CODE
MyVar = 0
MYVar2 = 0.0

wrong:
CODE
MyVar
MyVar2


both are equal to nothing (= nil in LUA)

var will resever memory so it will initialise this memory by setting it to 0 or 0.0 (not sure how it treats arrays)

cheers,

This post has been edited by Siggi: Jul 24 2010, 01:15 PM


--------------------


User banned by request due to personal reasons. - SV
Go to the top of the page
 
+Quote Post
Deji
post Sep 13 2010, 01:10 AM
Post #7


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



I think there was a theory behind use of 0900 for strings, but I'm not sure myself.

It's that, compiled, 0900 becomes 00 09. This will automatically end the last string (with the null-terminator 00) and define the next using the 09 data type.. Although that data type is for short strings, so I'm still a little baffled. Maybe it just ended up being used even though no one knew what they were using it for.


--------------------
Go to the top of the page
 
+Quote Post
Deji
post Oct 14 2010, 10:56 AM
Post #8


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



SANNY
01B4: set_player 0 can_move 0


Does exactly the same as

SANNY
01B4: set_player $PLAYER_CHAR can_move 0


But of course, the single value saves a byte more smile.gif

You could use 1 for player 2, etc. This is what's stored in the vars anyway (points to one of two memory pools).



I wanted to test these optimizations on the main.scm, so I used Sanny's regex replace (very useful - shame about lack of multiline). Only took a few minutes to finish automatically replacing everything with optimized versions smile.gif

CODE
Original  : 194146 b
Optimized : 182279 b
Reduction :  11867 b


Not that much, really.. but still a noticable amount. Lets see if we can find more optimizations to increase the reduction smile.gif


--------------------
Go to the top of the page
 
+Quote Post
Deji
post Nov 17 2010, 01:45 AM
Post #9


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



This one's for likers of low-construct, high-optimization... Who should really consider ASM when thinking about moving onto programming, lol tongue.gif

SANNY
{$CLEO}

0000: Conditionals Are Permanent!

:0
while true
    wait 0
    if
        0ADC:   test_cheat "COND"
    then
        if and
            0039:   1@ == 1
            0039:   2@ == 1
        
        :1
        0ACA: show_text_box "1"
        wait 1000
        else_jump @2
        jump @0
        
        :2
        0ACA: show_text_box "2"
        wait 1000
    end
end


What I'm trying to point out here is that the SCM is totally different from any other language in many ways.


In most languages, an IF statement is expected to contain a condition which returns true or false and it is expected that there be something for the code to do straight away depending on the result.

In SCM, each thread in the game is structured with data about it. This data includes a byte which is 1 if the last condition returned true and 0 if it returned false. When a condition or IF statement (which groups multiple conditions) in parsed, the game tests whether the conditions are true or false, then sets the thread byte accordingly.


Conditionals and IF statements don't have to be followed by a true/false jump/gosub etc. All a conditional does is perform any action required, check the statement and set the byte.

004D is a seperate opcode. It tests the byte and jumps if the byte is set. If not, it moves on.


So basically, you can freely check a condition in one place and use that anywhere else. This is why 0485 and 059A work. They could also be used before an else_jump to force the jump either way... not that there's much use in that. But it's nice to know.


--------------------
Go to the top of the page
 
+Quote Post
Deji
post Nov 28 2010, 11:27 PM
Post #10


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



There are many opcodes that have no function in SA... What I learned is that they crash the game upon use. It's good that I doubt stuff I learn from others until I can proove it myself, because this is not true...

I'll explain how the opcode handler works and what these opcodes can be used for in terms of optimization by showing a SCM representation:
SANNY
const
    OPCODE  = 0x0001
end

{$CLEO}

:Script
while 0039: 0@ = false
    wait 0
    0AB1: call_scm_func @OpcodeHandler 1 OPCODE 0@
end
0A93: end_custom_thread

:OpcodeHandler
// 0@ contains the opcode
0871: switch 0@ jumps 7 default 0 @default jumps 0 @0000 1 @0001 2 @0002 3 @0003 4 @0004 5 @0005 6 @0006

:0000
0AB2: ret 1 true

:0001
// (0001 code here)
0AB2: ret 1 false

:0002
// (0002 code here)
0AB2: ret 1 false

:0003
// (0003 code here)
0AB2: ret 1 false

:0004
1@ = 2
jump @SetVar

:0005
// (0005 code here)
0AB2: ret 1 false

:0006
1@ = 1

:SetIntegerVar
// some more code which prooves 0004 and 0006 are exactly the same, lol
0AB2: ret 1 false

:default
{
  if an inexistant opcode is passed, the opcode returns -1.
  this is NOT false, so the script stops parsing and continues
  to process the game... just like 0001, except more optimised!
}

0AB2: ret 1 -1


So you can use any inexistant opcode within range of these handlers (from 003D to 09DF) in place of "wait 0", which saves a whole 2 bytes!!! Plus it looks cool and adds that extra bit of edge to your script... noobs will run around panicking when they see a loop without wait biggrin.gif

It does save processing time, but that's microsecond optimisation tongue.gif


Working example:
SANNY
{$CLEO}
0000:

while true
    01CD: render_game_frame
end


--------------------
Go to the top of the page
 
+Quote Post
Deji
post Dec 12 2010, 10:41 PM
Post #11


Coding like a Rockstar!

Group Icon

Posts: 1,468
From: ???
Joined: 28-May 09



I did some speed tests against DMA and regular memory operations (mainly out of boredom). It's not much of a surprise that DMA is faster...

CODE
Direct Memory Access
  5  million times - 0.903
  10 million times - 1.926
  25 million times - 4.491

Write Memory
  5  million times - 1.275
  10 million times - 2.512
  25 million times - 6.191


So.. if you ever need to repeat a memory operation 25 million times, I hope you'll use DMA tongue.gif

Then, I tried testing normal global variable setting 25 million times...
CODE
Global Var Setting
  25 million times - 4.127


Somewhat faster than DMA by the looks of things, since we're not working with arrays, but still.. not much. And just in case you're curious... the result for local variables on 25 million iterations was 4.177. Still, I wouldn't go to extremes to use these for optimisation.


--------------------
Go to the top of the page
 
+Quote Post
Reply to this postStart new topic

1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members: