Monday, January 10, 2005

Java Tip #2 - Contiguous Switches

When coding a switch statement, use contiguous ranges even if they are dummies. The compiler can optimize switches that use contiguous ranges. If you decompile to bytecode using DeCafe or similar, you SHOULD see the "tableswitch" bytecode where this switch begins. If you see "lookupswitch", it will use a O(n) lookup instead of directly jumping to the statement. I've seen nice performance increases using this technique for large switch statements.

Here is an example class with three methods. The method switchTest1 has contiguous values for 1-5. The method switchTest2 has values for 1 and 3-5 and the final switchTest3 has values for 1,3,4, and 128. This is a nonsensical class, but does show the behavior.

The results are that:
1) switchTest1 uses the desired "tablelookup".
2) switchTest2 is modified by the compiler to add the missing "2" to the default case and thus uses the desired "tablelookup" bytecode.
3) switchTest2 is too complicated for the compiler to resolve, so it falls back to the "lookupswitch" bytecode.

Below is a decompile with bytecode annotations from DeCafe of a 1.4.2 built class. Need to test this in 1.5.


public class SwitchTest
{

public SwitchTest()
{
// 0 0:aload_0
// 1 1:invokespecial #9
// 2 4:return
}

public long switchTest1(int n)
{
long result = 0L;
// 0 0:lconst_0
// 1 1:lstore_2
switch(n)
//* 2 2:iload_1
{
//* 3 3:tableswitch 1 5: default 69
// 1 36
// 2 41
// 3 48
// 4 55
// 5 62
case 1: // '\001'
result = 1L;
// 4 36:lconst_1
// 5 37:lstore_2
break;

//* 6 38:goto 74
case 2: // '\002'
result = 4L;
// 7 41:ldc2w #16
// 8 44:lstore_2
break;

//* 9 45:goto 74
case 3: // '\003'
result = 9L;
// 10 48:ldc2w #18
// 11 51:lstore_2
break;

//* 12 52:goto 74
case 4: // '\004'
result = 16L;
// 13 55:ldc2w #20
// 14 58:lstore_2
break;

//* 15 59:goto 74
case 5: // '\005'
result = 25L;
// 16 62:ldc2w #22
// 17 65:lstore_2
break;

//* 18 66:goto 74
default:
result = n * n;
// 19 69:iload_1
// 20 70:iload_1
// 21 71:imul
// 22 72:i2l
// 23 73:lstore_2
break;
}
return result;
// 24 74:lload_2
// 25 75:lreturn
}

public long switchTest2(int n)
{
long result = 0L;
// 0 0:lconst_0
// 1 1:lstore_2
switch(n)
//* 2 2:iload_1
{
//* 3 3:tableswitch 1 5: default 62
// 1 36
// 2 62
// 3 41
// 4 48
// 5 55
case 1: // '\001'
result = 1L;
// 4 36:lconst_1
// 5 37:lstore_2
break;

//* 6 38:goto 67
case 3: // '\003'
result = 9L;
// 7 41:ldc2w #18
// 8 44:lstore_2
break;

//* 9 45:goto 67
case 4: // '\004'
result = 16L;
// 10 48:ldc2w #20
// 11 51:lstore_2
break;

//* 12 52:goto 67
case 5: // '\005'
result = 25L;
// 13 55:ldc2w #22
// 14 58:lstore_2
break;

//* 15 59:goto 67
case 2: // '\002'
default:
result = n * n;
// 16 62:iload_1
// 17 63:iload_1
// 18 64:imul
// 19 65:i2l
// 20 66:lstore_2
break;
}
return result;
// 21 67:lload_2
// 22 68:lreturn
}

public long switchTest3(int n)
{
long result = 0L;
// 0 0:lconst_0
// 1 1:lstore_2
switch(n)
//* 2 2:iload_1
{
//* 3 3:lookupswitch 4: default 70
// 1: 44
// 3: 49
// 4: 56
// 128: 63
case 1: // '\001'
result = 1L;
// 4 44:lconst_1
// 5 45:lstore_2
break;

//* 6 46:goto 75
case 3: // '\003'
result = 9L;
// 7 49:ldc2w #18
// 8 52:lstore_2
break;

//* 9 53:goto 75
case 4: // '\004'
result = 16L;
// 10 56:ldc2w #20
// 11 59:lstore_2
break;

//* 12 60:goto 75
case 128:
result = 16384L;
// 13 63:ldc2w #30
// 14 66:lstore_2
break;

//* 15 67:goto 75
default:
result = n * n;
// 16 70:iload_1
// 17 71:iload_1
// 18 72:imul
// 19 73:i2l
// 20 74:lstore_2
break;
}
return result;
// 21 75:lload_2
// 22 76:lreturn
}
}

1 Comments:

Anonymous Anonymous said...

Actually, that's not true :) Lookupswitches and Tableswitches are all internally within the VM done by a binary search tree (true as of Java6 b55 or so). The newer builds of Java6 use jumptables and do a simple lookup/jump and are faster.

9:09 AM  

Post a Comment

<< Home