19.04.2015, 16:43
Code optimisation
Contents
- Contents
- Introduction
- String optimisation
- State machines (automata)
- Callback hooks
- Interesting Macros
- Testing
- foreach
- Minor optimisations
- IEEE numbers
- Rearranging
- Data rearrangements
- Know the language
- Distance checks
- Speed order
- Know your values
- Return values
- Small snippets
- Equivalence
- Empty strings
- Copying strings
- Assumptions
- Introduction
- Solutions
- Ignore it
- Make modifications easy
- Code defensively
- Important
- Memory reduction
- All vehicles
- All attributes
- More that 32 values
- Excess dimensions
- CPU vs Memory
- Lists
- Types
- Mixed lists
- Code
- Binary trees
- Example
- Balanced and unbalanced
- Modification
- Addition
- Deletion
These are just some techniques for making your code faster that I have picked up up on the way. Please note that I in no way pretend to be an authority on this subject, these are just what I know, others may know other methods, in which case please do share them as even if no-one else cares I would be interested in knowing them. Note also that many of these techniques apply to languages beside PAWN (the last one I wrote was accused of being stupid because PAWN does not have dynamic memory allocation (personally I think that's a good thing but there we go)), however a lot of other languages may incorporate bits of these into the compiler to do in-line optimisation.
This does not promise to make your code good, just hopefully better. Also note that some bits are fairly complex, it's aimed as a more advanced tutorial, so it does make some assumptions about knowledge in some areas.
String optimisation
For those of you who haven't read my thread on better string usage, it's here:
Why you shouldn't make your strings 256 cells big
State machines (automata)
For those of you who haven't read my thread on state machines (automata), it's here:
State machines (automata)
Callback hooks
For those of you who haven't read my thread on callback hooking for libraries, it's here:
Simpler library writing and usage
Interesting Macros
This topic gives some very interesting uses for macros to make complex code simpler:
Interesting macros
Testing
Firstly I'm going to explain my testing procedure. If I have two pieces of code which do the same thing but in different ways and I want to know which is faster I clock them:
pawn Код:
#define CODE_1 printf("%d", 42);
#define CODE_2 new str[4]; format(str, sizeof (str), "%d", 42); print(str);
#define ITERATIONS (10000)
Test()
{
new
t0,
t1,
t2,
i;
t0 = GetTickCount();
for (i = 0; i < ITERATIONS; i++)
{
CODE_1
}
t1 = GetTickCount();
for (i = 0; i < ITERATIONS; i++)
{
CODE_2
}
t2 = GetTickCount();
printf("Time 1: %04d, time 2: %04d", t1 - t0, t2 - t1);
}
It's also sometimes good to wrap the Test function in another loop, especially if the results are very close, to verify the results. Execution times can vary slightly due to threads, this is visible if you run multiple tests in the form of maybe a few milliseconds variation on each time. If you run close results repeatedly then you can check than one is consistently faster rather than faster just the once, as that may have been a fluke and 90% of the time the other is faster, just not that one time.
Sometimes you may need more advanced test code, e.g. to test more than two equivalents, this is fairly easy to expand:
pawn Код:
#define CODE_1 printf("%d", 42);
#define CODE_2 new str[4]; format(str, sizeof (str), "%d", 42); print(str);
#define CODE_3 print("42");
#define ITERATIONS (10000)
Test()
{
new
t0,
t1,
t2,
t3,
i;
t0 = GetTickCount();
for (i = 0; i < ITERATIONS; i++)
{
CODE_1
}
t1 = GetTickCount();
for (i = 0; i < ITERATIONS; i++)
{
CODE_2
}
t2 = GetTickCount();
for (i = 0; i < ITERATIONS; i++)
{
CODE_3
}
t3 = GetTickCount();
printf("Time 1: %04d, time 2: %04d, time 3: %04d", t1 - t0, t2 - t1, t3 - t2);
}
- foreach
pawn Код:
#define FAKE_MAX 200
#define SKIP 0
Iter_Create(P2, FAKE_MAX);
TestFunc()
{
new
fep = 0,
fet = 0,
fip = 0,
fit = 0,
i = 0;
while (i < SKIP)
{
Itter_Add(P2, i++);
}
while (i <= FAKE_MAX)
{
new
t0,
t1,
t2,
j;
t0 = GetTickCount();
for (j = 0; j < 10000; j++)
{
for (new playerid = 0; playerid < FAKE_MAX; playerid++)
{
if (IsPlayerConnected(playerid))
{
// Do something
}
}
}
t1 = GetTickCount();
for (j = 0; j < 10000; j++)
{
foreach(P2, playerid)
{
// Do something
}
}
t2 = GetTickCount();
printf("Players: %04d, for: %04d, foreach: %04d", i, t1 - t0, t2 - t1);
fit = fit + t1 - t0;
fet = fet + t2 - t1;
fip += FAKE_MAX;
fep += i;
if (i < FAKE_MAX)
{
Itter_Add(P2, i);
}
i++;
}
printf("for ms/p: %04d, foreach ms/p: %04d", (fit * 100) / fip, (fet * 100) / fep);
}
pawn Код:
t0 = GetTickCount();
for (j = 0; j < 10000; j++)
{
for (new playerid = 0; playerid < FAKE_MAX; playerid++)
{
Kick(playerid);
}
}
t1 = GetTickCount();
for (j = 0; j < 10000; j++)
{
foreach(P2, playerid)
{
Kick(playerid);
}
}
t2 = GetTickCount();
pawn Код:
t0 = GetTickCount();
for (j = 0; j < 10000; j++)
{
for (new playerid = 0; playerid < FAKE_MAX; playerid++)
{
if (IsPlayerConnected(playerid))
{
Kick(playerid);
}
}
}
t1 = GetTickCount();
for (j = 0; j < 10000; j++)
{
foreach(P2, playerid)
{
Kick(playerid);
}
}
t2 = GetTickCount();
I have actually looked into modifying a compiler so you can do something like:
Код:
eqiv { { for (new playerid = 0; playerid < FAKE_MAX; playerid++) { if (IsPlayerConnected(playerid)) { Kick(playerid); } } } { foreach(P2, playerid) { Kick(playerid); } } }
Minor optimisations
- IEEE numbers
pawn Код:
if (!IsPlayerConnected(playerid))
{
return -1.0;
}
return GetDistance(playerid);
pawn Код:
new
bool:first = true,
Float:distance = 0.0;
foreach (Player, playerid)
{
if (first)
{
first = false;
distance = GetDistance(playerid);
}
else
{
new
Float:temp = GetDistance(playerid);
if (temp < distance)
{
distance = temp;
}
}
}
pawn Код:
new
Float:distance = 100000.0;
foreach (Player, playerid)
{
new
Float:temp = GetDistance(playerid);
if (temp < distance)
{
distance = temp;
}
}
pawn Код:
#define FLOAT_INFINITY (Float:0x7F800000)
#define FLOAT_NEG_INFINITY (Float:0xFF800000)
#define FLOAT_NAN (Float:0xFFFFFFFF)
pawn Код:
new
Float:distance = FLOAT_INFINITY;
foreach (Player, playerid)
{
new
Float:temp = GetDistance(playerid);
if (temp < distance)
{
distance = temp;
}
}
pawn Код:
if (!IsPlayerConnected(playerid))
{
return FLOAT_NAN;
}
return GetDistance(playerid);
pawn Код:
stock IsNaN(number)
{
return !(number <= 0 || number > 0);
}
pawn Код:
stock IsNaN(number)
{
return (number != number);
}
pawn Код:
if (number == FLOAT_NAN)
Let's look at this when you want the distance to see if they're in range of something:
pawn Код:
if (DistanceWithConnectionCheck(playerid) < 100.0)
{
// They're in range and connected
}
pawn Код:
new
Float:distance = DistanceWithConnectionCheck(playerid);
if (distance == -1.0)
{
// They're not connected
}
else if (distance < 100.0)
{
// They're in range and connected
// -1.0 is less than 100, so we need to check for that specially
}
- Rearranging
pawn Код:
printf("%d", 4 + 5);
pawn Код:
printf("%d", 9);
pawn Код:
new
var = (4 + somevar) - 11;
pawn Код:
new
var = somevar + (4 - 11);
pawn Код:
new
var = somevar - 7;
pawn Код:
new
gLastTime[MAX_PLAYERS];
#define EXPIRY 1000
pawn Код:
new
time = GetTickCount();
foreach (Player, playerid)
{
if (time - gLastTime[playerid] > EXPIRY)
{
SendClientMessage(playerid, 0xFF0000AA, "Your time expired");
}
}
Код:
time - gLastTime[playerid] > EXPIRY
Код:
time > EXPIRY + gLastTime[playerid]
Код:
time - EXPIRY > gLastTime[playerid]
pawn Код:
new
time = GetTickCount() - EXPIRY;
foreach (Player, playerid)
{
if (time > gLastTime[playerid])
{
SendClientMessage(playerid, 0xFF0000AA, "Your time expired");
}
}
- Data rearrangements
pawn Код:
#define MAX_OWNDED_VEHICLES 10
new gVehicleOwner[MAX_OWNDED_VEHICLES] = {0, 2, 4, 6, 8, 10, 12, 14, 16, 18};
pawn Код:
printf("The owner of vehicle %d is %d", vehicleid, gVehicleOwner[vehicleid]);
pawn Код:
new i = 0;
while (i < MAX_OWNDED_VEHICLES)
{
if (gVehicleOwner[i] == playerid)
{
printf("Player %d owns vehicle %d", playerid, i);
break;
}
i++;
}
if (i == MAX_OWNDED_VEHICLES)
printf("Player %d does not own a vehicle", playerid);
pawn Код:
#define MAX_PLAYERS 20
new gPlayerVehicle[MAX_PLAYERS] = {0, -1, 1, -1, 2, -1, 3, -1, 4, -1, 5, -1, 6, -1, 7, -1, 8, -1, 9, -1};
Let's look at a far more clean-cut example that was actually in a topic I read recently. This example is VERY cut down, I'm only using 10 models here:
pawn Код:
new
gCars[] = {400, 403, 404, 406, 408, 409},
gHeavyVehicles[] = {400, 402, 408},
gBoats[] = {401, 407},
gFireEngines[] = {402, 405};
pawn Код:
new
gIsACar[] = {1, 0, 0, 1, 1, 0, 1, 0, 1, 1},
gIsAHeavyVehicle[] = {1, 0, 1, 0, 0, 0, 0, 0, 1, 0},
gIsABoat[] = {0, 1, 0, 0, 0, 0, 0, 1, 0, 0},
gIsAFireEngine[] = {0, 0, 1, 0, 0, 1, 0, 0, 0, 0};
pawn Код:
if (gIsACar[model - 400])
Using an entire cell to store a boolean value (1 or 0) is also very inefficient, but we'll cover that later.
- Know the language
pawn Код:
if (2 <= a <= 4)
pawn Код:
if (2 <= a && a <= 4)
If, for example, you didn't know about the "&" operator and you wanted to test if the second bit of a number was set you would need to do something like:
pawn Код:
if ((a << 30) >>> 31)
pawn Код:
if ((a % 4) >>> 1)
pawn Код:
if (a & 2)
This is clearly a very basic example but there are many many other examples. People tend to baulk when I tell them to read pawn-lang.pdf then wonder why I seem to know more about PAWN than they do, 2 + 2 = ... There's a reason I have it bookmarked, and it's not so I can quickly copy the link to post for people (although it is handy for that too).
As my example at the start of this section shows, there's always something new for you to learn, no matter how much you may already know. I can think of two huge parts of PAWN that I don't know at all and the fact that I don't know of any other areas just means I don't know them yet, it doesn't mean they don't exist (it's hard to know what you don't know).
- Distance checks
pawn Код:
IsPlayerInRangeOfPoint(playerid, Float:range, Float:x, Float:y, Float:z);
pawn Код:
if (PlayerDistanceToPoint(playerid, 10.0, 20.0, 2.0) <= 5.0)
{
// They're near 10.0, 20.0, 2.0 - do something
}
Код:
(((x1 - x2) ^ 2) + ((y1 - y2) ^ 2) + ((z1 - z2) ^ 2)) ^ 0.5
pawn Код:
floatsqroot(floatadd(floatadd(floatpower(floatsub(x1, x2), 2), floatpower(floatsub(y1, y2), 2)), floatpower(floatsub(z1, z2), 2)));
pawn Код:
floatsqroot(floatpower(x1 - x2, 2) + floatpower(y1 - y2, 2) + floatpower(z1 - z2, 2));
pawn Код:
floatsqroot(((x1 - x2) * (x1 - x2)) + ((y1 - y2) * (y1 - y2)) + ((z1 - z2) * (z1 - z2)));
pawn Код:
x1 -= x2;
y1 -= y2;
z1 -= z2;
floatsqroot((x1 * x1) + (y1 * y1) + (z1 * z1));
Код:
((x * x) + (y * y) + (z * z)) ^ 0.5 <= 5.0
Код:
((x * x) + (y * y) + (z * z)) ^ 0.5 <= 5.0 (x * x) + (y * y) + (z * z) <= 5.0 ^ 2 (x * x) + (y * y) + (z * z) <= 5.0 * 5.0
pawn Код:
if (PlayerDistanceToPointSquared(playerid, 10.0, 20.0, 2.0) <= 5.0 * 5.0)
{
// They're near 10.0, 20.0, 2.0 - do something
}
pawn Код:
if (IsPlayerInRangeOfPoint(playerid, 5.0, 10.0, 20.0, 2.0))
{
// They're near 10.0, 20.0, 2.0 - do something
}
pawn Код:
stock IsPlayerInRangeOfPoint(playerid, Float:range, Float:x, Float:y, Float:z)
{
new
Float:px,
Float:py,
Float:pz;
GetPlayerPos(playerid, px, py, pz);
px -= x;
py -= y;
pz -= z;
return ((px * px) + (py * py) + (pz * pz)) < (range * range);
}
Update:
I've found the original timing analysis I did on this area and the results are not what some people would expect. I compared a whole load of different distance analysis functions, including less accurate ones people use for speed, for example:
pawn Код:
Type1(Float:x1, Float:y1, Float:z1, Float:x2, Float:y2, Float:z2, Float:dist)
{
x1 = (x1 > x2) ? x1 - x2 : x2 - x1;
if (x1 > dist) return false;
y1 = (y1 > y2) ? y1 - y2 : y2 - y1;
if (y1 > dist) return false;
z1 = (z1 > z2) ? z1 - z2 : z2 - z1;
if (z1 > dist) return false;
return true;
}
The results I got were:
Код:
1703 1781 1594 1641 2265 1782 2281 1891
Note that running the linked code will produce 9 values due to a bug - just ignore the last value (a fatal mistake I made).
My analysis code can be found here.
- Speed order
- Nothing
- Constants
- Variables
- Arrays
- Native functions
- Custom functions
- Remote functions
pawn Код:
for (new i = 0; i < MAX_PLAYERS; i++)
pawn Код:
for (new i = 0, j = GetMaxPlayers(); i < j; i++)
pawn Код:
for (new i = 0; i < GetMaxPlayers(); i++)
I'm not sure where control structures fit in to the list, for example I'm not sure which of these is faster:
pawn Код:
new var = a ? 0 : 1;
printf("%d", var);
printf("%d", var);
printf("%d", var);
printf("%d", var);
printf("%d", var);
pawn Код:
printf("%d", a ? 0 : 1);
printf("%d", a ? 0 : 1);
printf("%d", a ? 0 : 1);
printf("%d", a ? 0 : 1);
printf("%d", a ? 0 : 1);
So why is "nothing" in the list? Consider the following two bits of code:
pawn Код:
new var = random(10);
printf("%d", var);
pawn Код:
printf("%d", random(10));
Where the line starts getting blurry is with repeated calls. For example, which of these is faster:
pawn Код:
new var = gArr[10];
printf("%d %d", var, var);
pawn Код:
printf("%d %d", gArr[10], gArr[10]);
More than one function call - save it in a variable, more than two array reads save it in a variable, so for the above code I would probably use the second version, however a three element print would require three array accesses, which is more than two, thus I would use an intermediary variable:
pawn Код:
new var = gArr[10];
printf("%d %d %d", var, var, var);
- Know your values
pawn Код:
if (killerid == INVALID_PLAYER_ID)
SendDeathMessage(INVALID_PLAYER_ID, playerid, reason);
else
SendDeathMessage(killerid, playerid, reason);
pawn Код:
if (var == 1)
printf("%d", 1);
else
printf("%d", var);
pawn Код:
printf("%d", var);
pawn Код:
SendDeathMessage(killerid, playerid, reason);
- Return values
pawn Код:
new Float:health;
for (new i = 0; i < MAX_PLAYERS: i++)
if (IsPlayerConnected(i))
{
GetPlayerHealth(i, health);
SetPlayerHealth(i, health + 10.0);
}
pawn Код:
new Float:health;
for (new i = 0; i < MAX_PLAYERS: i++)
if (GetPlayerHealth(i, health))
SetPlayerHealth(i, health + 10.0);
This can also be applied to other functions, even if they have return values:
pawn Код:
if (IsPlayerInAnyVehicle(playerid))
{
new vehicleid = GetPlayerVehicleID(playerid);
SetVehiclePos(vehicleid, 0.0, 0.0, 10.0);
}
pawn Код:
new vehicleid = GetPlayerVehicleID(playerid);
if (vehicleid)
SetVehiclePos(vehicleid, 0.0, 0.0, 10.0);
One other aspect of return values is how long they exist for. If you set a variable in an if statement (which, if you’re not careful, will give an unintended assignment warning) the value you just set is still in the if statement, so if you did:
pawn Код:
new
a = 1,
b = 0;
if ((b = a))
Then "a" would be assigned to "b", so "b" would be 1, and that 1 would still be active in the if effectively as the return of the assignment, so this if is true, however:
pawn Код:
new
a = 0,
b = 1;
if ((b = a))
pawn Код:
stock strcpy(dest[], src[])
{
new i = 0;
while ((dest[i] = src[i])) i++;
}
pawn Код:
new vehicleid;
for (new i = 0; i < MAX_PLAYERS: i++)
if ((vehicleid = GetPlayerVehicleID(i)))
SetVehiclePos(vehicleid, 0.0, 0.0, 10.0);
pawn Код:
NameCheck(name[])
{
new
i,
ch;
while ((ch = name[i++]) && ((ch == ']') || (ch == '[') || (ch == '_') || ('0' <= ch <= '9') || ((ch |= 0x20) && ('a' <= ch <= 'z')))) {}
return !ch;
}
- Small snippets
- Equivalence
pawn Код:
if (string[1] == 65)
pawn Код:
if (string[1] == 0x41)
pawn Код:
if (string[1] == 0b01000001)
pawn Код:
if (string[1] == 'A')
This can be taken a bit further with zero:
pawn Код:
if (string[1] == 0)
pawn Код:
if (string[1] == 0x00)
pawn Код:
if (string[1] == ((27 + 3) / 5) - 6)
pawn Код:
if (string[1] == 0b00000000)
pawn Код:
if (string[1] == '\0')
pawn Код:
if (string[1] == false)
pawn Код:
if (string[1] != true)
pawn Код:
if (string[1] == 0.0)
pawn Код:
if (!string[1])
- Empty strings
pawn Код:
if (strlen(string) == 0)
{
// The string is empty because it's length is 0
}
As we know that the end of a string is denoted by a NULL character all we need to do to see if a string is empty is to see if the first character is the end of the string or not:
pawn Код:
if (string[0] == '\0')
{
// The string is empty because it's first character is the end
}
pawn Код:
if (!string[0])
{
// The string is empty because it's first character doesn't exist
}
pawn Код:
if (string[0] == '\1' && string[1] == '\0')
{
// The string is empty because it's specially marked as empty
}
pawn Код:
#define isnull(%1) \
((!(%1[0])) || (((%1[0]) == '\1') && (!(%1[1]))))
pawn Код:
if (isnull(string))
{
// The string is empty because isnull said so
}
- Copying strings
pawn Код:
format(dest, sizeof (dest), "%s", src);
pawn Код:
strmid(b, a, 0, strlen(a), sizeof (b));
format(b, sizeof (b), "%s", a);
b[0] = '\0';
strcat(b, a, sizeof (b));
memcpy(b, a, 0, strlen(a) * 4 + 4, sizeof (b)); // Length in bytes, not cells.
strcpy(b, a);
b = a;
For short strings in small arrays, "b = a;" is fastest when applicable, strcat with prior NULL termination (important) is second.
For short strings in large arrays, strcat is fastest.
For longer strings in longer arrays, "b = a;" is again fastest, with memcpy second.
For huge arrays "b = a;" seems to be fastest.
Where possible use standard array assignment, however this is not always possible, for example when a string of unknown size is passed to a function. In these cases I would suggest using strcat (if you're interested, note the bizzare syntax):
pawn Код:
#define strcpy(%0,%1,%2) \
strcat((%0[0] = '\0', %0), %1, %2)
pawn Код:
strcpy(dest, src, sizeof (dest));
Assumptions
- Introduction
Assumptions are saying you know what something always is just because it's often that. Example:
pawn Код:
public OnGameModeInit()
{
AddPlayerClass(167, 0.0, 0.0, 5.0, 0.0, 0, 0, 0, 0, 0, 0); // Clucking Bell employee
AddPlayerClass(179, 0.0, 0.0, 5.0, 0.0, 0, 0, 0, 0, 0, 0); // Ammunation employee
}
public OnPlayerRequestClass(playerid, classid)
{
switch (classid)
{
case 0:
{
GameTextForPlayer(playerid, "Clucking Bell", 5000, 3);
}
case 1:
{
GameTextForPlayer(playerid, "Ammunation", 5000, 3);
}
}
return 1;
}
- A person using your mode also has filterscripts which add skins. This will throw off your class values.
- A person using your mode decides they want to add skins and add them to the start of the list. This again throws off your class values.
- A SA:MP version change alters the way IDs are assigned. Entirely altering your class IDs.
- Solutions
- Ignore it
This is clearly not a solution at all.
- Make modifications easy
pawn Код:
dcmd_chicken(playerid, params[])
{
if (gClass[playerid] != 0) return SendClientMessage(playerid, 0xFF0000AA, "Sorry, you're not a Clucking Bell employee");
...
}
pawn Код:
#define CLUCKING_BELL_CLASS (0)
#define AMMUNATION_CLASS (1)
public OnGameModeInit()
{
AddPlayerClass(167, 0.0, 0.0, 5.0, 0.0, 0, 0, 0, 0, 0, 0); // Clucking Bell employee
AddPlayerClass(179, 0.0, 0.0, 5.0, 0.0, 0, 0, 0, 0, 0, 0); // Ammunation employee
}
public OnPlayerRequestClass(playerid, classid)
{
switch (classid)
{
case CLUCKING_BELL_CLASS:
{
GameTextForPlayer(playerid, "Clucking Bell", 5000, 3);
}
case AMMUNATION_CLASS:
{
GameTextForPlayer(playerid, "Ammunation", 5000, 3);
}
}
return 1;
}
dcmd_chicken(playerid, params[])
{
if (gClass[playerid] != CLUCKING_BELL_CLASS) return SendClientMessage(playerid, 0xFF0000AA, "Sorry, you're not a Clucking Bell employee");
...
}
- Code defensively
pawn Код:
new
gCluckingBellSkin = -1,
gAmmunationSkin = -1;
public OnGameModeInit()
{
gCluckingBellSkin = AddPlayerClass(167, 0.0, 0.0, 5.0, 0.0, 0, 0, 0, 0, 0, 0); // Clucking Bell employee
gAmmunationSkin = AddPlayerClass(179, 0.0, 0.0, 5.0, 0.0, 0, 0, 0, 0, 0, 0); // Ammunation employee
}
public OnPlayerRequestClass(playerid, classid)
{
if (classid == gCluckingBellSkin)
{
GameTextForPlayer(playerid, "Clucking Bell", 5000, 3);
}
else if (classid == gAmmunationSkin)
{
GameTextForPlayer(playerid, "Ammunation", 5000, 3);
}
return 1;
}
dcmd_chicken(playerid, params[])
{
if (gClass[playerid] != gCluckingBellSkin) return SendClientMessage(playerid, 0xFF0000AA, "Sorry, you're not a Clucking Bell employee");
...
}
- Important
Memory reduction
Recall this code from earlier:
pawn Код:
new
gIsACar[] = {1, 0, 0, 1, 1, 0, 1, 0, 1, 1},
gIsAHeavyVehicle[] = {1, 0, 1, 0, 0, 0, 0, 0, 1, 0},
gIsABoat[] = {0, 1, 0, 0, 0, 0, 0, 1, 0, 0},
gIsAFireEngine[] = {0, 0, 1, 0, 0, 1, 0, 0, 0, 0};
- All vehicles
Код:
Bit: 0 1 2 3 4 5 6 7 8 9 10 ... 31 Val: 1 2 4 8 16 32 64 128 256 512 1024 ... 2147483648 1/0: 1 0 0 1 1 0 1 0 1 1 x ... x
Let's say we want to find out if vehicle model 403 is a car. Firstly we subtract 400 to get the number in range then we need to test the fourth bit (0, 1, 2, 3). The fourth bit has a value of eight, so we need to test whether the number 857 has the eight bit set, this is done using bitwise AND:
pawn Код:
if (857 & 8)
{
// Bit is set, the vehicle is a car
}
pawn Код:
if (gIsACar & 8)
{
// Bit is set, the vehicle is a car
}
pawn Код:
switch (model - 400)
{
case 0: if (gIsACar & 1) ...
case 1: if (gIsACar & 2) ...
case 2: if (gIsACar & 4) ...
case 3: if (gIsACar & 8) ...
case 4: if (gIsACar & 16) ...
pawn Код:
switch (model - 400)
{
case 0: return true;
case 1: return false;
case 2: return false;
case 3: return true;
case 4: return true;
}
pawn Код:
new
bit = 1;
model -= 400;
for (new i = 0; i < model; i++)
{
bit *= 2;
}
Код:
8 = 1 << 3
pawn Код:
if (gIsACar & (1 << (model - 400)))
{
return true;
}
return false;
pawn Код:
stock IsACar(model)
{
return gIsACar & (1 << (model - 400));
}
pawn Код:
#define IsACar(%0) \
(gIsACar & (1 << ((%0) - 400)))
- All attributes
Model 402 is a heavy fire engine, it's not a car or boat. So we have bits 2 and 3 set, that's 4 and 8, 4 + 8 (technically 4 | is 12, so we have:
pawn Код:
new gModel402 = 12;
pawn Код:
return gModel402 & 2;
This can be turned into an array of attributes for all models:
pawn Код:
new gModels[] = {x, x, 12, x, x, x, x, x, x, x};
pawn Код:
return gModels[model - 400] & 2;
pawn Код:
#define IsACar(%0) \
(gModels[(%0) - 400] & 1)
#define IsABoat(%0) \
(gModels[(%0) - 400] & 2)
#define IsAHeavy(%0) \
(gModels[(%0) - 400] & 4)
pawn Код:
#define MODEL_CAR (1)
#define MODEL_BOAT (2)
#define MODEL_HEAVY (4)
#define MODEL_FIRE (8)
new gModels[] =
{
x,
x,
MODEL_HEAVY | MODEL_FIRE,
x,
x,
x,
x,
x,
x,
x
};
#define IsACar(%0) \
(gModels[(%0) - 400] & MODEL_CAR)
#define IsABoat(%0) \
(gModels[(%0) - 400] & MODEL_BOAT)
#define IsAHeavy(%0) \
(gModels[(%0) - 400] & MODEL_HEAVY)
pawn Код:
enum (<<= 1)
{
MODEL_CAR = 1,
MODEL_BOAT,
MODEL_HEAVY,
MODEL_FIRE
}
- More that 32 values
pawn Код:
enum e_MODELS
{
MODEL_CAR,
MODEL_BOAT,
MODEL_HEAVY,
MODEL_FIRE,
...
MODEL_SOMETHING
}
new Bit:gModels[410 - 400][Bit_Bits(e_MODELS)];
#define IsACar(%0) \
(Bit_Get(gModels[(%0) - 400], MODEL_CAR))
#define IsSomething(%0) \
(Bit_Get(gModels[(%0) - 400], MODEL_SOMETHING))
- Excess dimensions
I have an array of 10 values, let's for the sake of argument call them weapon prices. So we have 10 weapons, each with a price, and we want to store them in an array. Each weapon has an ID, from 0 to 9, so to get that weapon's price you need to access that index in the array:
pawn Код:
new
gPrices[10] = // 10 weapons, thus 10 prices
{
1000,
2000,
5000,
2000,
10000,
500,
3000,
2000,
100,
750
};
pawn Код:
new
weaponPrice = gPrices[5];
pawn Код:
new
gPrices[10][1] = // 10 weapons, thus 10 prices
{
{1000},
{2000},
{5000},
{2000},
{10000},
{500},
{3000},
{2000},
{100},
{750}
};
pawn Код:
new
weaponPrice = gPrices[5][0];
CPU vs Memory
This is such a common factor in writing code that it deserves a special mention. In almost everything you have a choice of how to do something, usually one way will be fast but use a lot of memory and the other will be slow but use very little memory. For example in the foreach example above the foreach code is faster, but uses a big array, IPC is slower but uses next to no memory as it has nothing to store. In all these cases you just have to make the decision based on circumstances. The previous section on memory reduction listed all sorts of ways to use less memory but they all used extra code to do it, making them slower than the original huge arrays. However in this case the reduction was so great (an approximately thirty-two fold reduction in memory use) that it more than offset the minor speed decrease (as stated above also bitwise operators are very fast).
There is, however, a third option - complexity.
In the foreach/IPC example foreach had the greater speed and IPC has the better memory footprint, but it's possible to combine the best of both worlds by writing more complex code. More complex code generally handles more situations explicitly, meaning you don't have to write generic code to handle all possibilities. In the player loop example this would manifest itself as something like:
pawn Код:
if (IsPlayerConnected(0))
{
// Do something
}
if (IsPlayerConnected(1))
{
// Do something
}
if (IsPlayerConnected(2))
{
// Do something
}
...
if (IsPlayerConnected(199))
{
// Do something
}
Lists
This and the next setion are basically ripped straight from the wiki as I wrote it there in the first place.
Lists are a very useful type of structure, they're basically an array where the next piece or relevant data is pointed to by the last piece.
Example:
Say you have the following array:
Код:
3, 1, 64, 2, 4, 786, 2, 9
Код:
1, 2, 2, 3, 4, 9, 64, 786
Код:
start = 1 3, 1, 64, 2, 4, 786, 2, 9 4, 3, 5, 6, 7, -1, 0, 2
The other advantage of this method of sorting the numbers is adding more numbers is a lot faster. If you wanted to add another number 3 to the sorted array you would need to first shift at least 4 numbers one slot to the right to make space, not terrible here but very slow in larger arrays. With the list version you could just append the 3 to the end of the array and modify a single value in the list;
Код:
start = 1 3, 1, 64, 2, 4, 786, 2, 9, 3 8, 3, 5, 6, 7, -1, 0, 2, 4 ^ modify this value ^ next highest slot
Код:
start = 1 3, 1, 64, X, 4, 786, 2, 9, 3 8, 6, 5, 6, 7, -1, 0, 2, 4 ^ Changed to jump over the removed value
- Types
Код:
start = 1 end = 5 value: 3, 1, 64, 2, 4, 786, 2, 9, 3 next: 8, 3, 5, 6, 7, -1, 0, 2, 4 last: 6, -1, 7, 1, 8, 2, 3, 4, 0
Код:
2, 3, 3 1, 2, -1 -1, 2, 0
Код:
2, 3, 3 1, 2, -1 -1, 0, 1
The other type of list is the looping one where the last value points back to the first. The obvious advantage to this is that you can get to any value from any other value without knowing in advance whether the target is before or after the start point, you just need to be careful not to get into an infinite loop as there's no explicit -1 end point. These lists do still have start points. You can also do double looping lists where you have a next and last list, both of which loop round:
Код:
start = 1 end = 5 3, 1, 64, 2, 4, 786, 2, 9, 3 8, 3, 5, 6, 7, 1, 0, 2, 4 6, 5, 7, 1, 8, 2, 3, 4, 0
- Mixed lists
Код:
sortedStart = 3 unusedStart = 1 value: 34, X, X, 6, 34, 46, X, 54, 23, 25, X, 75, X, 45 sort: 4, 8, 13, 7, 11, 9, 0, -1, 5 free: 2, 6, 10, 12, -1
Код:
sortedStart = 3 unusedStart = 1 value: 34, X, X, 6, 34, 46, X, 54, 23, 25, X, 75, X, 45 next: 4, 2, 6, 8, 13, 7, 10, 11, 9, 0, 12, -1, -1, 5
- Code
This example shows how to write code for a list sorted numerically ascending.
pawn Код:
#define NUMBER_OF_VALUES (10)
enum E_DATA_LIST
{
E_DATA_LIST_VALUE,
E_DATA_LIST_NEXT
}
new
gListData[NUMBER_OF_VALUES][E_DATA_LIST],
gUnusedStart = 0,
gListStart = -1; // Starts off with no list
// This function initializes the list
List_Setup()
{
new
i;
size--;
for (i = 0; i < size; i++)
{
// To start with all slots are unused
gListData[i][E_DATA_LIST_NEXT] = i + 1;
}
// End the list
gListData[size][E_DATA_LIST_NEXT] = -1;
}
// This function adds a value to the list (using basic sorting)
List_Add(value)
{
// Check there are free slots in the array
if (gUnusedStart == -1) return -1;
new
pointer = gListStart,
last = -1
slot = gUnusedStart;
// Add the value to the array
gListData[slot][E_DATA_LIST_VALUE] = value;
// Update the empty list
gUnusedStart = gListData[slot][E_DATA_LIST_NEXT];
// Loop through the list till we get to bigger/same size number
while (pointer != -1 && gListData[pointer][E_DATA_LIST_VALUE] < value)
{
// Save the position of the last value
last = pointer
// Move on to the next slot
pointer = gListData[pointer][E_DATA_LIST_NEXT];
}
// If we got here we ran out of values or reached a larger one
// Check if we checked any numbers
if (last == -1)
{
// The first number was bigger or there is no list
// Either way add the new value to the start of the list
gListData[slot][E_DATA_LIST_NEXT] = gListStart;
gListStart = slot;
}
else
{
// Place the new value in the list
gListData[slot][E_DATA_LIST_NEXT] = pointer;
gListData[last][E_DATA_LIST_NEXT] = slot;
}
return slot;
}
// This function removes a value from a given slot in the array (returned by List_Add)
List_Remove(slot)
{
// Is this a valid slot
if (slot < 0 || slot >= NUMBER_OF_VALUES) return 0;
// First find the slot before
new
pointer = gListStart,
last = -1;
while (pointer != -1 && pointer != slot)
{
last = pointer;
pointer = gListData[pointer][E_LIST_DATA_NEXT];
}
// Did we find the slot in the list
if (pointer == -1) return 0;
if (last == -1)
{
// The value is the first in the list
// Skip over this slot in the list
gListStart = gListData[slot][E_LIST_DATA_NEXT];
}
else
{
// The value is in the list
// Skip over this slot in the list
gListData[last][E_LIST_DATA_NEXT] = gListData[slot][E_LIST_DATA_NEXT];
}
// Add this slot to the unused list
// The unused list isn't in any order so this doesn't matter
gListData[slot][E_LIST_DATA_NEXT] = gUnusedStart;
gUnusedStart = slot;
return 1;
}
Binary trees are a very fast method of searching for data in an array by using a very special list system. The most well known binary tree is probably the 20 questions game, with just 20 yes/no questions you can have over 1048576 items. A binary tree, as it's name implies, is a type of tree, similar to a family tree, where every item has 0, 1 or 2 children. They are not used for ordering data like a list but sorting data for very efficient searching. Basically you start with an item somewhere near the middle of the ordered list of objects (e.g. the middle number in a sorted array) and compare that to the value you want to find. If it's the same you've found your item, if it's greater you move to the item to the right (not immediately to the right, the item to the right of the middle item would be the item at the three quarter mark), if it's less you move left, then repeat the process.
- Example
Код:
1 2 5 6 7 9 12 14 17 19 23 25 28 33 38
If we construct a binary tree from the data above we get:
If you read left to right, ignoring the vertical aspect you can see that the numbers are in order. Now we can try find the 7.
The start number is 14, 7 is less than 14 so we go to the slot pointed to by the left branch of 14. This brings us to 6, 7 is bigger than 6 so we go right to 9, then left again to 7. This method took 4 comparisons to find the number (including the final check to confirm that we are on 7), using a straight search would have taken 5.
Lets say there is no 7, we would end up with this binary tree:
This, unlike the example above, has a single child number (the 9), as well as 2 and 0 child numbers. You only get a perfect tree when there are (2^n)-1 numbers (0, 1, 3, 7, 15, 31 ...), any other numbers will give a not quite full tree. In this case when we get to the 9, where the 7 will be, we'll find there is no left branch, meaning the 7 doesn't exist (it cannot possibly be anywhere else in the tree, think about it), so we return -1 for invalid slot.
- Balanced and unbalanced
Obviously this tree is still valid but the right side is much larger than the left, however finding 25 still only takes 7 comparisons in this compared to 12 in the straight list. Also, as long as you start with a fairly middle number the random insertion method should produced a fairly balanced tree. The worst possible thing you can do is put the numbers in in order as then there will be no left branches at all (or right branches if done the other way), however even in this worst case the binary tree will take no longer to search than the straight list.
- Modification
- Addition
- Deletion
The first method is the simplest computationally. Basically you choose one of the branches (left or right, assume right for this explanation) and replace the node you've removed with the first node of that branch (i.e. the right child of the node you've removed). You then go left through than new branch till you reach the end and place the left branch there. E.g. if you removed the 14 from the original example you would end up with 25 taking it's place at the top of the tree and 6 attached to the left branch of 17. This method is fast but ends up with very unbalanced trees very quickly.
The second method is to get all the numbers which are children of the node you just removed and rebuild a new binary tree from them, then put the top of that tree into the node you've just removed. This keeps the tree fairly well balanced but is obviously slower.
The third method is to combine the two methods above and rebuild the tree in-line, this is more complex to code but keeps the tree balanced and is faster than the second method (though no-where near as fast as the first).
The final method listed here is to simply set a flag on a value saying it's not used any more, this is even faster than the first method and maintains the structure but means you can't re-use slots unless you can find a value to replace it with later.