SA-MP Forums

Go Back   SA-MP Forums > SA-MP Scripting and Plugins > Scripting Help > Tutorials

Reply
 
Thread Tools Display Modes
Old 25/06/2016, 04:16 PM   #1
Yashas
Gangsta
 
Join Date: Jun 2012
Location: India
Posts: 873
Reputation: 263
Default Creative & smart scripting techniques - improve performance & readablity in one go

Scripting Ideas
Improve readablity and performance at the same time.

These are some collection of ideas which improve performance significantly without compromising the readability of the code. Some of these ideas are shocking different and unexpected. This topic does not tell you tell you how to optimize your code by listing do's and dont's (we already have lot of topics for that) rather it gives some clever ideas using which you can improve the performance and readability at the same time. Before we dive into the details, I would like to stress on one golden rule again since this rule governs almost all kinds of optimizations that you can do including the ones mentioned in this thread.

The Golden Rule that every scripter needs to know is PAWN code is insanely slow compared to the natives!

The above fact has far-reaching consequences. If you write your own strcmp, the native strcmp will beat your version by a factor of at least five. Memcpy can set a complete array 50-100 times faster than using a plain PAWN loop. In other words, the rule just asks you to make use of native functions (could be a part of the standard library or could be a function provided by a plugin) whenever possible.

Every script that you create basically runs on a virtual computer called AMX Machine and this machine, in turn, runs on your real hardware. This poor little machine needs to first decode your PAWN code (it is actually P-Code/object code which the compiler generates from your PAWN code) then do a series of checks on every PAWN instruction after which it is executed on the real hardware. Now it's pretty much obvious that PAWN code is very slow.

The natives, on the other hand, are provided by the machine and hence are directly executed on your real computer which is why natives are faster.

Dummy Array & Dummy Array Element
The ideas mentioned here makes use of dummy arrays whose sole purpose is to store the default data so that we can use the memcpy native to copy the default data to an actual array instead of using a loop to initialize the array. This takes advantage of fastness of the memcpy function. In fact, all natives are tens of times faster in most cases when it comes to handling big chunks of data.

Initilizing large arrays (enum based)
This idea is best suitable for very big gamemodes where you have a lot of variables to be set in an array (enum based array). The idea is to have an extra element in your array which is initialized with the default data for the given array so that when you need the default data, one memcpy (which is amazingly fast compared to manual assignments) call would do.

Let us take an example. Assume that the following is enum array that you use.
Code:
enum PlayerInfo_t
{
      Name[MAX_PLAYER_NAME],
      MoneyInHand,
      BankMoney,
      Team,
      Skin,
      Kills,
      Deaths,
      WantedLevel,
      AdminLevel,
      Muted,
      Jailed,
      Frozen      
}
new PlayerInfo[MAX_PLAYERS + 1][PlayerInfo_t];
This is how you'd probably do which is really annoying since its takes lot of space in the code in such an important callback.

Code:
public OnPlayerConnect()
{
       PlayerInfo[playerid][Team] = -1;
       PlayerInfo[playerid][Skin] = 1;
       PlayerInfo[playerid][MoneyInHand] = 
       PlayerInfo[playerid][BankMoney] = 
       PlayerInfo[playerid][Kills] = 
       PlayerInfo[playerid][Deaths] =
       PlayerInfo[playerid][WantedLevel] = 
       PlayerInfo[playerid][AdminLevel] =  0;
       PlayerInfo[playerid][Muted] = true;
       PlayerInfo[playerid][Jailed] =
       PlayerInfo[playerid][Frozen] =  false;
}
If you were to use the idea mentioned in this section, you will have to make room for the dummy element in the array and initialize the dummy element in OnGameModeInit/OnFilterScriptInit.

