GP32 Compiler / C optimization question..


RobertG

Member
Joined
Apr 15, 2003
Messages
188
The 6809 CPU core from the Vectrex emulator I'm porting has some code that I think may be slowing it down, something like:-

switch(number)
{
case 0: case1: case 2: case 3: case 4:
case 5: case6: case 7: case 8: case 9:
case 10: case11: case 12: case 13: case 14:
case 15: case16: case 17: case 18: case 19:
case 20: case21: case 22: case 23: case 24:
case 25: case26: case 27: case 28: case 29:

/*do some stuff */
break;

case 30: case 31: case 32....


/*and so on*/

}

I'm wondering how the compiler will handle this switch statement.. I'm a lousy asm programmer, so bare with me, but I'm guessing that a switch statement is normally compiled into something a little like this:-

--*pseudo assembly*--
mov r0, number
mov r1, switchstatement
mov r2, [r0 + r1]

switchstatement:
@dcd 1,2,3,4,5,6

1:
;code here

2:
;code here


..etc

Which is fine and dandy. As far as I can tell the switch statement from the Vectrex emulator can be assembled to one of two things:-

1) it can be expanded out, eg:-

case 1: case 2: case 3:
/* code here */
break;

becomes:-

1:
;code here

2:
;code here (same code as 1)

3:
;code here (same code as 1 and 2)


Which will result in a massive jump table, and so a bigger binary, but the performance will be alright (right?).


2) something like:-

mov r0, number
cmp r0, $1
beq 1

cmp r0, $2
beq 1

cmp r0, $3
beq 1

1:
;code here


...Which would be a performance nightmare!

So any ideas on how the compiler would handle this, whether it's worth changing, etc? :)

Thanks

Robert
 
Generally a good compiler makes good code out of that.. basically cases till the first break are handled as one code and one compare.. possible holes may require few additional compares. Switches generate usually a mixture of few compares and jump tables. A long run of compares also may use binary search instead of linear compare code.

I wouldn't recommend to change that switch code as switch-case code without breaks is a known old trick to generate faster code i.e. just think about scanline polyfiller with this technique ;)
 
Thanks Mr.spiv :)

By the way this code is to handle addressing modes, it goes something like this:-

void fetchexe(void)
{
op=ram[pc]

switch(op)
{
case 1:
addressingmode=getaddressingmode();
do some other stuff
break;

case 2:
addressingmode=..... and so on...
break;
}
}

int getaddressingmode()
{
op=ram[pc++];
switch(op)
{
case 1:case2:case3... etc..
}
}



Would you recommend getting rid of the getaddressingmode function and hardcoding the addressing mode handling into each function?

That would get rid of the overhead from the switch, function call, etc...


I'm beginning to think that the main bottleneck is the Vectrex graphics hardware emulation, I wonder if I could intercept the bios calls and "HLE" it.. God, that's going to be a nightmare ;)
 
Change the getaddressingmode function to a macro, it's the same as hardcoding it - oh I'm sure you already know that. Anyways I would suggest that.
 
Rico,

if I change it to macro wouldn't it still have to go through the switch statement?

I.e

#define getaddressmode() switch(whatever){\
case 1:case 2:.... etc..


..be pretty much the same as inlineing it?
 
It's exactly the same a inlining it, all it does is increase the binary size and speed it up a very small amount. But I wasn't exactly sure what you were doing, you mentioned 'hard-coding' so I assumed that.
 
Rico, the addressingmode function is doing something like this:-


switch(opcode)
{
0:
dosomething;
break;

1:
dosomethingelse;
break

etc..
}

void fetchexe()
{
opcode=ram[pc];
switch(opcode){
case 0:
blah=getaddressingmode();
some other stuff;
case 1:
blah=getaddressingmode();
more stuff;
}
}


what I meant by "hardcoding" the addressing mode stuff is replacing the above "fetchexe" function with something like:-

void fetchexe()
{
opcode=ram[pc];
switch(opcode){
case 0:
some addressing mode stuff for opcode 0;
some other stuff;
case 1:
some addressing mode stuff for opcode 1;

more stuff;
}
}


So that it cuts out the function call (although the function call is already inlined), and also cuts out the switch statement.
 
I think that would be a good idea, it's similar to unrolling the loops. If you can't see other ways to optimise it, I think for the filesize increase this would prove quite effective. Two switches on similar variables may be a little redundant.

So go for it :p
 
RobertG,

Using some massive switch statement to handle different opcodes (I assume this is what you are doing) is extremely inefficient when making an emulator -- even though it is what is done 99% of the time ;)

I would recommend doing the following bit of code:

Code:
typedef void (*opcodeFunctionPtr)( void );

void fnMov() {
  // TODO: emulate MOV instruction
}

void fnLdx() {
  // TODO: emulate load X register
}

const opcodeFunctionPtr[] = {
  fnMov, // 1
  fnLdx, // 2
  // etc...
};

void EmulateOpcode( int opcode ) {
  opcodeFunctionPtr[opcode]();
}

Done. And very fast, with no crazy case statements (which can be compiled differently -- either tables or even ifs).

Hope this helps. :D

And if you aren't writing an emulator, sorry for the crazy off-topic post...

Jeff
 
Jma,

This is a emulator I'm porting, so the cpu core isn't mine. Personally if I was writing from scratch I would have used your method :) ;)

It's the method I used in my Javascript chip8 emulator :)

var opcodes=new Array();
opcodes[whatever]=new Function(whatever);
opcodes[oooh!!]=new Function(aaaaah);


Hardcore stuff ;)

I first saw this technique done in a 6502 cpu core, and have used it ever since.. I think it also makes the code alot more manageable.
 
Back
Top