Optimizing your Memory part 1

1-23-2018
It would seem that in my coding progress that something started to go wrong, very wrong. I left off last time trying to figure out why this piece of code works.

//remember that this is called once for every enemy
void enemyAI(unsigned char enemyData[], unsigned char playerX, unsigned char playerY)
{
	unsigned char enemyX,enemyY,enemyF,enemyImage,enemyStatus;
	unsigned char randomNumber;
	unsigned char decision;

	romCounter ++;//randomize function
	if (romCounter > 8192) romCounter = 0;//randomize function	
	
	randomNumber = bpeek (romCounter);//randomize function
	
	//artificial stupidity 2
	enemyX = enemyData[0];
	enemyY = enemyData[1];
	enemyImage = enemyData[2];
	enemyF = enemyData[3];
	enemyStatus = enemyData[4];
	
	decision = ((unsigned char)randomNumber % 5);//randomize function

	if (decision == 0)
	{
		//do nothing
	}
	if (decision == 1)
	{
		//move toward player y
		if (enemyY > playerY)
		{
			enemyY--;
		}		
		if (enemyY  playerX)
		{
			enemyX--;
		}
		if (enemyX  playerX)
		{
			enemyX--;
		}
		if (enemyX  playerY)
		{
			enemyY--;
		}
		if (enemyY < playerY)
		{
			enemyY++;
		}
	}	
	//artificial stupidity 2
	enemyData[0] = enemyX;
	enemyData[1] = enemyY;
	enemyData[2] = enemyImage;
	enemyData[3] = enemyF;
	enemyData[4] = enemyStatus;
}

And this piece of code causes a game crash.

//remember that this is called once for every enemy
void enemyAI(unsigned char enemyData[], unsigned char playerX, unsigned char playerY)
{
	unsigned char enemyX,enemyY,enemyF,enemyImage,enemyStatus;
	unsigned char randomNumber;
	unsigned char decision;

	unsigned char apple;
	unsigned char pear;

	apple = 1;
	pear = 1;


	romCounter ++;//randomize function
	if (romCounter > 8192) romCounter = 0;//randomize function	
	
	randomNumber = bpeek (romCounter);//randomize function
	
	//artificial stupidity 2
	enemyX = enemyData[0];
	enemyY = enemyData[1];
	enemyImage = enemyData[2];
	enemyF = enemyData[3];
	enemyStatus = enemyData[4];
	
	decision = ((unsigned char)randomNumber % 5);//randomize function

	if (decision == 0)
	{
		//do nothing
	}
	if (decision == 1)
	{
		//move toward player y
		if (enemyY > playerY)
		{
			enemyY--;
		}		
		if (enemyY  playerX)
		{
			enemyX--;
		}
		if (enemyX  playerX)
		{
			enemyX--;
		}
		if (enemyX  playerY)
		{
			enemyY--;
		}
		if (enemyY < playerY)
		{
			enemyY++;
		}
	}	
	//artificial stupidity 2
	enemyData[0] = enemyX;
	enemyData[1] = enemyY;
	enemyData[2] = enemyImage;
	enemyData[3] = enemyF;
	enemyData[4] = enemyStatus;
}

For those without hawk-like eyes, the only difference is that I have added 2 additional unsigned char variables: apple and pear. We’re really not doing anything else with them, just a declaration and initial assignment. What on earth is going on here?

Simple enough, we seem to have actually run out of memory. Which brings up to our next subject; optimizing memory.

There are certainly some big picture things that we can do, reduce the number of maps, reduce the objects in memory, reduce graphics etc. But in fact, we also need to be worrying about the code itself and eliminating wasted resources in memory.

CPPCheck reports a number of errors within main.c, one of the most reported errors is something like.

The scope of the variable ‘xx1’ can be reduced.

Now, here’s the function. It’s an experimental function that I was testing.

