GP2X Writing Fast Code For The Gp2x ..


Epicenter

Well-Known Member
Joined
Oct 9, 2005
Messages
2,068
Age
40
Location
USA
Website
www.epicgaming.us or http
I used to have a PDF document covering all sorts of specific ways to optimize C to run faster on ARM9 processors-- for example, running for() loops to count DOWN from their final value toward 0 rather than climbing UP from 0 to their final value (number of iterations). But, I seem to have misplaced it. Has anyone seen this who might know where to find it again?

Also, what are the fundamentals of obtaining maximum speed in a graphically intense program on the GP2x? So far, it's quite obvious to use hardware surfaces on everything possible that is creating heavy load to draw on the display, and to use 16-bit surfaces for everything with Paeryn's HW-SDL lib, also to use an integer-based OGG decoding library (naturally) .. are there any other major tricks to obtain maximum speed? Given the intensity of the game I am developing, every little drop of performance I can squeeze from the 920T would be beneficial.

Thanks for any tips. :)
 
Epicenter posted on Mar 21 2006 at 03:18 AM said:
I used to have a PDF document covering all sorts of specific ways to optimize C to run faster on ARM9 processors-- for example, running for() loops to count DOWN from their final value toward 0 rather than climbing UP from 0 to their final value (number of iterations). But, I seem to have misplaced it. Has anyone seen this who might know where to find it again?

Also, what are the fundamentals of obtaining maximum speed in a graphically intense program on the GP2x? So far, it's quite obvious to use hardware surfaces on everything possible that is creating heavy load to draw on the display, and to use 16-bit surfaces for everything with Paeryn's HW-SDL lib, also to use an integer-based OGG decoding library (naturally) .. are there any other major tricks to obtain maximum speed? Given the intensity of the game I am developing, every little drop of performance I can squeeze from the 920T would be beneficial.

Thanks for any tips. :)

Have you seen my demo what I have done for the coding competition?
(I should improve it already - lazy bastard of me...)

It's just software - no hardware tricks at all. I had to experiment much with Arm assembler to get to such speeds. But it payed of.

Basically when doing anything do it always using 32 bit intengers. Never resort to "unsigned char *" for pixel access even at 8bpp. The Arm's bus is 32 bit wide and memory also is 32 bit wide. Using bytes or short words is wasting bandwith of these and cpu in the GP2x can move around 150MB/s of data by itself. That's using ldm/stm opcodes and when you need pixel granularity of one it's always better to mask it manually than let compiler do it.

The another important point is to use most of registers whenever possible. There are lots of cycles to use. They are cheap comparably to memory acesses. For an example with clock of 200MHz cpu should be able to push 800MB/s but it only can 150MB/s (or 50MB/s using ldr/str instead of ldm/stm). Therefore assuming that you have to load/process/store pixel data the cpu is 10 times faster than memory. With hand coded assembled it's lots of resources.

So when doing 2D graphics sacrificing cpu cycles for less frequent memory read/writes is alright. It's better to compute a constant than load it from memory (in reasonable limits - I'm talking about series of mov and orr with immediate arguments not computing the sinus values for an example).

When doing multiple playfields there is another trick (was used extensively in my demo) - read many times but write to the framebuffer only once. That way for 4 playfields you have to make only 5 memory accesses (4 reads of playfields and only final write to the framebuffer) where with "classical aproach" you will have to do 12 memory acesses (1 - read source, 2 - read fb for possible masking, 3 - write results back to the fb - multiply that for numbers of playfields). It possible when combining playfields' pixels in registers simultaneously.
 
Last edited by a moderator:
There was a hand optimsed memcpy for the GP32, will that work on the gp2x too (I guess so)? will it benfit also from that one?
 
pea posted on Mar 21 2006 at 09:41 AM said:
Here it is: snippet: unknown author - Fast assembler memcpy routine
Not specific to gp32, so if you can get it to compile, it should work fine. In my tests it was around 17 to 18 times faster than standard IIRC.

Yes, in GP32 the performance of this code was not good, but it really flies in GP2X...

I have used memcpy.S in MAME GP2X 1.9. There are also more optimizations: memset.S, memcmp.S, etc (i got them from ucLib, i will add them in next MAME GP2X version).
 
Last edited by a moderator:
Well, rewriting some major parts of my engine (the ones that run every frame most notably, especially loops) using the optimizations noted in the document from ARM caused some incredibly dramatic improvements. Far more than I expected; the same test run on the engine in its previous iteration ran at ~5 FPS. The same test, and subsequently more stressful ones, using the new optimized functions runs at a sustainable 30 FPS+. I'm very impressed.

I'd highly advise everyone developing for the '2x in C take a good long look at this document.... the most significant gains I've had so far seem to have come from running loops backward from their goal value to 0 rather than incrementing from 0 up, and storing global variables' contents in new local variables within functions if the value will be used many times in the function, so that the value can reside in a register on the ARM9 rather than in main memory which is far slower to access.

Example: My engine has a function that performs an operation on an array of objects every frame-- a rather large array of objects. By reversing the direction the loop that checks through them all runs, and mirroring the variables it uses over 200 times a frame in the function rather than just globally, it now runs at least 6 times faster than it did before.
 