Code:
public OnGameModeInit()
{
      PlayerInfo[MAX_PLAYERS][Team] = -1;
      PlayerInfo[MAX_PLAYERS][Skin] = 1;
      PlayerInfo[MAX_PLAYERS][MoneyInHand] = 
      PlayerInfo[MAX_PLAYERS][BankMoney] = 
      PlayerInfo[MAX_PLAYERS][Kills] = 
      PlayerInfo[MAX_PLAYERS][Deaths] =
      PlayerInfo[MAX_PLAYERS][WantedLevel] = 
      PlayerInfo[MAX_PLAYERS][AdminLevel] =  0;
      PlayerInfo[MAX_PLAYERS][Muted] = true;
      PlayerInfo[MAX_PLAYERS][Jailed] =
      PlayerInfo[MAX_PLAYERS][Frozen] =  false;
}
Now you have just one line in OnPlayerConnect and its super-efficient.
Code:
public OnPlayerConnect(playerid)
{
      memcpy(PlayerInfo[playerid], PlayerInfo[MAX_PLAYERS], 0, sizeof(PlayerInfo[])*4, sizeof(PlayerInfo[]));
}
Filling arrays
Suppose you have an array EventPoints[MAX_PLAYERS] and you wish to fill it with zeros before the start of an event. You might do the following,

Code:
for(new i = 0; i < MAX_PLAYERS; i++)
     EventPoints[i] = 0;
To be honest, this is an awful way to do it since it is pure PAWN code (PAWN code is insanely slow).

There are two efficient approaches (which are tens of times faster) to deal with cases where you want to fill a whole array with the same value.
  1. Using memset
  2. Dummy array

Using memset
Memset basically, as the name says, sets a region of memory with the same value. This is the most efficient way to fill an array with zeros if you don't want to make a dummy array. The C/C++ Standard memset sets a fixed number of bytes to a specific value but this is not the case with the PAWN version of memset (Slice's memset; PAWN library does not provide any memset). Slice's memset sets all the cells in the region of the memory to the fixed value. The PAWN Implementer Guide says it wrong about the FILL assembly instruction that it fills the bytes with a fixed value. It actually fills cells with a particular value.

You can obtain a copy of memset by Slice here.

That code itself is a source of inspiration since the code modifies itself on the fly. It is done so due to a limitation of the AMX assembly. The FILL instruction accepts a constant number as the operand (fixed during compile-time) and hence, a variable number of cells/bytes cannot be used. Slice smartly overcomes the problem by writing code that edits itself during run-time (Refer to AMX Assembly tutorial to learn how to write code that edits itself). The only other way left if not to use the self-editing code would be to have loops to separate the data into pieces of powers of 2 (why powers of 2? its a mathematical problem - you can express any number with sums of powers of two - binary number basically for those who understood) and fill piece by piece which would have affected the performance of memset. However, even such a memset would be faster than manually filling the array using a loop since it does not make use of arrays.

