15.04.2015, 18:29
(
Последний раз редактировалось corne; 03.01.2017 в 21:58.
)
OK, this is something which many many people get wrong all the time. I'll explain this in one line:
99% of the time you do not need, and should not use, PVars
To explain this I'm going to have to explain a bit about regular variables and a bit about PVars:
Regular Variables
Regular variables are stored within the memory space of your PAWN script. When you use a varaible the PAWN script sees an address - this is where the data is, it loads the data in this address and uses that for whatever you were using the varaible for. If you write to a variable the script again, at the point where you placed the varaible, has the memory address to put the data in.
That's it - it's very fast because the script sees an address and loads/stores the data at that point.
PVars
Here is what happens when you load a PVar:
The script loads your player variable (see above) to see which player to load the data for.
The script loads another varaible which is actually the string name of the PVar. Note that this is how the compiler stores strings - they are all placed in the same area of memory and the main code simply contains a pointer to the right address (making the code basically identical to that for loading varaibles).
These values are pushed on to the stack and the relvant PVar load function is called.
Inside this load function the name is looked up from a table (I'm not sure what lookup system is used, but for speed I would hope hash maps). When the passed name is found in this map (as the key) the value stored there (maps relate "keys" to "values", so you can essentially index anything with anything else) is loaded.
This value is returned as the PVar value for the player (note that strings are even harder). Writing is essentially the same, but writing to the value instead of loading it.
Arrays
The main item of code against which PVars compete is arrays. Arrays are done in a manner very similar to variables, but with an extra step. First, the index is loaded (this may be another variable, a calculation, or even another array element). This index is added on to the base address of the array and that address is loaded as a normal variable.
Use
I hope from that explanation alone it is obvious which is the slowest method for storing and loading data (I'll give you a hint - it's not variables or arrays). I don't have any timings, but none are needed to prove that given that most PVar calls actually contain a varaible load within them, meaning that they can't possibly be faster.
So when should you use them?
There are three valid use-cases I can think of (you may have more):
Not
The most glaring ommision from the list above is user data. If a player logs in to your server and you load their data from a file, most of it is known in advance (in terms of what data is being loaded). This means you can set in an enum what data will be loaded in advance, so there's no need to use the dynamic features of PVars to allocate the memory. Speaking of memory...
Memory
This is an argument thrown around a lot, which is simply wrong. The bottom line is that PVars are considerably worse than arrays for memory. Firstly the data being stored is the same size regardless of where it is, so that instantly puts both systems on an equal footing. In addition to this PVars need to store the name of the variable (often twice - once in your mode and once in the lookup table) and I'm sure there is additional lookup table overheads.
Some people state that PVars are useful if you only have a few players, but if that's the case why have "MAX_PLAYERS" set to 500? Grow your server with you community - yes you will want some leeway in your array sizes for growth, but if you maximum player count is 20, you're unlikely to get 480 new players super fast. In fact growing your server with your community is good advice regardless of what you are thinking about.
If you have a well defined "MAX_PLAYERS" macro and your server is relatively full, arrays will use less memory that PVars. If your server is in a lull then you may be using slightly more memory to store the currently unused varaibles, but given that most modes are less that 1% of available RAM, why does that matter so much to you?
Reset
"PVars reset when a player joins or leaves". If this is your main reason for using PVars then that's frankly just lazy! Yes, they do reset automatically, but IMHO that's not enough of a reason on it's own to use them - especially given how easy resetting a variable is*.
* Also, I'm working on a new library that will address this anyway.
Conclusion
I am no complaining about PVars - as I said they have their uses, but people need to understand when they should be used and that they are not a total replacement for normal arrays, not even close!
Comment from Kalcor
99% of the time you do not need, and should not use, PVars
To explain this I'm going to have to explain a bit about regular variables and a bit about PVars:
Regular Variables
Regular variables are stored within the memory space of your PAWN script. When you use a varaible the PAWN script sees an address - this is where the data is, it loads the data in this address and uses that for whatever you were using the varaible for. If you write to a variable the script again, at the point where you placed the varaible, has the memory address to put the data in.
That's it - it's very fast because the script sees an address and loads/stores the data at that point.
PVars
Here is what happens when you load a PVar:
The script loads your player variable (see above) to see which player to load the data for.
The script loads another varaible which is actually the string name of the PVar. Note that this is how the compiler stores strings - they are all placed in the same area of memory and the main code simply contains a pointer to the right address (making the code basically identical to that for loading varaibles).
These values are pushed on to the stack and the relvant PVar load function is called.
Inside this load function the name is looked up from a table (I'm not sure what lookup system is used, but for speed I would hope hash maps). When the passed name is found in this map (as the key) the value stored there (maps relate "keys" to "values", so you can essentially index anything with anything else) is loaded.
This value is returned as the PVar value for the player (note that strings are even harder). Writing is essentially the same, but writing to the value instead of loading it.
Arrays
The main item of code against which PVars compete is arrays. Arrays are done in a manner very similar to variables, but with an extra step. First, the index is loaded (this may be another variable, a calculation, or even another array element). This index is added on to the base address of the array and that address is loaded as a normal variable.
Use
I hope from that explanation alone it is obvious which is the slowest method for storing and loading data (I'll give you a hint - it's not variables or arrays). I don't have any timings, but none are needed to prove that given that most PVar calls actually contain a varaible load within them, meaning that they can't possibly be faster.
So when should you use them?
There are three valid use-cases I can think of (you may have more):
- Sharing data - As I'm sure you mostly all know, PVars are shared between gamemodes and filterscripts so can be used to pass data between them. However, properties and "CallRemoteFunction" can also be used to share data, so they may not even be the best way of sharing data.
- Sparse arrays - A "sparse array" is an array which may well often have most of it's values empty. This is the only time I have used PVars. In the YSI text draw library you can show a TD to a player for a set time (like the time in Game Texts) - this means that every player needs to have a timer for every text draw to know when to hide it. However, given that there is a high chance that TDs won't be on timers and given the fact that this would require an array of several million cells just to store handles to a few timers (even if all TDs are on timers, no player will have all the TDs shown at once and some may have none on display). In this case I used PVars to store the timer handles as the memory is dynamically allocated.
- Dynamic Memory - It may be the case that you need to store data on an unknown number of items. An example of this could be a "spawn" command, which takes two parameters - one a model ID and one a count. When a player executes this command the given object is spawned the given number of times.
Not
The most glaring ommision from the list above is user data. If a player logs in to your server and you load their data from a file, most of it is known in advance (in terms of what data is being loaded). This means you can set in an enum what data will be loaded in advance, so there's no need to use the dynamic features of PVars to allocate the memory. Speaking of memory...
Memory
This is an argument thrown around a lot, which is simply wrong. The bottom line is that PVars are considerably worse than arrays for memory. Firstly the data being stored is the same size regardless of where it is, so that instantly puts both systems on an equal footing. In addition to this PVars need to store the name of the variable (often twice - once in your mode and once in the lookup table) and I'm sure there is additional lookup table overheads.
Some people state that PVars are useful if you only have a few players, but if that's the case why have "MAX_PLAYERS" set to 500? Grow your server with you community - yes you will want some leeway in your array sizes for growth, but if you maximum player count is 20, you're unlikely to get 480 new players super fast. In fact growing your server with your community is good advice regardless of what you are thinking about.
If you have a well defined "MAX_PLAYERS" macro and your server is relatively full, arrays will use less memory that PVars. If your server is in a lull then you may be using slightly more memory to store the currently unused varaibles, but given that most modes are less that 1% of available RAM, why does that matter so much to you?
Reset
"PVars reset when a player joins or leaves". If this is your main reason for using PVars then that's frankly just lazy! Yes, they do reset automatically, but IMHO that's not enough of a reason on it's own to use them - especially given how easy resetting a variable is*.
* Also, I'm working on a new library that will address this anyway.
Conclusion
I am no complaining about PVars - as I said they have their uses, but people need to understand when they should be used and that they are not a total replacement for normal arrays, not even close!
Comment from Kalcor
Quote:
That list of points that Garsino posted was originally written by me as an explanation for why the PVar system was created.
And that's exactly where the idea for PVars came from. Pawn properties have a big limitation though: you can't easily use them as arrays. In SA-MP's case, you can't easily bind them to a particular player id. And even if you could, you would have to write extra code to reset these every time a player joined/parted the server. The real power of PVars is that it makes dealing with player data extremely simple. When a player joins the game and logs in, you can get a list of their PVars from a database or file and load them. During gameplay you can update their PVar values, which might include things like player stats etc and use those values for your script logic. You can even add new PVars to their list during gameplay. When the player exits the server, you can dump all of their PVars (I created an example function Util_CreatePVarList() in test_cmds.pwn, which comes with the server) back to the file or database. Now you have a system for persistent player data storage which requires a lot less code than creating fake structs with enums or constantly querying a database. Not all data in your script is player-specific. In these cases you have no use for PVars. PVars weren't actually designed for that purpose. PVars were created to store player-specific information that you would want to save and share across scripts. Anything related to player stats is a good example. There is something missing though. Even though I created a function to dump the list of a player's PVars to a string, I didn't create the opposite function to load the player's PVars from a string. The whole system might have made a little bit more sense if I had done that. There is a performance tradeoff for using PVars over arrays in pawn, but that tradeoff is nothing compared to what would happen if you had a poorly designed player data storage system that relied on querying a database or reading from files. If you look back at the some of the first SA-MP scripts that had persistent player data, like GF, you'll see that using fake structs and .INI files eventually becomes a big mess which requires a lot of extra script just to store and retrieve player information. If you want to add something new to the player data, you have to go and update the enum, update the .INI data saving/loading procedures, make sure that information is reset when the player join/exits the server. PVars can make the above very easy. Are your PVars loaded/saved when the player logs in and leaves the server? Want to create a new experience level? pawn Код:
pawn Код:
|