Epicenter posted on Mar 28 2006 at 01:46 PM said:
the most significant gains I've had so far seem to have come from running loops backward from their goal value to 0

I considered re-writing some of my loops like this until I recalled that I was already using -funroll-loops as a compiler option.

It produces larger binaries, but if speed is your most important issue, I'd recommend using the loop unrolling over any other fixed-size loop optimization.

Of course, if you're talking about a loop that will run through a variable number of iterations, then I do believe your re-writing would help!
 
Last edited by a moderator:
Franxis posted on Mar 23 2006 at 02:20 PM said:
pea posted on Mar 21 2006 at 09:41 AM said:
Here it is: snippet: unknown author - Fast assembler memcpy routine
Not specific to gp32, so if you can get it to compile, it should work fine. In my tests it was around 17 to 18 times faster than standard IIRC.

Yes, in GP32 the performance of this code was not good, but it really flies in GP2X...

I have used memcpy.S in MAME GP2X 1.9. There are also more optimizations: memset.S, memcmp.S, etc (i got them from ucLib, i will add them in next MAME GP2X version).

would you please provide them to us, too?
 
Last edited by a moderator:
fishybawb posted on Mar 29 2006 at 07:54 PM said:
Just download the latest MAME source ;)

Yeah :D

More tricks:

- Use unsigned types if possible (operations with signed types are slower).

- If variables are used extensively, don't use static variables in a module, use global variables. Access to static variables is slower.

- Use static __inline functions were possible.

- Compile with -mcpu=arm920 -mtune=arm920t -std=c99 -O3 -fstrict-aliasing -fexpensive-optimizations -falign-functions -fweb -frename-registers -fomit-frame-pointer -ffast-math -finline -finline-functions -fno-builtin -fno-common -mstructure-size-boundary=8 -msoft-float -pedantic
NOTE: -msoft-float -ffast-math is for better math performance.

- Link with -s for smaller and faster executable (no debug)...

- Local variables are put in registers by the compiler. But global variables or function parameters doesn't.

- Avoid real types operations.

- Avoid divide operand.

- Loops decreasing until 0.

- Try to use >> and << operands to do multiplications and divides by constants multiple of 2.
 
Last edited by a moderator:
Franxis posted on Mar 29 2006 at 04:10 PM said:
- Try to use >> and << operands to do multiplications and divides by constants multiple of 2.

My C is rusty, but wouldn't that be used for constants that are powers of two?
 
Last edited by a moderator:
GeminiDomino posted on Apr 4 2006 at 09:55 AM said:
My C is rusty, but wouldn't that be used for constants that are powers of two?
Yes.

You can lose accuracy too with larger bitshifts, but they are such cheap operations (free on the arm in some situations it seems) that thay are still incredibly useful.
 
Last edited by a moderator:
If variables are used extensively, don't use static variables in a module, use global variables. Access to static variables is slower.

I declare in modules globals vars as static to control the scope. Does this has any negative penalties?

Local variables are put in registers by the compiler. But global variables or function parameters doesn't.

That's not true, the ARM doc above says that up to four function parameters go into registers, that's why they recommend using functions with up to four parameters.

-std=c99 -pedantic
you seem to like pain! :)
 
On a kind of related note, does anyone know what the increase in overhead would be when code is written in C++ vs "plain" C ? Or is this negligible/not the case? I'm just assuming that there probably needs to be RTTI lookups performed in addition to method calls so this would slow things down somewhat?

Tony
 
RTTI isn't used for everyday method calls - it's only needed if you want to dynamic_cast or do explicit type lookups yourself. I don't think it slows down code that doesn't use it, and if you want to be sure, you can turn it off on the command line.

Non-virtual class methods generally cost about the same as a function call with one extra parameter. Virtual methods cost a bit more, both in terms of cache misses (the vtable is generally stored away from the class data) and indirect calls (pipelined CPUs suffer a lot from this, because they can't prefetch instructions from the target function until they know where it is). I don't know which of these are relevant on ARM9.

Exception handling tends to be more of a hit in C++ than RTTI management - RTTI bloats the code a bit, but exception handling bloats every function that can create objects on its stack, and makes all such function calls somewhat slower IIRC. Essentially the problem is that if an exception is triggered, the function needs to be able to destruct objects appropriately before returning due to the exception.
 
You can lose accuracy too with larger bitshifts, but they are such cheap operations (free on the arm in some situations it seems) that thay are still incredibly useful.

This may be a dumb question, but how do you lose accuracy with a bit shift compared to integer division/multiplication? Don't they both result in truncation of any fractional part of the result?

Also, how does bitshifting work for signed numbers on the GP2X processor? Is there anything special I need to worry about in the negative case?
 
synkro posted on Apr 4 2006 at 10:25 AM said:
Local variables are put in registers by the compiler. But global variables or function parameters doesn't.
That's not true, the ARM doc above says that up to four function parameters go into registers, that's why they recommend using functions with up to four parameters.
Function parameters != globals. So yes up to 4 parameters (r0-r3) should be ok to use.
Bitshifting for negative numbers are done with ASR (arithmic shift right), just remember that you if you shift -1 >>1 you will still get -1 and not 0.
 
Last edited by a moderator:
Back
Top