GP2X Optimizing A Routine For Speed


JonHolland

Still Fresh
Joined
Dec 17, 2006
Messages
26
Today I spent some time creating some primitive graphic functions using SDL, here is a quick code snippet:

Code:
/* GFX_Rect (SDL_Surface, X, Y, X2, Y2, Color)

Usage: Draws a hollow rectangle in specified color on specified Surface

SDL_Surface:		  The Surface to draw on
X,Y:				  Starting Coordinates
X2,Y2:				Ending Coordinates
Color:				Color value in hexidecimal (or Use SDL_MapRGB)

Example:
		
GFX_Rect (surface,0,0,100,100,SDL_MapRGB(surface->format,0,255,0));

Draws a green box that is 100x100.

*/

void GFX_Rect (SDL_Surface *surface, int x, int y, int x2, int y2, Uint32 color)
{
	 
	 // Draw the top and bottom of the Rect
	 
	 GFX_HLine (surface,x,y,x2,color);		  // Top
	 GFX_HLine (surface,x,y2,x2,color);		 // Bottom
	 
	 // Draw the sides
	 
	 GFX_VLine (surface,x,y,y2,color);		  // Left Side
	 GFX_VLine (surface,x2,y,y2,color);		 // Right Side
	 
}

GFX_HLine and GFX_VLine are both simple for/next loops. My thought that a faster version of the above function would be this:

Code:
void GFX_FastRect (SDL_Surface *surface, int x, int y, int x2, int y2, Uint32 color)
{
	 int iCount; 
	 
	 // Draw Top and Bottom Lines
	 
	 for (iCount = x; iCount <= x2; iCount++)
	 {
		 GFX_PutPixel(surface,iCount,y,color);
		 GFX_PutPixel(surface,iCount,y2,color);
	 }
	 
	 // Draw Left and Right Sides
	 
	 for (iCount = y; iCount <= y2; iCount++)
	 {
			GFX_PutPixel(surface,x,iCount,color);
			GFX_PutPixel(surface,x2,iCount,color);
	 }
}

In this function, the only calls to other functions is to PutPixel (I suppose I could further speed it up by using a macro for putpixel instead of a function) and is using 2 loops instead of four.

However, all that said, the increase in speed is very small, and the function is slightly harder to read, and a good bit larger in size.

My few Questions:
  • How many CPU cycles are used when you call a function.
  • Are For loops slower than while loops?
  • How would you further optimize this function just for kicks?
 
Jonathan Holland posted on Dec 22 2006 at 02:51 AM said:
In this function, the only calls to other functions is to PutPixel (I suppose I could further speed it up by using a macro for putpixel instead of a function) and is using 2 loops instead of four.

However, all that said, the increase in speed is very small, and the function is slightly harder to read, and a good bit larger in size.

My few Questions:
  • How many CPU cycles are used when you call a function.
  • Are For loops slower than while loops?
  • How would you further optimize this function just for kicks?

Off the top of my head:
  • If the putpixel isn't inline, it's going to kill you.
  • Understanding and using pointers for this kind of thing will gain you a lot of speed
  • Loops are faster if they decrement on the ARM (free comparison with zero, effectively) ... but (on the horizontal draws) if the area is cached and buffered you probably want to write to incrementing addresses
For the function, if it inlined then it just puts the content of that function where you call it. If it's not inlined it's marshalling 4 things into the bottom 4 registers (they should be there already if the compiler's not insane) and stacking a register then going to the function.

You're also drawing the corners twice (y and y2), so there's a tiny optimisation to your code already ;)
 
Last edited by a moderator:
Optimisation is another one of those 80-20 rules, 20% of the code takes 80% of the processing. It's better to go for the easier way to start with, then see if there's a problem. Ideally you then profile the code to see where the real bottlenecks are. If you optimise upfront, chances are you are optimising in the 80% that takes 20% of the processing.

You will probably want to look at the assembler the compiler has produced, it's not always obvious what's going to produce less instructions.

For kicks, If you want to optimise the horizontal line drawing, and you are using less than a 32 bit surface e.g. 16 bit, you might want to use a 32 bit value set 2 pixels at a time.
 
Optimization is simply a means to an end. I'd go as far as coding the whole project in Python and then if I can't get it fast enough, rewriting it in C or whatever. I've had too many projects that went nowhere simply because I kept re-optimizing the same thing, over and over. You have to accept it will never be the fastest way of doing it.

You'd be surprised how easy it is to rewrite a program than to write it, even if you didn't write the original and you don't have the code. It helps to know where your going and how to get there, in other words the internal structure behind the program.

It is good to get it *working* and then make it faster if need be. I've noticed at least with my latest project that I may have changed and at some points rewritten the implementation for certain parts of my code entirely, but I've barely touched the structure of things once I had decided upon it.

Then again, regarding interface/structure, its good to design with optimization in mind. There are ways of structuring certain things that make it near impossible to optimize. But the right way of structuring something is usually obvious, so far I've only once had to restructure part of something entirely. At least I got it right the second time. And afterwards it seemed so obvious!

Good luck on your adventures in coding. I wish I wasn't tired of my own code...I want to get it out by Christmas.
 
To have fast rectangles, a call to the blitter is a must, it's faster than the fastest pixels-by-pixels algorithm. See the GP2X wiki and the harware optimised SDL to do that.
 
From a function call point of view the calling convention is like this:

1) The first four arguments are in registers r0 - r3 assuming they are 32-bits or less. Any arguments after that are pushed on the stack. This means from a calling point of view functions with 4 or fewer arguments are more efficient to call than ones with more arguments. Of course this is the calling effientcy not the functions itself.

2) Arguments and return values that are shorts or chars are not as efficient as one that are integer or long (32-bit registers) because of extra code to "narrow" the arguments in the calling code and/or the called code. The load and store machine instructions can extend (to 32-bit or 16-bit) or shorten (to 16-bit or 8 bit) for free as aprt of the load or store instruction.

Loop overhead is about 4 cycles so every reduction of a loop cycle saves you 4 cycles. This "unrolling" of loops is generally only useful in critical loops since the unrolling makes the code bigger. Count down loops with a termination of != 0 or == 0 are more efficient because the comparison to zero is generally free (Z bit in processor status register). If the loop count can not be zero then a do {} while loop is n=more efficient that a for () {} loop.

All of these are general guide lines and should not be taken as absolutes for every situation. Hope this helps ...

Almost all of this can be found in the "ARM System Developer's Guide" ISBN 1-55860-874-5. There are things in this book I disagree with but most of the information is good.
 
There are tons of ways you can optimize stuff. At the very least I would inline the pixel writing function like previously said.
Then there are subtle optimizations which will make almost no difference, like
Code:
for (iCount = ++y; iCount < y2; iCount++)
for the second loop.
 
Back
Top