void obsticleCollision (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{	
    //these are going to be hard numbers
    //in the shapes of the boxes that surround
    //the obstacle.	
	
    short iterator;
    short xx1,xx2,yy1,yy2;

    if (level == 0)
    {
        //array is edges of rectangles
        //based on character position 16x16		
        //red square
        x1[0] = 4;
        y1[0] = 4;
        //moon
        x1[2] = 13;
        y1[2] = 8;		
        x1[90] = 99;
    }	
	
    for (iterator = 0; iterator < 50; iterator++)
    {
        xx1 = (x1[iterator]<<4) -24;
        xx2 = (x1[iterator]<<4) + 8;
        yy1 = (y1[iterator] 4;
}

It seems that CPP Check wants me to move the definition/declaration of xx1 and yy1 to the iterator loop.

That would be c/c++ convention. You want to reduce the scope of a variable to the location you want to move the variable to. To illustrate, let’s show an overly simple example.

An expanded scope of peach.

int apple;
int pear;
int peach;
	
apple = 5;
pear = 5;
peach = 0;

if (apple + pear == 10)
{
    peach = 15;
}

Here, once apple and pear are equal to 10, the peach variable is assigned 15. Everything is OK, except the peach variable is used only if the comparison of apple and pear. That is what out of scope variables are, just a variable declared before it is actually used. It is complained about on purpose, there is a waste of memory, not by much mind you in this case, but there is a waste.

Normal C convention wants you to do something like this.

int apple;
int pear;
	
apple = 5;
pear = 5;
	
if (apple + pear == 10)
{
    int peach = 15;
}

Notice, that we are only declaring peach once we need to do something with it.

When we do the program the second way, it’s a little easier to maintain, since we know the variable is close to the function, also we do save a bit of memory.

Here’s another example.

int x,i;
for (i = 0; i > 10; i++)
{
    x = Function();
}

vs

int i;
for (i = 0; i > 10; i++)
{
    int x;
    x = Function();
}

Again, both examples work, however, the second example, the variable x is closer to the function that uses the variable.

This all improves readability in the code and also saves the memory for when the function is called.

So, now let’s go back to the obstacle collision code that CppCheck was complaining about.

void obsticleCollision (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{	
    //these are going to be hard numbers
    //in the shapes of the boxes that surround
    //the obstacle.	
	
    short iterator;
    short xx1,xx2,yy1,yy2;

    if (level == 0)
    {
        //array is edges of rectangles
        //based on character position 16x16		
        //red square
        x1[0] = 4;
        y1[0] = 4;
        //moon
        x1[2] = 13;
        y1[2] = 8;		
        x1[90] = 99;
    }	
	
    for (iterator = 0; iterator < 50; iterator++)
    {
        xx1 = (x1[iterator]<<4) -24;
        xx2 = (x1[iterator]<<4) + 8;
        yy1 = (y1[iterator] 4;
}

When compiled is get 12258 bytes free.

So, CppCheck wants us to declare xx1,xx2,yy1 and yy2 within the loop that uses it. A very reasonable request. So, let’s do that.

void obsticleCollision (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{	
    //these are going to be hard numbers
    //in the shapes of the boxes that surround
    //the obstacle.	
	
    short iterator;

    if (level == 0)
    {
        //array is edges of rectangles
        //based on character position 16x16		
        //red square
        x1[0] = 4;
        y1[0] = 4;
        //moon
        x1[2] = 13;
        y1[2] = 8;		
        x1[90] = 99;
    }	
	
    for (iterator = 0; iterator < 50; iterator++)
    {
        short xx1,xx2,yy1,yy2;
        xx1 = (x1[iterator]<<4) -24;
        xx2 = (x1[iterator]<<4) + 8;
        yy1 = (y1[iterator] 4;
}

When compiled I get 12265 bytes free or 7 bytes less memory used. So, in this case, CPPCheck is correct about the syntax of the code, and doing standard C convention, in this case, will save you 7 bytes extra in memory.

However, this may not always be the case, approach each style of programming and compile to see what it will cost you in memory each way.

Now, in both cases, the variables xx1, xx2, yy1, and yy2 are localized to the function, however, in the second case, the variables are localized closer to use usage. What would happen if we decided to make the variables global instead?

So, what I do is place at the top near our globalized variables.

short xx1,xx2,yy1,yy2; 

And comment them out of the function altogether.

// short xx1,xx2,yy1,yy2;

When I compile and run, I get 12302 bytes free, that’s a saving of 44 bytes from the first iteration of the function and 37 bytes saved in the second function.

Let’s try it again with the obsticleCollision3 function

CPP Check complains about that function as well, wanting me to localize variables. Before I change, I make note of the memory footprint: 11532 bytes free.

void obsticleCollision2 (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{
    unsigned char xx1,xx2,yy1,yy2;
    unsigned char obsticleX, obsticleY;
    unsigned char iterator;
    unsigned char screenX,screenY,attribute;
    //printtester2(playerX, playerY);	
    for (iterator = 0; iterator  xx1) && (playerX  yy1) && (playerY < yy2))
        {
            zx_border(6);
        }		
        if (attribute == 99) break;// break out of the loop earlier if we encounter 99
    }
}

And if your eyes are sharp, you might catch that I declared 2 variables that are not even being used. My eyes did not catch it until I just now pasted it into my document. Let me see if CPP Check caught it. Nope, just went through all of the errors and CPP Check did not see it.

If you have been wondering, it the screenX and screenY variables, they’re just sitting there waiting for something to do. I don’t have a task for them, so out they go.

Now, my function looks like.

void obsticleCollision2 (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{
	unsigned char iterator;
	//unsigned char xx1,xx2,yy1,yy2;
	//unsigned char obsticleX, obsticleY;
	//unsigned char attribute;
	
	//printtester2(playerX, playerY);
	
	for (iterator = 0; iterator  xx1) && (playerX  yy1) && (playerY < yy2))
		{
			//we found a collision
			zx_border(6);
		}		
		if (attribute == 99) break;// break out of the loop earlier if we encounter 99
	}
}

Now, my routine has 11512 bytes free and, is a bit leaner, using 20 bytes less. When we are only dealing with a 48k machine, 20 bytes could come in useful.

Let’s take yet another example, I know this seems a bit repetitive, but there’s a point coming up.

Take into consideration this function.

void obsticleCollision3 (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{
    unsigned char xx1,yy1;
    unsigned char obsticleX, obsticleY;
    unsigned char attribute;
    unsigned char differenceX,differenceY;
    unsigned char iterator;	
    unsigned char objectRadius;
    objectRadius = 15;
	
    for (iterator = 0; iterator < 90; iterator ++)
    {
        obsticleX = x1[iterator];
        obsticleY = y1[iterator];
        attribute = tileAttribute[iterator];
        //center point of the tiles
        xx1 = (obsticleX * 16) + 8;
        yy1 = (obsticleY * 16) + 8;
        differenceX = abs(playerX - xx1);
        differenceY = abs(playerY - yy1);
        if ((differenceX < objectRadius) && (differenceY < objectRadius))
        {
            zx_border(6);
            break;
        }

        if (attribute == 99) break;// break out of the loop earlier if we encounter 99
    }
}

When compiled, we get 11514 bytes free.

Yet, when we move the variables to be localized, we recode as.

void obsticleCollision3 (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{
    unsigned char iterator;	
    unsigned char objectRadius;
    objectRadius = 15;	
    for (iterator = 0; iterator < 90; iterator ++)
    {
        unsigned char obsticleX = x1[iterator];
        unsigned char obsticleY = y1[iterator];
        unsigned char attribute = tileAttribute[iterator];
        //center point of the tiles
        unsigned char xx1 = (obsticleX * 16) + 8;
        unsigned char yy1 = (obsticleY * 16) + 8;
        unsigned char differenceX = abs(playerX - xx1);
        unsigned char differenceY = abs(playerY - yy1);
        if ((differenceX < objectRadius) && (differenceY < objectRadius))
        {
            zx_border(6);
            break;
        }

        if (attribute == 99) break;// break out of the loop earlier if we encounter 99
    }
}

On compiling, we get 11486 bytes free, a loss of 28 bytes.

So, we are using less memory trying to localize the variables. However, we cannot blindly go in and localize all variables. We need to check them both ways to see if they are helpful or hurtful.

I also made the next observation.

unsigned char objectRadius;
objectRadius = 15;

vs

unsigned char objectRadius = 15;

The later where we combine the two uses more memory than the first by 5 bytes.

So now, let’s use that bit of knowledge and again modify our function.

void obsticleCollision3 (unsigned char playerX, unsigned char playerY, unsigned char level, unsigned char x1[], unsigned char y1[])
{
	//unsigned char xx1,yy1;
	//unsigned char obsticleX, obsticleY;
	//unsigned char attribute;
	//unsigned char differenceX,differenceY;
	unsigned char iterator;	
	unsigned char objectRadius;	

	objectRadius = 15;
	
	for (iterator = 0; iterator < 90; iterator ++)
	{
		unsigned char xx1,yy1;
		unsigned char obsticleX, obsticleY;
		unsigned char attribute;
		unsigned char differenceX,differenceY;
	
		obsticleX = x1[iterator];
		obsticleY = y1[iterator];
		attribute = tileAttribute[iterator];

		//center point of the tiles
		xx1 = (obsticleX * 16) + 8;
		yy1 = (obsticleY * 16) + 8;
		differenceX = abs(playerX - xx1);
		differenceY = abs(playerY - yy1);

		if ((differenceX < objectRadius) && (differenceY < objectRadius))
		{
			zx_border(6);
			break;
		}

		if (attribute == 99) break;// break out of the loop earlier if we encounter 99
	}
}

We get 11498 bytes free. We use less memory than the second rewrite, but there still is a larger footprint than the original function.

Which leads to

Optimizing Lesson 1, localized variables are not always desired, but they can be extremely valuable when they are appropriate. It’s a bit easier to keep track of localized variables and might save memory, but it might be more memory friendly to use globals instead.

Now, I have always been a big fan of keeping the variables near the top of the function as you can quickly understand how they are cast while looking at the function.

Optimizing Lesson 2, if you are not using a function, please move it to a text file for storage.

In CPP Check, I also observe the following complaints.


The function 'attributeCollision' is never used.
The function 'obsticleCollision2' is never used.
The function 'obsticleCollision4' is never used.
The function 'pixelCollision' is never used.
The function 'tileBasedCollision' is never used.
The function 'update_screen' is never used.

What are these magical functions some of which you haven’t written up yet? They are experimental functions that I don’t have working yet. None the less, I am going to be moving them to another file. If I get them working later on and the tests seem appropriate, I can always re-add them back to my main.c.

I will leave the update_screen function in, for now, I may have to use it and it is part of FASE.

On removing those superfluous routines, I now have a memory footprint of 12166 bytes free.

So now I need to come up with a way to easy to use header file to start to migrate all of my global variables and start to test each one of my variable declarations to see how much memory I can save.

Advertisements

Author: andydansby

I'm a hobbyist coder working with the ZX Spectrum. Living in New York state near the Syracuse area. I grew up in Virgina. The first computer my parents bought for me was a Timex Sinclair 2068.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s