[Tutorial] New Code Optimizations
#21

A method for optimizing loops, if you are doing a loop that requires distance checks , or something that is going to work around for a player / the player's vicinity. You can use IsPlayerStreamedIn

You can also use this for vehicle loops for example if you want to respawn the vehicles around the player you would use IsVehicleStreamedIn.

It comes pretty handy.
Reply
#22

Well done!, I really like your work and you impress me, keep up the good work! And do not forget to update this if you came up with new optimizations!. Thanks..
Reply
#23

Quote:
Originally Posted by Gammix
Посмотреть сообщение
Yes in most of the case, why care about negligible speed!


No, for all Pool sizes. Because the pool size have all the valid cells and if you don't use =, you'll miss the last cell.


Doesn't make sense if you use i++. It will turn out to be an infinite loop which will always exceed a value greater than Pool size every time (+1).
The loop you're using ( for example if MAX_PLAYERS is set to 40), it will start from 40 and every time do -1 which means that it's going the opposite direction. That doesn't mean that you optimized it in a better way, like you stated in your post.


Anyway I might be wrong, misread

Код:
Use the above if the order in which the code is executed does not matter. You can optimize that too further but I feel that it will reduce the readability of the code.
But still I was wondering since you say (+1), I wondered about the loop that uses i-- which in this case is
Код:
for(new i = GetPlayerPoolSize();--i != -1;) {}
Reply
#24

Quote:
Originally Posted by Cypress
Посмотреть сообщение
The loop you're using ( for example if MAX_PLAYERS is set to 40), it will start from 40 and every time do -1 which means that it's going the opposite direction. That doesn't mean that you optimized it in a better way, like you stated in your post.


Anyway I might be wrong, misread

Код:
Use the above if the order in which the code is executed does not matter. You can optimize that too further but I feel that it will reduce the readability of the code.
But still I was wondering since you say (+1), I wondered about the loop that uses i-- which in this case is
Код:
for(new i = GetPlayerPoolSize();--i != -1;) {}
A close inspection on the assembly code will tell you why backward loop is faster than forward loop and why. In a forward loop, you are using an additional variable 'j' which needs to be fetched from memory for every iteration whereas in the backward loop you needn't. For the same reason , '--i != -1' is faster than 'i != -1;i--' (you are decrementing & checking the falsity of the condition at one variable fetch).
Reply
#25

Very nice work. I read all of it and found it very interesting.
But there is one minor problem at "my house" and if you could answer me it would be great!

