[Include] [I-ZCMD]Improved ZCMD - Fastest Command Processor
#21

Just use pawn states...

[EDIT] Nvm, forget about states, here is quick and dirty version to benchmark both processors (zcmd and i-zcmd)

pawn Код:
#include <a_samp>
#include <zcmd>

#define TEST1 1000
#define TEST2 10000
#define TEST3 100000
#define COMMANDS_CALLED 5


main()
{
    print("ZCMD vs I-ZCMD Benchmark");

    TestZCMD(TEST1);
    TestIZCMD(TEST1);
    TestZCMD(TEST2);
    TestIZCMD(TEST2);
    TestZCMD(TEST3);
    TestIZCMD(TEST3);
}

forward TestZCMD(tests);
public TestZCMD(tests)
{
    new time[2]; time[0]=GetTickCount();
    for(new i = 0; i < tests; i ++)
    {
        CallLocalFunction("OnPlayerCommandText", "is", INVALID_PLAYER_ID, "/somecmd");
        CallLocalFunction("OnPlayerCommandText", "is", INVALID_PLAYER_ID, "/anothercmd someparam");
        CallLocalFunction("OnPlayerCommandText", "is", INVALID_PLAYER_ID, "/verylong command with many params");
        CallLocalFunction("OnPlayerCommandText", "is", INVALID_PLAYER_ID, "/invalidcmd");//These command does not exists (we need to handle these to...)
        CallLocalFunction("OnPlayerCommandText", "is", INVALID_PLAYER_ID, "/invalidcmd with params");//These command does not exists (we need to handle these to...)
    }
    time[1]=GetTickCount();
    printf("Benchmark of %d ZCMD commands: %d", tests * COMMANDS_CALLED, time[1] - time[0]);
}

#define OnPlayerCommandText OnPlayerCommandText2
forward OnPlayerCommandText2(playerid, cmdtext[]);
#include <izcmd>

forward TestIZCMD(tests);
public TestIZCMD(tests)
{
    new time[2]; time[0]=GetTickCount();
    for(new i = 0; i < tests; i ++)
    {
        CallLocalFunction("OnPlayerCommandText2", "is", INVALID_PLAYER_ID, "/somecmd");
        CallLocalFunction("OnPlayerCommandText2", "is", INVALID_PLAYER_ID, "/anothercmd someparam");
        CallLocalFunction("OnPlayerCommandText2", "is", INVALID_PLAYER_ID, "/verylong command with many params");
        CallLocalFunction("OnPlayerCommandText2", "is", INVALID_PLAYER_ID, "/invalidcmd");//These command does not exists (we need to handle these to...)
        CallLocalFunction("OnPlayerCommandText2", "is", INVALID_PLAYER_ID, "/invalidcmd with params");//These command does not exists (we need to handle these to...)
    }
    time[1]=GetTickCount();
    printf("Benchmark of %d i-ZCMD commands: %d", tests * COMMANDS_CALLED, time[1] - time[0]);

}

//--->>> Both processors have same syntax for commands and callbacks so we define them only once...

public OnPlayerCommandPerformed(playerid, cmdtext[], success)
{
    return 1;
}

public OnPlayerCommandReceived(playerid, cmdtext[])
{
    return 1;
}

CMD:somecmd(playerid, params[])
{
    return 1;
}

CMD:anothercmd(playerid, params[])
{
    return 1;
}

CMD:verylong(playerid, params[])
{
    return 1;
}
Here are results that i get (Note they are not averaged, its just a single run)
Код:
[10:08:19] ZCMD vs I-ZCMD Benchmark
[10:08:19] Benchmark of 5000 ZCMD commands: 25
[10:08:19] Benchmark of 5000 i-ZCMD commands: 16
[10:08:19] Benchmark of 50000 ZCMD commands: 248
[10:08:19] Benchmark of 50000 i-ZCMD commands: 152
[10:08:22] Benchmark of 500000 ZCMD commands: 2462
[10:08:23] Benchmark of 500000 i-ZCMD commands: 1505
Reply
#22

