Welcome, Guest |
You have to register before you can post on our site.
|
Online Users |
There are currently 517 online users. » 0 Member(s) | 514 Guest(s) Bing, Google, Yandex
|
Latest Threads |
What is mebendazole used ...
Forum: Questions and Suggestions
Last Post: lucasmillerfeb2022
6 hours ago
» Replies: 0
» Views: 12
|
Rosalife Gamemode [openMP...
Forum: Gamemodes
Last Post: vactidylee
Yesterday, 04:17 AM
» Replies: 5
» Views: 4,549
|
White Screen
Forum: Support
Last Post: Phat202146_real
2024-11-21, 02:50 PM
» Replies: 0
» Views: 25
|
Offensive-Core: TDM
Forum: Gamemodes
Last Post: threezhang.cn
2024-11-21, 09:54 AM
» Replies: 5
» Views: 940
|
Outstanding Customer Serv...
Forum: Chat
Last Post: va6220902
2024-11-21, 07:52 AM
» Replies: 0
» Views: 130
|
New place
Forum: Life
Last Post: sjaardamilly
2024-11-21, 06:58 AM
» Replies: 0
» Views: 24
|
How Zhewitra Oral Jelly W...
Forum: General Discussions
Last Post: erctilenovus
2024-11-20, 08:39 AM
» Replies: 0
» Views: 31
|
Cenforce 100 Mg Medicine ...
Forum: Chat
Last Post: ezraallen45ea
2024-11-19, 10:00 AM
» Replies: 0
» Views: 38
|
I get error 021 using y_h...
Forum: Pawn Scripting
Last Post: daniscript18
2024-11-18, 11:34 PM
» Replies: 0
» Views: 46
|
What is the use of Wakler...
Forum: Other
Last Post: allencooper
2024-11-18, 10:37 AM
» Replies: 0
» Views: 30
|
|
|
The internals of switch and chained else ifs and which one should you use |
Posted by: kristo - 2019-04-17, 12:25 PM - Forum: Tutorials
- No Replies
|
|
The internals of switch and chained else ifs and which one should you use
This tutorial takes a look into how if and switch statements work on the assembly level and helps determine, where to use a switch and where to prefer chained else ifs. I decided to write this tutorial, because one of the topics I am about to address is a semi-common issue and currently there is no good place to refer to when it comes to it. In addition to that, the research I did for this tutorial helped me understand the AMX structure quite a bit more than I used to before.
The internals
I used the -a flag to generate the assembly of two simple snippets of code that perform the same task, one using switch and the other using chained else ifs. I also took the time to rewrite them in a more human-readable form (using variable names instead of raw addresses, named labels instead of numbered ones prefixed with l., etc.) and to document them. It is worth noting that while the rewritten assembly is valid, it does not currently compile due to a compiler bug related to forward jumps.
Chained else ifs
PHP Code: new val;
if (val == 1) {
? ?// do something here
} else if (2 <= val <= 4) {
? ?// do something here
} else {
? ?// do something here
}
For the sake of simplicity I used the form 2 <= val <= 4 instead of val >= 2 && val <= 4 for the range check. If written the other way, the code would use 7 jump instructions instead of 4 and keeping track of the instructions would be more difficult. It would also be a tiny bit slower since it would have to load the value of val to the primary register one more time.
When using chained else ifs, the assembly contains the AMX instructions for each individual check. If the check fails, it jumps to the next check, otherwise it jumps to the end of the chain. Here is the assembly equivalent to this code:
PHP Code: new val;
// if (val == 1)
#emit LOAD.S.pri ? ?val ? ? ? ? // load the value of val to the primary register
#emit EQ.C.pri ? ? ?1 ? ? ? ? ? // compare the value in the primary register to constant 1: they must be equal; store the result in the primary register
#emit JZER ? ? ? ? ?range ? ? ? // if the result of the comparison is false, jump to "range" to perform the next check
// do something here
#emit JUMP ? ? ? ? ?done ? ? ? ?// everything is done, jump to "done"
// else if (2 <= val <= 4)
range:
#emit CONST.pri ? ? 2 ? ? ? ? ? // store the constant value 2 in the primary register
#emit LOAD.S.alt ? ?val ? ? ? ? // load the value of val to the alternate register
#emit SLEQ ? ? ? ? ? ? ? ? ? ? ?// compare the values in the registers to eachother: 2 (primary) must be less than or equal to val (alternate); store the result in the primary register
#emit PUSH.pri ? ? ? ? ? ? ? ? ?// load the value to the primary register (the result of the comparison) in the stack
#emit CONST.pri ? ? 4 ? ? ? ? ? // store the constant value 4 in the primary register
#emit SGEQ ? ? ? ? ? ? ? ? ? ? ?// compare the values in the registers to eachother: 4 (primary) must be greater than or equal to val (alternate); store the result in the primary register
#emit POP.alt ? ? ? ? ? ? ? ? ? // load the value of the first comparison from the stack to the alternate register
#emit AND ? ? ? ? ? ? ? ? ? ? ? // check if both of the comparisons were true by checking if both registers contain a non-zero value
#emit JZER ? ? ? ? ?otherwise ? // if the result of the comparison is false, jump to "otherwise"
// do something here
#emit JUMP ? ? ? ? ?done ? ? ? ?// everything is done, jump to "done"
// else
otherwise:
// do something here
// no jump needed since "done" is just after this code
done:
If the value of val is 1, the code uses four instructions:
PHP Code: LOAD.S.pri? val
EQ.C.pri? ? 1
JZER? ? ? ? range ; the jump does not happen, but the instruction is still used
JUMP? ? ? ? done
If the value of val is 2, 3 or 4, the code uses 13 instructions:
PHP Code: LOAD.S.pri? val
EQ.C.pri? ? 1
JZER? ? ? ? range
CONST.pri? ?2
LOAD.S.alt? val
SLEQ
PUSH.pri
CONST.pri? ?4
SGEQ
POP.alt
AND
JZER? ? ? ? otherwise ; the jump does not happen, but the instruction is still used
JUMP? ? ? ? done
If the value is not matched by any of the previous checks, the code uses 12 instructions:
PHP Code: LOAD.S.pri? val
EQ.C.pri? ? 1
JZER? ? ? ? range
CONST.pri? ?2
LOAD.S.alt? val
SLEQ
PUSH.pri
CONST.pri? ?4
SGEQ
POP.alt
AND
JZER? ? ? ? otherwise
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
PHP 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:
PHP 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:
PHP 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.
PHP Code: #define MAX 30
#define ITERATIONS 1000000
new 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 switch is faster no matter what and using a switch with two cases is much faster than an if-else. However, their runtime performance is about the same and where an if and an else make more sense, they should be used instead.
Tips and tricks
Using switch with floatcmp to compare floating point values
Comparison operators for variables with the Float tag are overloaded to use the floatcmp native instead of comparing them with AMX instructions. When comparing floating point values to eachother and having different outcomes depending of if one of the values is larger than the other or they are equal, you can save a floatcmp call by comparing them using switch instead. floatcmp returns 0 if the values are equal, 1 if the first value is larger and -1 if the second value is larger, so the following snippets of code are equivalent, but the second one performs better:
PHP Code: if (a > b) { // floatcmp called implicitly
? ?// a is larger than b
} else if (a < b) { // floatcmp called implicitly
? ?// a is smaller than b
} else {
? ?// a and b are equal
}
PHP Code: switch (floatcmp(a, b)) {
? ?case 1: {
? ? ? ?// a is larger than b
? ?}
? ?case -1: {
? ? ? ?// a is smaller than b
? ?}
? ?case 0: {
? ? ? ?// a and b are equal
? ?}
}
Using switch to compare strings
Comparing strings with strcmp has been notorious being both slow and tedious for a long time. Although most of that bad reputation came when ZCMD became the goto command processor and strcmp is still very useful elsewhere, there is a grain of truth in every joke. When comparing a string to a long chain of other strings, comparing their hashes would be more efficient and also convenient due to the ability to use switch, because the hashes are in the form of integers. The PAWN language does not support such thing natively, but y_stringhash from YSI does just the job. According to Y_Less? tests, comparing strings using hashes becomes more efficient than using strcmp at around 10 comparisons. More information about y_stringhash and its usage can be found from this topic.
PHP Code: if (!strcmp(number, "one")) {
} else if (!strcmp(number, "two") || !strcmp(number, "three")) {
} else {
}
PHP Code: switch (YHash(number)) {
? ?case _H<one>: {
? ?}
? ?case _H<two>, _H<three>: {
? ?}
? ?default: {
? ?}
}
Credits
- Yashas for the excellent AMX Assembly documentation.
- Y_Less for reviewing the tutorial prior to me publishing it.
- Users of the SA-MP Discord for productive conversations on this topic.
|
|
|
[MAP] Desert Airport |
Posted by: iChiwi_. - 2019-04-17, 11:28 AM - Forum: Filterscripts
- No Replies
|
|
Abandoned Airport, which?had been destroyed due to the wars that happened recently between the?countries.
Every country trying to capture this place to have the most powerful vehicles.
Download:-
https://pastebin.com/W0VAbtc9?( will retexture it soon, so check the topic regularly )
Credits:-
iChiwi_.
|
|
|
Discord SACNR Monitor Bot |
Posted by: Bork - 2019-04-17, 10:22 AM - Forum: Tech
- Replies (14)
|
|
I wrote a discord bot that will query information from the SACNR Monitor based on a IP and port you provide. It currently does not support URL's with an A record for an IP but I will eventually bring that out.
The invite link can be found here:?https://discordapp.com/api/oauth2/authorize?client_id=565207337883795487&permissions=8&scope=bot
To use the bot, all you need to type is:
Code: !sacnr ip port(default 7777)
You'll get information like this thrown out:
For those who are wondering where it's being hosted and what it was written in, dakyskye?is hosting the bot and it was written in C#.
Have fun!
|
|
|
[Include] pawn-array-view: non-owning spans for multi-dimensional pawn arrays |
Posted by: Yashas - 2019-04-17, 05:00 AM - Forum: Plugins
- No Replies
|
|
pawn-array-view
pawn-array-view is an efficient C library that abstracts the complex memory layout of multi-dimensional pawn arrays and provides an intuitive C/ array-like interface for dealing with them. The name suggests that the library only allows read-only access to the arrays but that's not true; you can access and modify the arrays. Any decent C compiler will be able to optimize off all access and modifications to the arrays into simple pointer arithmetic.
Defects (Help Wanted)
LICM optimizations aren't pushing loop invariant subexpressions out of the loop because of aliased pointers. I haven't figured out how to correctly place restrict qualifiers which aren't ignored.
Code: int main() {
pawn::array_view<int, 3> array((cell*)0x10000);
for(int i = 0; i < 100; i) {
for(int j = 0; j < 100; j) {
for(int k = 0; k < 128; k) {
array[i][j][k] = 10;
}
}
}
return 0;
}
The compiler does not move the array[i][j] calculations outside the loop. There is a valid reason to not move them since modifying an element could modify something which would require recalculating the indexes. This is useful only when indirection tables are being modified. The common case is data being modified but not the indirection table. In this situation, moving the calculations out of the loop improves performance.
There are more consequences. The compiler can also vectorize the loop if it manages to pull the subexpression out of the loop. The failure to move the subexpression outside the loop prevents a ton of optimizations.
Note that the optimizations happen when the array is only being read. The problem is for modifications only.
Plans
If the size of the arrays are known at compile-time, a better interface can be provided. I have plans on providing full-fledged container like support for arrays where the dimensions are available at compile time.
There is a project going on in the develop branch which has working but inefficient code for dealing with known dimensions at run-time.
I plan on renaming array_view to span. This include was initially providing a view but then the ability to mutate was added but the name wasn't changed. It's a very misleading name now.
Links
|
|
|
old trailer |
Posted by: Atom - 2019-04-17, 03:21 AM - Forum: Videos and Screenshots
- No Replies
|
|
Hi all.
This is the trailer I made for one of my old projects, I had posted it in the sa-mp forum but I was erased and banned because of the youtube rule.
And I wanted to put it here because in the past it did not last long until they deleted the post xD
[Video: https://www.youtube.com/watch?v=VKkTnhBWTf4]
[Video: https://youtu.be/eyxwyUoZPOA]
The server was about a world where there was a world crisis where only the most money was kept, the media were extorted to show a false reality where everything was beautiful.
That's the message I wanted to convey in the second anchored video.
|
|
|
pawn-plus-mysql: PawnPlus extensions and helpers for the MySQL plugin |
Posted by: Graber - 2019-04-17, 01:38 AM - Forum: Libraries
- Replies (8)
|
|
pawn-plus-mysql
PawnPlus extensions and helpers for the MySQL plugin
Installation
Simply install to your project:
Code: sampctl package install AGraber/pawn-plus-mysql
Include in your code and begin using the library:
It is recommended that you set a PawnPlus version explicitely on your pawn.json (preferibly the latest) to avoid always downloading the latest one.
If you don?t use sampctl, just download the pp-mysql.inc include and drop it to your includes/ folder, and then download the PawnPlus plugin and include from here.
While you?re on it and if you don?t use PawnPlus yet, you should check it out!
Usage
All existing mysql_[t/p]query now have a variant that accepts PawnPlus strings, ending with _s (for example, mysql_tquery_s).
In addition, a new member arrives into the family of these natives: mysql_aquery[_s], which returns a task that will be set as completed when the query completes successfully, or fault when there?s an error during its execution.
Note that mysql_aquery[_s] will piggyback on mysql_tquery by default. You can override this and use mysql_tquery by setting the parallel parameter at the end to true, or explicitly change this default value by defining MYSQL_ASYNC_DEFAULT_PARALLEL to you desired value.
Examples
Using PawnPlus strings on natives
PHP Code: public OnPlayerTookAllCandies(playerid)
{
? ?mysql_tquery_s(DatabaseHandle, @("UPDATE player SET took_candies = UNIX_TIMESTAMP() WHERE id = ") % GetPlayerDBID(playerid));
}
Task-based asynchronous queries:
PHP Code: LoadPlayerData(playerid)
{
? ?await mysql_aquery_s(DatabaseHandle, @("SELECT * FROM player WHERE id = ") % GetPlayerDBID(playerid), .parallel = false);
? ?// Cache functions will be available after this
? ?cache_get_value_name_float(0, "health", PlayerHealth[playerid]);
? ?// ...
}
|
|
|
Update System [MySQL] |
Posted by: solstice - 2019-04-16, 11:34 PM - Forum: Filterscripts
- No Replies
|
|
Hey there, I decided to make my second release here and this time a filterscript (again, this is reuploaded from the SA:MP forums).
This is a filterscript I made for server owners / developers to simply add updates in-game, without having to make edits on the script multiple times.
To use it, you must clone the repository:
Commands:
-?/addupdate
-?/removeupdate
-?/updates
Credits:
Y_Less - sscanf
BlueG / maddinat0r - MySQL
Emmet_ -?easyDialog
Zeex - zcmd
Here's a list of screenshots if you want to see how it looks:
https://imgur.com/a/fdIbGIs
|
|
|
|