Recently I started updating my script and I had a problem. The problem was in loops(You can check my issue here https://sampforum.blast.hk/showthread.php?tid=580573 ).

So basically, I am having this loop for MySQL which I am using to load biz.

for(new i, j = cache_num_rows(); i <= j ; i++)

But, that loop loads non-existing row 1 and I get erros like this
Code:
[ERROR] CMySQLResult::GetRowDataByName() - invalid row index ('1')
[ERROR] cache_get_field_content_int - invalid datatype
If I put my old loop, there are no errors.

for(new i = 0; i < cache_num_rows(); i++)

Point is, everything works great but in mysql log I get these errors because it's trying to load rows that are not there.

What loop should I use then?
Reply
#26

pawn Code:
for(new i, j = cache_num_rows(); i < j ; i++)
When you're using your new loop (i <= j) you're looping until i is equal to number of rows, but row indexes start from 0, not 1. You have to use i < j, so it stops just before it.
Reply
#27

Quote:
Originally Posted by kvann
View Post
pawn Code:
for(new i, j = cache_num_rows(); i < j ; i++)
When you're using your new loop (i <= j) you're looping until i is equal to number of rows, but row indexes start from 0, not 1. You have to use i < j, so it stops just before it.
Okay. What about other loops then? Should I remove "=" from every loop like that or is this only for things like MySQL?
Reply
#28

Quote:
Originally Posted by Cypress
View Post
Code:
for(new i = GetPlayerPoolSize();--i != -1;) {}
Does this loop is better than foreach?
Reply
#29

Nope.
Reply
#30

What is faster?

Code:
new x;
new y;
new z;
new Float:h;

new x, y, z, Float:h;
?
Reply
#31

Quote:
Originally Posted by vannesenn
View Post
What is faster?

Code:
new x;
new y;
new z;
new Float:h;

new x, y, z, Float:h;
?
Second.
Reply
#32

Quote:
Originally Posted by vannesenn
View Post
What is faster?

Code:
new x;
new y;
new z;
new Float:h;

new x, y, z, Float:h;
?
Both are the same, its for compile time.
Reply
#33

One thing I see constantly, and which is not mentioned in the first post, is the excessive usage of GetPlayerName. A player can't change his name while connected (the exception of course being SetPlayerName) so it seems redundant to call the function over and over again. I would say using a wrapper is even worse. Simply store it in a variable when the player connects and then use that variable everywhere. The same goes for GetPlayerIP.
Reply
#34

Quote:
Originally Posted by Vince
View Post
One thing I see constantly, and which is not mentioned in the first post, is the excessive usage of GetPlayerName. A player can't change his name while connected (the exception of course being SetPlayerName) so it seems redundant to call the function over and over again. I would say using a wrapper is even worse. Simply store it in a variable when the player connects and then use that variable everywhere. The same goes for GetPlayerIP.
One thing i see a lot is masking natives. It takes really less time to retrieve player name/ip, yet slower than a variable but variable would only be useful when you are looping alot of players at a high rate, unless it's pretty fine i guess.
Reply
#35

One more thing.
, what is faster

Code:
foreach(Player, i)
{
format(..., "SELECT ID, Name etc.. FROM TABLE WHERE X = %d", i);
mysql_tquery(...); // CALLING PUBLIC(callback[] parameter) FOR EACH QUERY
}

// OR

format(..., "SELECT * FROM TABLE", ...);
mysql_tquery(...); // IN PUBLIC(callback[] parameter) WILL BE LOOP THROUGH RESULT
Reply
#36

Table will contain online users. In table is few rows(less than ten). 2nd query I can write also like this:

SELECT ID, Name etc.. FROM TABLE(direct in tquery. I see that I can do query without format function).

@ Also

Code:
if(var == x)
{
if(y == z)
{
}
}

// OR

if(var == x && y == Z)
Reply
#37

Quote:
Originally Posted by Vince
View Post
One thing I see constantly, and which is not mentioned in the first post, is the excessive usage of GetPlayerName. A player can't change his name while connected (the exception of course being SetPlayerName) so it seems redundant to call the function over and over again. I would say using a wrapper is even worse. Simply store it in a variable when the player connects and then use that variable everywhere. The same goes for GetPlayerIP.
What makes it the worst is that there is an internal memcpy or something similar which is slow. However, raw function calls are many faster than dereferencing a 2D array. I have a foreach which is faster than y_iterate because it makes use of native function calls (to a plugin; plugin? that means insertion/deletion should be amazingly fast compared to its PAWN implementation).


Quote:
Originally Posted by Gammix
View Post
One thing i see a lot is masking natives. It takes really less time to retrieve player name/ip, yet slower than a variable but variable would only be useful when you are looping alot of players at a high rate, unless it's pretty fine i guess.
The speed loss is however, negligible when you create wrappers. In fact, sometimes wise use of wrappers can speed up the execution. Before you even ask me why I keep complaining about your code when you make such wrappers, I say it beacause your wrappers serve no purpose most of the times. You should make functions when you have large chunks of code repeating many times in your script. You may also do so when you have big chunk of code which does a specific job where moving that chunk into a function improves readability.

OT: If you have a function which modifies an array such as insertion, deletion, etc then

Code:
for(new y...)
{
     Array[playerid][y] = something;
}
IS SLOWER THAN

Code:
stock DoSomething(arr[])
{
     for(new y...)
     {
         arr[y] = something;
     }
}
Why is it so? A quick look at how arrays are dereferenced in PAWN explains it.

AMX Assembly - Arrays & Strings Section

2D Array
Code:
#emit CONST.alt arr //Load the address of the array
#emit CONST.pri 2 //We want to access the 2nd sub-array
#emit IDXADDR //Address of the 2nd element of the major array

#emit MOVE.alt //Keep a copy of that address since we need to add it to the offset to get the address of the sub array

//ALT = PRI = Address of the 2nd element of the major array
#emit LOAD.I

//ALT = Address of the 2nd element of the major array
//PRI = offset relative to the address stored in the ALT to the 2nd sub-array
#emit ADD

//PRI now has the address of the sub-array
#emit MOVE.alt //Move the address of the first element of the sub-array from PRI to ALT

#emit CONST.pri 4 //We want the 4th element of the sub-array
#emit LIDX//Load the value stored at arr[2][4]
1D Array
Code:
#emit CONST.alt array_address
#emit CONST.pri n
#emit IDXADDR //PRI now has the address of the (n + 1)th element

#emit CONST.alt array_address
#emit CONST.pri n
#emit LIDX //PRI now has the value stored in the (n + 1)th element
This is how arrays are passed
Code:
 //Pushing the address of the global string
    #emit PUSH.C global_str 

    //Pushing a local string
    #emit PUSH.S cmdtext
Which clearly explains why that works. When you push arrays, you push the address of the array so in your function call you recieve a 1D array. The first part of the 2D array dereference code is essentially skipped in every iteration in this case which makes it a whole lot faster.

Its sad that PAWN doesn't provide pointers.
Reply
#38

Quote:
Originally Posted by Yashas
View Post
Today I learned that the fact that PAWN arranges static/global data linearly can be 'abused'.

Code:
static hello[] = {''H','e','l','l','o'}, world[]={' ', 'W', 'o', 'r', 'l', 'd', '!', 0};
print(hello);
That will result in "Hello World!".

The main potential I see for this idea is you can avoid repeated addtiton of a constant to an array.

Code:
static str[144] = "123_";

while(pos < 100)
     str[pos + 3] = some_other_String[pos++];

print(str);
can be written as

Code:
static prefix[4] = {'1', '2', '3', '_'}, str[144];

while(pos < 100)
     str[pos] = some_other_String[pos++];

print(prefix); //will print the contents of str along with the contents of prefix since prefix is not null terminated.
Quote:
Originally Posted by Slice
View Post
Mind clarifying the last two examples? I don't get what you mean.
Quote:
Originally Posted by Yashas
View Post
Fixed a typo in the example. You can avoid the +3 and thats it!

Of course, its stupid to do such useless optimizations because it murders the readablity.

In the example, I try to copy a string to another string such that the destination string has a prefix "123_". In the first code, I had to add +3 while assigning the array element in every iteration so that the first 4 characters are skipped whereas in the second code doesn't need it at all. The example is just bad... strins would do it faster. I couldn't find a better example.

I now feel guilty for calling it "main potential".
This example given by Slice is much better.

Quote:
Originally Posted by Slice
View Post
Ah ok I see what you mean. This would be better then:
pawn Code:
static prefix[4] = {'1', '2', '3', '_'}, str[144];

str = some_other_String;

print(prefix);
Reply
#39

Quote:
Originally Posted by Yashas
View Post
There is another myth which says "making functions is always slower" which isn't true at all when it comes to dealing with multi-dimensional arrays. If done properly, making functions can actually improve performance significantly.

Code:
for(new y = 0; y < 100; y++)
{
  Array[playerid][y] = y;
}
is considerably slower than

Code:
stock DoSomething(arr[])
{
  for(new y = 0; y < 100; y++)
  {
  arr[y] = y;
  }
}
The reason lies within the roots of the assembly code. A quick look at how arrays are dereferenced in PAWN explains it.
Ain't it the same as doing
pawn Code:
for(new y = 0,arr=Array[playerid]; y < 100; y++)
{
  arr[y] = y;
}
And actually faster than calling a function for this?
Reply
#40

Quote:
Originally Posted by Kaperstone
View Post
Ain't it the same as doing
pawn Code:
for(new y = 0,arr=Array[playerid]; y < 100; y++)
{
  arr[y] = y;
}
And actually faster than calling a function for this?
That causes the contents of 'Array' to be copied to 'arr' which is unnecessary. It might be faster for quite small array dimensions though.

A overhead of a function call is not significant when the body of the function is computation heavy.
Reply


Forum Jump:


Users browsing this thread: 2 Guest(s)