|
![]() |
|
Thread Tools | Display Modes |
|
![]() |
#1 |
Gangsta
![]() ![]() ![]() ![]() Join Date: Jun 2012
Location: India
Posts: 882
Reputation: 317
|
![]()
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]; Code:
public OnPlayerConnect(playerid) { 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; } 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; } Code:
public OnPlayerConnect(playerid) { memcpy(PlayerInfo[playerid], PlayerInfo[MAX_PLAYERS], 0, sizeof(PlayerInfo[])*4, sizeof(PlayerInfo[])); //or PlayerInfo[playerid] = PlayerInfo[MAX_PLAYERS]; } 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; 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.
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); 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)); 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; } } } 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]; Code:
public OnObjectMoved(objectid) { switch(ObjectsType[objectid]) { case OBJECT_TYPE_BOMBS: { new i = ObjectsSpecialID[objectid]; Bombs[i][Moved] = true; } } } 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]; 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; } Code:
for(new i = 0, j = GetPlayerPoolSize(); i <= j; i++) { if(PlayerInfo[playerid][PlayersShot][i]) { } } 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 } Code:
new pos = 0; while((pos = strfind(PlayerInfo[playerid][PlayerShot], "Y", false, pos)) != -1) { //pos has the id of a player who shot pos++; } Code:
memset(PlayerInfo[playerid][PlayerShot], MAX_PLAYERS, 'N'); You can similarly use other string natives while working with arrays provided that you have zero at the end of the array. Last edited by Yashas; 31/01/2018 at 07:00 PM. |
![]() |
![]() |
![]() |
#2 |
Banned
![]() Join Date: Oct 2015
Posts: 580
Reputation: 142
|
![]()
Wow nice.
Ok so I want to ask a question, is the memcpy method to reset an array faster than this? PHP Code:
|
![]() |
![]() |
![]() |
#3 | |
Banned
![]() Join Date: Sep 2013
Location: Flames of Hell
Posts: 3,331
Reputation: 652
|
![]() Quote:
|
|
![]() |
![]() |
![]() |
#4 | |
Gangsta
![]() ![]() ![]() ![]() Join Date: Jun 2012
Location: India
Posts: 882
Reputation: 317
|
![]() Quote:
^ and the array size where they meet in performance depends on the CPU! I did not mention anything about that in the tutorial is because the speed gain is insignificant. If you do it with a normal loop, say it takes around 1000ms. If you do it with memcpy, you can do it in less than 20ms. If you do the direct array indexing, you may do it in 15ms. Ask yourself, is the 5ms gain significant compared to the 980ms gain? Deciding when to use memcpy and when to assign directly would be a unnecessary headache for such an insignificant improvement. Even if assigning directly was 2x faster than memcpy, it would be insignificant. Why? Check the numbers again! The numbers are guesses but the actual measurements will yield the same results. |
|
![]() |
![]() |
![]() |
#5 | |
High-roller
Join Date: Dec 2013
Posts: 4,772
Reputation: 754
|
![]() Quote:
50,000 iterations: ![]() Part 1 => http://pastebin.com/zULdXUm9 Part 2 => http://pastebin.com/A8WaE6fv Part 3 => http://pastebin.com/gX0Yv2Fw Part 4 => http://pastebin.com/95kJi2a6 |
|
![]() |
![]() |
![]() |
#6 |
Spam Machine
![]() ![]() ![]() ![]() ![]() ![]() Join Date: Dec 2011
Posts: 11,846
Reputation: 1399
|
![]()
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. |
![]() |
![]() |
![]() |
#7 | |
Gangsta
![]() ![]() ![]() ![]() Join Date: Oct 2012
Posts: 694
Reputation: 121
|
![]() Quote:
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 ****** 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. |
|
![]() |
![]() |
![]() |
#8 |
Gangsta
![]() ![]() ![]() ![]() Join Date: Aug 2013
Location: Berlin
Posts: 803
Reputation: 155
|
![]()
I'm actually in love with the memcpy idea, thanks for this precious tutorial!
|
![]() |
![]() |
![]() |
#9 |
Spam Machine
![]() ![]() ![]() ![]() ![]() ![]() Join Date: Sep 2007
Location: Belgium
Posts: 10,089
Reputation: 2655
|
![]()
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.
|
![]() |
![]() |
![]() |
#10 | |
Gangsta
![]() ![]() ![]() ![]() Join Date: Oct 2012
Posts: 694
Reputation: 121
|
![]() Quote:
![]() |
|
![]() |
![]() |
![]() |
Thread Tools | |
Display Modes | |
|
|
![]() |
||||
Thread | Thread Starter | Forum | Replies | Last Post |
Looking for someone creative | jameskmonger | Everything and Nothing | 15 | 16/08/2012 10:55 AM |
How to improve performance[Windows XP/Vista/7] | Kalroz | Everything and Nothing | 10 | 29/07/2012 07:28 AM |
Do you guys know how you can improve lag - shooting..I want to improve. | SpezzyBreeze | General | 9 | 26/12/2011 05:23 PM |