Code:
memset(EventPoints, sizeof(EventPoints), 0);
Using dummy array
This is even faster than memset but in my opinion, it doesn't really matter which method you use as long as you don't write plain PAWN code to fill the array (which is nowhere close to memset's speed).

Code:
new bigdummy_zero[1000];

memcpy(EventPoints, bigdummy_zero, 0, sizeof(EventPoints)*4, sizeof(EventPoints));
You can use the same dummy array to fill other arrays too.

Efficient handling of objects/areas/labels/
Most of you make use of loops to work with objects. These loops are relatively expensive since the checks you add inside the loop to find the some id from the objectid which contains arrays like in the example given below.

Code:
public OnObjectMoved(objectid)
{
      for(new i = 0; i < sizeof(Bombs); i++)
      {
           if(Bombs[i][bomb_objid] == objectid) 
           {
                   Bombs[i][Moved] = true;
                   break;
           }
      }
}
Here is a more efficient way to do the same but it takes some extra memory. I don't think few KBs of extra memory is of any concern even if you have limited amount of RAM. Moreover, the performance improvement will easily outweigh the cost of using extra memory especially when you have hundreds of those entities (areas, objects, etc).

The idea is to have arrays whose index is used as the object id. The array stores what type of object the objectid corresponds to. We first make an enum where the first entry is OBJECT_TYPE_NONE (this should be zero) and the every following entry in the enum takes up non-zero value.
Code:
enum
{
      OBJECT_TYPE_NONE = 0, //Keeping it zero is an advantage since the PAWN compiler initializes the data to zero by default
      OBJECT_TYPE_BOMBS = 1,
      OBJECT_TYPE_WEAPON,
      .
      .
      .
}
new ObjectsType[MAX_OBJECTS];
new ObjectsSpecialID[MAX_OBJECTS];
The first array gives you what type of object the objectid corresponds to and the second array gives you some additional information about the object. What you store in the ObjectsSpecialID array is up to you. Remember that you have the freedom to store different type of data for different type of objects, for example, you can use ObjectsSpecialID to store the bomb id (related to your script, say, Bombs[MAX_BOMBS][bomb_info]) and store playerid for cloths. They won't conflict because the ObjectsType array is first checked which distinguishes between a cloth and a bomb. The following example should clear all your doubts.

Code:
public OnObjectMoved(objectid)
{
      switch(ObjectsType[objectid])
      {
            case OBJECT_TYPE_BOMBS:
            {
                   new i = ObjectsSpecialID[objectid];
                   Bombs[i][Moved] = true;
            }
      }
}
This is extremely efficient when you have many objects since it avoids the loop and the dirty arrays which are involved in every iteration.

If you are using the streamer plugin, you will have to use two sets of arrays since streamer ids can sometimes conflict with a SAMP assigned id (you can have an id whose streamer id is 1 and you can have an object created using CreateObject to have an id as one too).

Code:
new DynamicObjectsType[MAX_OBJECTS];
new DynamicObjectsSpecialID[MAX_OBJECTS];

new NormalObjectsType[MAX_OBJECTS];
new NormalObjectsSpecialID[MAX_OBJECTS];
Of course, these are memory hungry but are definitely worth sacrificing some space.

String natives work on arrays too!
The idea is to store data in the form of strings so that string related functions can be used on the data. For example, if you would want to store if a player X shot another player Y before player Y dies, you would probably do something similar to the following,

Code:
enum pinfo
{
     bool:PlayersShot[MAX_PLAYERS]
}
new PlayerInfo[MAX_PLAYERS][pinfo];

public OnPlayerWeaponShot(...)
{
     PlayerInfo[hitid][PlayersShot][playerid] = true;
}
If you do so, you are going to have 'dirty expensive costly' loops when you'd want to search for players who had shot him. Let's say you wanted to reward the players who shot the player X after his death. You'd use something similar to the code given below

Code:
for(new i = 0, j = GetPlayerPoolSize(); i <= j; i++)
{
      if(PlayerInfo[playerid][PlayersShot][i]) { }
}
You are trying to access a 3D array which is very slow and that too inside a loop. You can do the same much faster (tens of times). But there could be a bit of compromise on the readability here for some of you. I make use of defines and give better names for the string natives and hence I find them more readable.

The idea is to convert the boolean array into a string (need to keep a null character at the end of the array) and store a character, say 'Y', if the player was shot by a player whose index is his shooter's playerid and 'N' if the player wasn't shot by that player.

Code:
enum pInfo
{
      PlayerShot[MAX_PLAYERS + 1], //+1 to make room for the null character
}
To obtain the players who shot, we use the strfind function.
Code:
new pos = 0;
while((pos = strfind(PlayerInfo[playerid][PlayerShot], "Y", false, pos)) != -1)
{
     //pos has the id of a player who shot
     pos++;
}
You can either use memset or a dummy to fill the array with 'N' (except the last character) when the player dies.

Code:
memset(PlayerInfo[playerid][PlayerShot], MAX_PLAYERS, 'N');
It is important that you use MAX_PLAYERS while filling the array instead of MAX_PLAYERS + 1. You should leave the last character a zero (its already zero so don't go to set it to 'N', just leave it as it is) so that strfind knows when it reaches the end or else strfind will start reading blocks of memory well past the array end which it shouldn't and you will see SAMP behaving weirdly which might ultimately end up in a crash.

You can similarly use other string natives while working with arrays provided that you have zero at the end of the array.

Notes
I had promised to make such a topic but I apologize that I couldn't fulfill it. At least here is what I had done till now. I will surely update this topic in the future. I have lot more ideas to share!

Quote:
Originally Posted by Yashas View Post
Coincidentally, all the optimizations mentioned in this topic atm improve readability.
Maybe someday in future, I will make a neat topic which will list optimizations that really make a "big" improvement which are actually based on "smart ideas" rather than coding techniques.
I started writing this tutorial two months ago but lost motivation in between due to misc reasons. I will be keeping myself away from computers for few months because health concerns. Here is what I have got for you for now... will add more some other day.

Last edited by Yashas; 25/06/2016 at 04:48 PM.
Yashas is offline   Reply With Quote
Old 25/06/2016, 05:20 PM   #2
iKarim
Huge Clucker
 
Join Date: Oct 2015
Posts: 484
Reputation: 82
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

Wow nice.

Ok so I want to ask a question, is the memcpy method to reset an array faster than this?
PHP Code:
enum P_DATA {
    
p_money,
    
p_stuff,
    
p_whatever
}
new 
    
Player[MAX_PLAYERS][P_DATA], 
    
resetPlayer[P_DATA]
    ;

    
// anywhere in the code:
    
Player[playerid] = resetPlayer
iKarim is offline   Reply With Quote
Old 25/06/2016, 05:56 PM   #3
Crayder
High-roller
 
Crayder's Avatar
 
Join Date: Sep 2013
Location: Flames of Hell
Posts: 3,836
Reputation: 565
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

Quote:
Originally Posted by PawnHunter View Post
Wow nice.

Ok so I want to ask a question, is the memcpy method to reset an array faster than this?
PHP Code:
enum P_DATA {
    
p_money,
    
p_stuff,
    
p_whatever
}
new 
    
Player[MAX_PLAYERS][P_DATA], 
    
resetPlayer[P_DATA]
    ;

    
// anywhere in the code:
    
Player[playerid] = resetPlayer
You just described the dummy array.
__________________
Those who deserve reputation, do not need to beg for it.
Also, don't expect the help you need when offering reputation, you'll just be attracting Rep Hunters.
Join SA-MP Discord!
Crayder is offline   Reply With Quote
Old 25/06/2016, 06:05 PM   #4
Konstantinos
Spam Machine
 
Konstantinos's Avatar
 
Join Date: Dec 2011
Posts: 11,938
Reputation: 1358
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

There is a difference. The method shown by PawnHunter will reset everything to 0 while Yashas' method can set any value. It is a very interesting way to reset an enum-array indeed!

As for smart techniques using streamer plugin is E_STREAMER_EXTRA_ID. When it loads all the houses for example, set that extra ID for the pickup/checkpoint to a value such as "MAX_HOUSES + index" and you can retrieve it back on pickup/entering cp and get directly the house the player is near, no unnecessary loops.
__________________
Life is like riding a bicycle. To keep your balance, you must keep moving.

[Tutorial] How to use SQLite
[FilterScript] Tune System



www.SF-SE.net
play.sf-se.net:7777
Play now!
Konstantinos is offline   Reply With Quote
Old 27/06/2016, 08:54 PM   #5
Battlezone
Gangsta
 
Battlezone's Avatar
 
Join Date: Aug 2013
Location: Hello
Posts: 794
Reputation: 124
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

I'm actually in love with the memcpy idea, thanks for this precious tutorial!
__________________
Battlezone is online now   Reply With Quote
Old 28/06/2016, 02:40 PM   #6
PrO.GameR
Gangsta
 
PrO.GameR's Avatar
 
Join Date: Oct 2012
Posts: 735
Reputation: 121
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

Quote:
Originally Posted by Konstantinos View Post
There is a difference. The method shown by PawnHunter will reset everything to 0 while Yashas' method can set any value. It is a very interesting way to reset an enum-array indeed!

As for smart techniques using streamer plugin is E_STREAMER_EXTRA_ID. When it loads all the houses for example, set that extra ID for the pickup/checkpoint to a value such as "MAX_HOUSES + index" and you can retrieve it back on pickup/entering cp and get directly the house the player is near, no unnecessary loops.
Well technically PawnHunter's way could have values,
resetPlayer[P_DATA]={0,-1,1 and so on}; while initializing, then Player[playerid] = resetPlayer;
Although I'm not sure which one is faster and if I remember correctly Y_Less did a little searching and PawnHunter's was faster in huge enum-arrays.

Rest points are quite interesting, I remember having argument about speed or memory, and with today's memory standard and sa-mp being single-threaded, speed wins everytime, having a couple megabytes of memory is no big deal with 16-32GB rams these days.
__________________
Blueberry Prison Roleplay will be back soon!
Follow the forums for more information about opening day.

Forums
PrO.GameR is offline   Reply With Quote
Old 28/06/2016, 03:18 PM   #7
Vince
Spam Machine
 
Vince's Avatar
 
Join Date: Sep 2007
Location: Belgium
Posts: 11,094
Reputation: 2647
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

Except not everyone runs their servers on super expensive dedis. You can run a very simple plain SA-MP server under Linux with just 128 MB of RAM. And while that should be plenty, it is wrong to assume that multiple gigabytes of RAM will be available.
__________________
Vince is offline   Reply With Quote
Old 28/06/2016, 04:28 PM   #8
CodeStyle175
Big Clucker
 
CodeStyle175's Avatar
 
Join Date: Apr 2014
Posts: 186
Reputation: 9
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

Well today we have all that memory, because its not 2004, where memory was limited
CodeStyle175 is offline   Reply With Quote
Old 28/06/2016, 05:01 PM   #9
PrO.GameR
Gangsta
 
PrO.GameR's Avatar
 
Join Date: Oct 2012
Posts: 735
Reputation: 121
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

Quote:
Originally Posted by Vince View Post
Except not everyone runs their servers on super expensive dedis. You can run a very simple plain SA-MP server under Linux with just 128 MB of RAM. And while that should be plenty, it is wrong to assume that multiple gigabytes of RAM will be available.
Cheapest VPSes around have 0.5/1 GB of ram at least, and as you are well aware 4mB of data means 1,000,000 (might be of by a factor, I'm kinda too lazy to do the exact math ) data cells, so even if you try and sacrifice memory for a lot of things, you won't end up using more than 40 MB of ram anyhow, thats perfectly acceptable at least for me.
__________________
Blueberry Prison Roleplay will be back soon!
Follow the forums for more information about opening day.

Forums
PrO.GameR is offline   Reply With Quote
Old 28/06/2016, 07:24 PM   #10
iKarim
Huge Clucker
 
Join Date: Oct 2015
Posts: 484
Reputation: 82
Default Re: Creative & smart scripting techniques - improve performance & readablity in one go

PHP Code:
new pName[MAX_PLAYERS][MAX_PLAYER_NAME]; // global array

// OnPlayerConnect

GetPlayerName(playeridpName[playerid], MAX_PLAYER_NAME);


// Anywhere in the script

... pName[playerid
PHP Code:
// anywhere in the script

new pName[MAX_PLAYER_NAME];
GetPlayerName(playeridpNameMAX_PLAYER_NAME);
... 
pName 
which is more preferred first method or second? And why?

Last edited by iKarim; 29/06/2016 at 06:14 AM.
iKarim is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
Looking for someone creative jameskmonger Everything and Nothing 15 16/08/2012 09:55 AM
How to improve performance[Windows XP/Vista/7] Kalroz Everything and Nothing 10 29/07/2012 06:28 AM
Do you guys know how you can improve lag - shooting..I want to improve. SpezzyBreeze General 9 26/12/2011 04:23 PM


All times are GMT. The time now is 01:41 PM.


Powered by vBulletin® Version 3.8.6
Copyright ©2000 - 2017, Jelsoft Enterprises Ltd.