Quote:
Originally Posted by Yashas
Посмотреть сообщение
I started the thread as an update for ZCMD and it ended up competing with other processors. -.-
It all started when someone's benchmarks(I-ZCMD vs y_commands) weren't matching with mine.

I had PMed Zeex and he never responded therefore I made a new thread 'I-ZCMD'.Never intended to compete with others.Moreover I named it "I-ZCMD" because it is Zeex's concept which I have just updated.



Its very hard.I still haven't figured out how to use it
I want to benchmark it with I-ZCMD.

Код:
#include <a_samp>
#include <icmd>
// MAIN:

main()
{
    CallLocalFunction("OnPlayerCommandText","is",0,"mycommand");
}
forward OnCommandCalled(playerid, cmd[]);
public OnCommandCalled(playerid, cmd[])
{
	if (!cmd_exists(cmd))
	{
		SendClientMessage(playerid, -1, "Comando inexistente.");
	    return ICMD_ERROR;
	}
	return ICMD_OKAY;
}
public Listeners(playerid, params[])
{
    get_cmd_params(mycommand)
	{
	   print("CMD CALLED");
	}
	return 0;
}
Your code is missing the flag #define ICMD_CALLBACK, to use OnCommandCalled, then you don't need to create this forward again, it already exists on inc file.
Reply
#23

Is this case-sensitive command processor?, i dont find function like tolower.
Reply
#24

Quote:
Originally Posted by ipsNan
Посмотреть сообщение
Your code is missing the flag #define ICMD_CALLBACK, to use OnCommandCalled, then you don't need to create this forward again, it already exists on inc file.
Yea, I fixed it later :P

By the way, I made a pull request at github for ICMD!!
-------------------------------------------------------------------------------------
I-ZCMD new update is 3x faster than ZCMD,5x faster than y_commands and slightly faster than ICMD

I-ZCMD (v0.2) is 2.79 times faster than ZCMD
I-ZCMD (v0.2) is 5.06 times faster than y_commands
I-ZCMD (v0.2) is 1.35 times faster than ICMD

Speed Test Code for ICMD Benchmark
Код:
#include <a_samp>


#define IZCMD

#if defined ICMD

#define ICMD_CALLBACK
#include <icmd>

public OnCommandCalled(playerid, cmd[])
{
	if (!cmd_exists(cmd))
	{
	    return ICMD_ERROR;
	}
	return ICMD_OKAY;
}

public Listeners(playerid, params[])
{
	get_cmd_params(mycommand)
	{
		return 1;
	}
	return 0;
}

#endif

#if defined IZCMD
	#include <XCMD>
	COMMAND:mycommand(playerid,params[])
	{
		return CMD_SUCCESS;
	}
	public OnPlayerCommandReceived(playerid,cmdtext[])
	{
		return 1;
	}
	public OnPlayerCommandPerformed(playerid,cmdtext[], success)
	{
		return success;
	}
#endif

main ()
{
	new a = GetTickCount();
	for(new i = 0;i < 1000000;i++)
		CallLocalFunction("OnPlayerCommandText","is",0,"/mycommand someshit");
	new b = GetTickCount();
	printf("%d",b-a);
}
Reply
#25

Quote:
Originally Posted by Kar
Посмотреть сообщение
Why 28th character, why not 31 (32nd)?
Because the function name is prefixed with "cmd_".
Reply
#26

Quote:
Originally Posted by Kar
Посмотреть сообщение
Why 28th character, why not 31 (32nd)?

Everyone knows about that issue. Everyone who uses zcmd more or less knows about it and fixed it themselves using the comment I made here

https://github.com/YashasSamaga/I-ZC...8eda081a05d37c

You fixed it by making CallLocalFunction fail? Heh. Be careful with that function. I honestly rather NOT call that function if I know its going to fail.
Why not 31? ZCMD variants (YCMD, IZCMD) prefix "cmd_" to every command function that is created so as to distinguish between commands and user created public function. And also to prevent clashes between public function identifiers. (Check the defines in the include to understand better)

Everyone is confused about the recent fix and I apologize for not stating it clearly in the commit description.

What is the issue?
OnPlayerCommandText crashes when a player uses very lengthy commands since the working algorithm tries to copy the command to an array after converting the characters to lowercase and fails since it tries to access an index which is outside the array bounds.

