Hướng dẫn dùng sa mp 0.3.7

The longer your chain is and the more failed checks there are, the more instructions the code uses. Each failed single value check adds 3 instructions and each failed range check adds 9 instructions.

switch

Code:


new val;switch (val) { case 1: { // do something here }case 2 .. 4: { // do something here }default: { // do something here } }


When using switch, the compiler generates a case table that contains the value and jump address for every single case, including the default case. Here is the assembly equivalent to this code:

Code:


`new val;

emit LOAD.S.pri val

// load the value of val to the primary register

emit SWITCH casetable // jump to the case table// case 1:

single: // do something here

emit JUMP done // everything is done, jump to "done"

range:

// do something here

emit JUMP done // everything is done, jump to "done"

otherwise:

// do something here

emit JUMP done // everything is done, jump to "done"

casetable:

emit CASETBL

// default:

emit CASE 4 otherwise // the amount of case table items; the jump address of "default"

// case 1:

emit CASE 1 single // the value and jump address of the case

// case 2 .. 4:

emit CASE 2 range

emit CASE 3 range

emit CASE 4 range

done:

`


Whatever the value of val is 1, the code always uses just three instructions:

Code:


LOAD.S.pri val SWITCH casetable JUMP done


The SWITCH instruction jumps to the case table, performs a linear search on the cases and jumps to the correct one. If the search does not find a matching case, it jumps to the default case instead. It is similar to the behaviour of chained else ifs, but since it is done in native code instead of using AMX instructions, the performance is a lot better.

When NOT to use switch?

Large ranges

As seen before, case tables can only use single values as cases, which means that if you use a range of 10000 elements in your

switch, the compiler will have to write 10000 entries to the case table and the runtime will have to search through 10000 entries. Using the following benchmarking script I determined that the runtime performance of a case table becomes worse than the one of a single a <= x <= b check at around 30 entries.

Code:


`

define MAX 30

define ITERATIONS 1000000new tick = GetTickCount();for (new i; i < ITERATIONS; i++) {

switch ((i % MAX) + 1) { case 1 .. MAX: {} } }

printf(

"switch: %i", GetTickCount() - tick);

tick = GetTickCount();

for (new i; i < ITERATIONS; i++) { if (1 <= (i % MAX) + 1 <= MAX) {} }

printf(

"if: %i", GetTickCount() - tick);`


However, runtime performance is not the biggest issue. As seen from this forum topic, the amount of time it takes for the compiler to generate large case tables can be so long that it seems for the user that the compiler has ran into an infinite loop. Even ranges small enough to only have a small impact on the compilation time (for example, 1000 elements) can eventually make the compilation process painful if the delays stack up.

Floating point values

When using

switch with floating point values, the case table must also contain every single value within the range. The step between two cases grows as the values grow, but it starts at the smallest possible floating point value in the IEEE754 specification, which is 1.4E-45. This means that each entry differs from the previous and next one by just 0.0000000000000000000000000000000000000000000014.

Some examples of ranges with the respective amounts of case table elements:

Code:


`| Range | Amount of entries |

----------
0.0 .. 0.0000000000000000000000000000000000000001 71363
1.0 .. 1.01 83887
10.0 .. 10.1 104859
100.0 .. 101.0 131073
1000.0 .. 1010.0 163841
10000.0 .. 10100.0 102401
| 100000.0 .. 101000.0 | 128001 |`

This test should be enough to show that using switch with floating point values relevant in the context of SA-MP (coordinates, health, etc.) is completely unreasonable.

When to use switch?

I would say that everywhere where there are chains of comparisons against constant values. I have seen people saying that