What I draw from it is when not messing with arrays (array bounds checking).
Every case is better than.
JVM-L,
Do these tests with times make sense?
package ArrayTests;
public class ArrayVsClass {
public static void main(String[] args) {
long lVectorStartTime = System.currentTimeMillis();
int iterations = Integer.MAX_VALUE;
while (iterations-- > 0) {
// int iterations2 = 10;
//while (iterations2-- > 0)
{
//testArray(); // ARRAY
// vs
//testGETFIELD(); // GETFIELD
// vs
testIBean(); // INVOKE INTERFACE
// vs
//testBean(); // INVOKE VIRTUAL
// vs
//testABean(); // INVOKE VIRTUAL POSSIBLY THROW
// vs
//testSlots(); // INVOKE FOR AVALUE
}
}
long lVectorRunTime = System.currentTimeMillis() - lVectorStartTime;
System.out.println("Bench time: " + lVectorRunTime);
}
// SLOTS time: 33157,33250,33156
public static void testSlots() {
ClassWithSlots oneSlot = new ClassWithSlots(6);
int iterations = Integer.MAX_VALUE;
long result = 0;
while (iterations-- > 0) {
result += oneSlot.getValue();
}
}
// Array time: 18438,18437,18422
public static void testArray() {
final long[] accessArray = new long[] { 6 };
int iterations = Integer.MAX_VALUE;
long result = 0;
while (iterations-- > 0) {
result += accessArray[0];
}
}
// GETFIELD time: 14688,14531,14453
public static void testGETFIELD() {
ClassWithOneSlot oneSlot = new ClassWithOneSlot(6);
int iterations = Integer.MAX_VALUE;
long result = 0;
while (iterations-- > 0) {
result += oneSlot.slot;
}
}
// INVOKE VIRTUAL time: 14750,14594,14719
public static void testBean() {
ClassWithOneSlot oneSlot = new ClassWithOneSlot(6);
int iterations = Integer.MAX_VALUE;
long result = 0;
while (iterations-- > 0) {
result += oneSlot.getValue();
}
}
// INVOKE INTERFACE time: 14469,14610,14859
public static void testIBean() {
IBeanWithOneSlot oneSlot = new ClassWithOneSlot(6);
int iterations = Integer.MAX_VALUE;
long result = 0;
while (iterations-- > 0) {
result += oneSlot.getValue();
}
}
// INVOKE VIRTUAL POSSIBLY THROW time: 14641,14594,14547
public static void testABean() {
AClassWithOneSlot oneSlot = new ClassWithOneSlot(6);
int iterations = Integer.MAX_VALUE;
long result = 0;
while (iterations-- > 0) {
result += oneSlot.getValue();
}
}
static interface IBeanWithOneSlot {
public long getValue();
}
static class ClassWithOneSlot extends AClassWithOneSlot implements IBeanWithOneSlot {
final public long slot;
ClassWithOneSlot(long s) {
slot = s;
}
@Override
final public long getValue() {
return slot;
}
}
static class ClassWithSlots {
final public long[] slots = new long[1];
ClassWithSlots(long s) {
slots[0] = s;
}
final public long getValue() {
return slots[0];
}
}
static abstract class AClassWithOneSlot implements IBeanWithOneSlot {
@Override
public long getValue() {
throw new NullPointerException();
}
}
}
Some comments inline below.
On Sun, Nov 1, 2009 at 7:48 PM, <logi...@gmail.com> wrote:
> public class ArrayVsClass {
>
> public static void main(String[] args) {
> long lVectorStartTime = System.currentTimeMillis();
> int iterations = Integer.MAX_VALUE;
> while (iterations-- > 0) {
> // int iterations2 = 10;
> //while (iterations2-- > 0)
> {
> //testArray(); // ARRAY
> // vs
> //testGETFIELD(); // GETFIELD
> // vs
> testIBean(); // INVOKE INTERFACE
> // vs
> //testBean(); // INVOKE VIRTUAL
> // vs
> //testABean(); // INVOKE VIRTUAL POSSIBLY THROW
> // vs
> //testSlots(); // INVOKE FOR AVALUE
> }
> }
> long lVectorRunTime = System.currentTimeMillis() - lVectorStartTime;
> System.out.println("Bench time: " + lVectorRunTime);
>
> }
Because of optimization effects, you should try running them all
together in the same benchmark in varying orders. Short benchmarks
like these can skew actual results because only a small subset of the
full code needs to be considered for optimization.
> // SLOTS time: 33157,33250,33156
> public static void testSlots() {
> ClassWithSlots oneSlot = new ClassWithSlots(6);
...
> // Array time: 18438,18437,18422
> public static void testArray() {
> final long[] accessArray = new long[] { 6 };
Not too surprising; you're paying the cost of the array plus the cost
of the virtual invocation. So even after inlining, you've got
something like boundscheck + deref + virtual call.
> // GETFIELD time: 14688,14531,14453
> public static void testGETFIELD() {
> ClassWithOneSlot oneSlot = new ClassWithOneSlot(6);
...
> // INVOKE VIRTUAL time: 14750,14594,14719
> public static void testBean() {
> ClassWithOneSlot oneSlot = new ClassWithOneSlot(6);
This is exactly the pattern we use in JRuby for heap-based scopes. We
have from ZeroVarDynamicScope up to FourVarDynamicScope and then it
falls over into an array-based version. Because we can statically tell
how many variable slots we'll need in most Ruby scopes, this ended up
being a big perf improvement for us.
> // INVOKE INTERFACE time: 14469,14610,14859
> public static void testIBean() {
> IBeanWithOneSlot oneSlot = new ClassWithOneSlot(6);
>
> int iterations = Integer.MAX_VALUE;
> long result = 0;
> while (iterations-- > 0) {
> result += oneSlot.getValue();
> }
> }
invokeinterface ends up as fast as invokevirtual once it's been
inlined, so this is no surprise.
> // INVOKE VIRTUAL POSSIBLY THROW time: 14641,14594,14547
> public static void testABean() {
> AClassWithOneSlot oneSlot = new ClassWithOneSlot(6);
Exception-handling paths not followed do not impact performance, so
this is also not surprising. Try having one out of the N invocations
trigger the exception and watch the perf change drastically from then
on.
- Charlie