The algorithm stores the command name which was converted to lowercase in an internal array whose size is 32 (since identifiers in PAWN have a length limit of 32). If a player types a lengthy command (length > 28 ; command name + "cmd_" = length of 32 characters), the algorithm tries to copy the command to the array whose size is 32 which isn't sufficient and triggers the AMX's default exception handeling mechanism which in turn ends the OnPlayerCommandText function call (crashes OnPlayerCommandText).

What was the fix that was applied?
Increased an internal array size so that it can accommodate command names of 144 in size (length of the longest client message).

Does this fix the crash?
Yes.

Does this allow lengthy commands to work?
No.

So how is this a fix?
It is a fix since it solves the crash issue and lets OnPlayerCommandPerformed to be executed. The fix never intended to allow commands which have more than 28 characters to work.

Why let the CallLocalFunction fail? Why not simply stop the CallLocalFunction call?
1. No server has commands which consist more than 28 characters since the script wont compile if there were such commands.
2. The server nor the staff team of the server would ask anyone to use a command that doesn't exist. (lengthy commands)
3. The probability that someone uses a command that is very long is almost zero.
4. There are more chances of a player typing an invalid command which does not cross the character limit.

So its not worth adding protection against lengthy commands which are executed every single time for every command sent. (Did you understand now? You can stop reading: You may continue reading - (condtition)?(if true)if false)).

What were my options at the issue?
1. Add a length check before calling the command function.
Disadvantages: Waste of CPU

2. Increase the internal array length

Why I choose fix #2 over fix #1?
The answer is straight forward and simple, I did not want to add more checks to the code since it would waste CPU.

Isn't trying to call a command that doesn't exist (since its length is far more than the limit) using CallLocalFunction waste of CPU given that CallLocalFunction is a very slow function?
Yes, for cases where the command which was typed exceed the 28-character limit.

No for overall performance.

What happens if I had used fix #1?
I would be doing checks for all the commands to avoid invalid CallLocalFunction calls where the command was longer than the 28-character limit (which happens once in a blue moon - I will take a blind guess, maybe 1 in 1000000).

What happens if I had used fix #2?
IZCMD will carry out its usual command processing and will do it extremely fast.

If a command which is longer than 28 characters is sent, IZCMD will still carry out its normal processing and try to call the command function.

The last step of calling the command function will fail.

Is it worth avoiding the CLF call if the command name was very long?
No. Adding a check for that will waste CPU every single time a command is sent irrespective of if it was within the limits or not.

"Compared to the CPU wasted calling CallLocalFunction for an invalid command just once in 100000 trials is WAY WAY WAY WAY WAY less compared to the CPU wasted doing the check for legitimate commands."
Reply
#27

I know your include is all about performance but.. Wow.

Your still attempting to call CallLocalFunction which is a intensive function right? So your code would be slower if only long commands are used. (I know its rare but it happens). Because calllocalfunction will have to search through the object code and search every public function in pawn and end when none is found.
Reply
#28

Quote:
Originally Posted by Kar
Посмотреть сообщение
I know your include is all about performance but.. Wow.

Your still attempting to call CallLocalFunction which is a intensive function right? So your code would be slower if only long commands are used. (I know its rare but it happens). Because calllocalfunction will have to search through the object code and search every public function in pawn and end when none is found.
Yea, CLF is slow but who cares about the CPU wasted because of executing CLF once in 100000 trials? In fact, CLF will waste CPU every time an invalid command is sent and someone making a typo while typing a command is more probable. So trying to add a check to prevent bad CLF calls which are caused by lengthy commands after compromising the overall performance of the include is actually meaningless.

Moreover, CallLocalFunction is slow but much faster compared to the PAWN code since its execute directly in the server whereas the PAWN code is executed inside the AMX machine which in turn is executed in the server hardware.

If you are interested, here is how CLF works: Click Me

By the way I feel guilty for fooling people by claiming that IZCMD is so fast that it will boost the server performance. The reality is that the optimizations that I made in IZCMD is negligible compared to a server's overall performance even though IZCMD could be 2x,3x or 5x faster than other processors. Anyway its still a 2015 update for the outdated 2009 ZCMD and its always advisable to use updated includes.
Reply
#29

