View Single Post
HamletEagle
AMX Mod X Plugin Approver
Join Date: Sep 2013
Location: Romania
Old 07-23-2019 , 12:05   Re: OciXCrom's Rank System [XP|Levels|Ranks]
Reply With Quote #292

Disregard my previous post, it was incomplete. I planned to write a more detailed explanation about new/static, but I had to go so I figured posting something is better than nothing.
I am not familiar with the pawn assembly, so I'll use x86 specific registers, but this should suffice to understand the core concepts.

new is used to create a new variable on the stack. The stack is a storage space with a LIFO policy: last in first out(the last variable added to the stack is the first to be removed). It has a base register(the base address of the stack), a stack pointer register named esp(at which address we are currently in the stack) and it grows downwards(from the base address towards 0).

Creating a new variable means allocating some space for it on the stack, which in turn means moving esp to grow the stack. Since the stack grows downwards we actually have to lower esp.
A variable has 4 bytes, therefore we subtract 4 from the esp to make room for a new variable. This is called pushing to the stack.
To delete the variable we simply have to move the esp back to what it was, so add 4 to the esp. This is called popping the stack.
When a function exits it is responsible to leave the stack in the same state as when the function was called: the function has to clean all the variables it created before finishing it's job. This is exactly why local variables are not preserved between function calls.

In pawn variables are initialized to 0, so after changing esp we have to put 0 in that memory location.
The corresponding assembly would be:
PHP Code:
sub esp//grow the stack and make room for 4 bytes - one variable
mov dword [esp], //write 0 at the address pointed by esp register. dword only tells how to write to that memory location(dword 4 bytes/word 2 bytes/byte 1 byte)

//later
add esp//clean the stack 
Moving from one variable to one array: creating/deleting is pretty much the same, but instead of 4 we have to subtract/add 4 * ARRAY_SIZE. However we have to 0 the entire array, which we can't do with a simple mov instruction(would require a loop) so the cost of creating the array mainly comes from 0'ing the memory location, not from making space for the variable.

PHP Code:
   sub esp4000 // make room for one array of size 1000
    
    
mov ecx1000 // put the size of the array in the counter register. we need this to simulate a loop(ecx - the "i" variable from an usual loop)
initialize_memory:
    
mov dword [esp ecx 4], // esp is the base address for the array. ecx will be the index and we multiply by 4 because each cell is 4 bytes to get the actual cell address
    
sub ecx// decrement "i"
    
jnz initialize_memory // if "i" is not 0 keep looping - will jump back to initialize_memory exactly like a goto + if check
    
    
add esp4000 // free the memory 
I showed the 2 assembly code just for illustration purpose, I hope it may help to understand what happens behind the scenes when declaring variables/arrays.

This has the following implications:
-if we are in a heavy function like something called each frame(a task at 1.0 seconds does not qualify for heavy function) and we have an array of size 1000 or so, we can use static to avoid 0'ing the memory every time and saving very little time. Doing this is reasonable, but not performance-critical. 256 is definitely not big enough to worry about it.
-if we have a static variable and then we always assign a new value to it that's not any better than creating the variable with "new". We used static so we no longer have the continuous initialization with 0, but we still write to that memory on each function call.

There's also one more case where static can be useful when dealing with big arrays: when we do for example new array[40000] and we get a stack error. The error says we used too much memory and filled the stack. If we use static, then the array is no longer placed on the stack, but in the "data" section along with all the global variables(because static is implemented like a global variable, only with a restricted scope).
This is just FYI, if in any case someone needs so much memory to fill the entire stack then the plugin logic may need to be changed.

Compile this:
PHP Code:
#include <amxmodx>

public plugin_init()
{
    
func()
}

func()
{
    static 
a[12345]
    static 
b[12345]
    static 
c[12345]

and look at the output from the compiler.
When using static it says: Data size: 148140 bytes(which is exactly 12345 * 3 * 4, *4 because one var = 4 bytes). The static variables were placed in the data section.

Switch static to new and recompile: Data size: 0 bytes. nothing was placed in data section because we are not using any global/static variables.

So, when do you really want to use a static variable:
-for it's indented purpose: to save the value of the variable
-in pre frame functions when the array is really big: order of thousands of cells.
-if you are going to consume all the stack space(arguably).

Wasting precious time thinking if this variable should be new or static is useless, but if you think about the 3 cases above it should be clear enough.

Hope it makes sense and it clears the topic. Feel free to ask anything.
__________________

Last edited by HamletEagle; 07-23-2019 at 12:21.
HamletEagle is offline