Good work Dude
Reply
#30

Undefined Symbol 'MAX_FUNC_NAME' error when iZCMD is used in case-sensitive mode fixed.

Update affects those who are using iZCMD in case-sensitive mode!

Download from Github
Reply
#31

Quote:
Originally Posted by Yashas
Посмотреть сообщение
since identifiers in PAWN have a length limit of 32
No! The limit in length of 31. 32nd this is null-symbol!
Reply
#32

https://github.com/YashasSamaga/I-ZCMD/pull/3

Looking for opinions on that. Any better name for ZALT, maybe something more intutive and pleasing? CALT?

It just helps lazy people and maybe improve the readablity by a little bit.


I have plans to make a major update on IZCMD which includes algorithim change which is faster and which allows alternate command names WITHOUT the loss of performance. This will be after a long time because I am out for a break now.
Reply
#33

I actually find this alluring, shoot me a message if you want help with this. I made a not so different command processor not long ago with minor adjustments just like yours.
Reply
#34

Quote:
Originally Posted by Strawton
Посмотреть сообщение
I actually find this alluring, shoot me a message if you want help with this. I made a not so different command processor not long ago with minor adjustments just like yours.
I was looking for opinions because I don't think its really needed. I find it more readable and neater however.

I have plans to make a complete overhaul of the processor after many months (becaz of lack of time, I can't do much nowadays) with the processer entirely written in assembly. I wish to bring yini features to IZCMD without compromising the efficieny.

The ugly side of that is that the include code gets more and more unreadable but I personally don't care since you are simply going to include the file and not edit it. Its fine as long as it works.

I don't even know if it is even wise to keep improving a command processor since it has negligible impact on overall performance.
Reply
#35

Bug Fix (case-sensitive version) & optimizations

FIX: Case-sensitive version of iZCMD was not working correctly.
CHANGE: Algorithm update for both case-sensitive and non-case-sensitive version.
REMOVED: ALS Hook for OnPlayerCommandText

You can now call OnPlayerCommandText directly without having to use CallLocalFunction.


With more minor changes & improvements.
Reply
#36

You might want to perform a speed test compared to my edition...

Difference between cmd.inc and izcmd.inc:
pawn Код:
// cmd.inc
forward OnPlayerCommandPerformed(playerid, cmdtext[], params[], response);

//izcmd.inc
forward OnPlayerCommandPerformed(playerid, cmdtext[], response);
pawn Код:
#define CMD:%1(%2) \
            forward cmd_%1(%2); public cmd_%1(%2)

public OnPlayerCommandText(playerid, cmdtext[])
{
    #if defined OnPlayerCommandRecieved
        if (!OnPlayerCommandRecieved(playerid, cmdtext))
            return 1;
    #endif

    static function[31] = "cmd_";

    for (new i = 1; ; i++)
    {
        switch (cmdtext[i])
        {
            case ' ':
            {
                if (cmdtext[(i+1)] == EOS)
                    break;

                #if defined OnPlayerCommandPerformed
                    return OnPlayerCommandPerformed(playerid, function[4], cmdtext[(i+1)], CallLocalFunction(function, "is", playerid, cmdtext[(i+1)]));
                #else
                    return CallLocalFunction(function, "is", playerid, cmdtext[(i+1)]);
                #endif
            }

            case EOS:
            {
                break;
            }

            default:
            {
                function[((i-1) + 4)] = cmdtext[i];
            }
        }
    }

    #if defined OnPlayerCommandPerformed
        return OnPlayerCommandPerformed(playerid, function[4], "\1", CallLocalFunction(function, "is", playerid, "\1"));
    #else
        return CallLocalFunction(function, "is", playerid, "\1");
    #endif
}

#if defined OnPlayerCommandRecieved
   forward OnPlayerCommandRecieved(playerid, cmdtext[]);
#endif

#if defined OnPlayerCommandPerformed
    forward OnPlayerCommandPerformed(playerid, cmdtext[], params[], response);
#endif


Let me know the results here...
Reply
#37

#1 Your processor is case-sensitive. iZCMD case-sensitive version is 7x faster than your code.

I am sure that you still don't understand the difference between using natives and writing plain PAWN code.

#2 Even if you compare your code with iZCMD non-case-sensitive (ZCMD is non-case-sensitive), yours is still slightly slower! (just 1.0097x slower but anyway its slower inspite of doing less work than iZCMD)

#3 The correct spelling of re-ce-ived is "received" not "recieved"

#4 Your code doesn't even work! Do you know what static means? The data is saved after the function ends. You are not adding a null character at the end of the "function". Shouldn't it be zero by default? No! Static arrays are global arrays with limited scope so its not filled with zeros unlike local arrays which are filled with zeros when they are added to the heap.

#5 iZCMD code is almost identical to your code. You have used a 'switch' whereas iZCMD uses 'if..else'.

And to finish the reply with another blow, if else is faster than switch in this case!

Here is your fixed code:
Код:
#define CMD:%1(%2) \
			forward cmd_%1(%2); public cmd_%1(%2)

public OnPlayerCommandText(playerid, cmdtext[])
{
    #if defined OnPlayerCommandRecieved
		if (!OnPlayerCommandRecieved(playerid, cmdtext))
		    return 1;
	#endif

	static function[31] = "cmd_";

	for (new i = 1; ; i++)
	{
	    switch (cmdtext[i])
	    {
	        case ' ':
	        {
		        if (cmdtext[(i+1)] == EOS)
		        {
		            function[((i-1) + 3)] = 0;
		            break;
		        }

				#if defined OnPlayerCommandPerformed
					return OnPlayerCommandPerformed(playerid, function[4], cmdtext[(i+1)], CallLocalFunction(function, "is", playerid, cmdtext[(i+1)]));
				#else
					return CallLocalFunction(function, "is", playerid, cmdtext[(i+1)]);
				#endif
	        }

	        case EOS:
	        {
	            function[((i-1) + 4)] = 0;
				break;
	        }

	        default:
	        {
				function[((i-1) + 4)] = cmdtext[i];
	        }
	    }
	}

	#if defined OnPlayerCommandPerformed
		return OnPlayerCommandPerformed(playerid, function[4], "\1", CallLocalFunction(function, "is", playerid, "\1"));
	#else
		return CallLocalFunction(function, "is", playerid, "\1");
	#endif
}

#if defined OnPlayerCommandReceived
   forward OnPlayerCommandReceived(playerid, cmdtext[]);
#endif

#if defined OnPlayerCommandPerformed
	forward OnPlayerCommandPerformed(playerid, cmdtext[], params[], response);
#endif
#6 If I really want to make iZCMD even faster, I can do it by making insane optimizations which murder the readablity completely.

Код:
static prefix[] = {'c', 'm', 'd'}, funcname[32] ="_";
No need for jumping between indexs if that is used ^

Can add shitton of assembly code to get rid of all redudent index calculations.

I can approximate that I can make iZCMD code 50% faster but its simply ridiculous to do all those kind of optimizations.
Reply
#38

Make the ability to add aliases for commands, please.

Код:
CMD:radio(playerid, params[])
{
	return CMD_SUCCESS;
}
ALIAS:radio("r"); // The command will be available in two forms /radio and /r
Reply
#39

Quote:
Originally Posted by Kolstin
Посмотреть сообщение
Make the ability to add aliases for commands, please.

Код:
CMD:radio(playerid, params[])
{
	return CMD_SUCCESS;
}
ALIAS:radio("r"); // The command will be available in two forms /radio and /r
I think there is already one:

Код:
COMMAND:r(playerid, params[])
{
	return cmd_radio(playerid, params);
}

COMMAND:radio(playerid, params[])
{
	// Content
	return 1;
}
Reply
#40

Quote:
Originally Posted by Yousha
Посмотреть сообщение
I think there is already one:

Код:
COMMAND:radio(playerid, params[])
{
	return cmd_r(playerid, params);
}
Lol, you guys are in store for some great stuff... Much more than just that... That's all I'm going to say!
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)