Some Array vs GETFILED access times

1 view
Skip to first unread message

logi...@gmail.com

unread,
Nov 1, 2009, 8:48:14 PM11/1/09
to armedbe...@common-lisp.net, armedbea...@lists.sourceforge.net, jvm-la...@googlegroups.com
ABCL,
For implementing MOP-like objects these times are something to consider.

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();
}

}
}

Charles Oliver Nutter

unread,
Nov 1, 2009, 11:47:04 PM11/1/09
to jvm-la...@googlegroups.com
None of your results are too surprising to me. In JRuby, we moved
several structures from encapsulating an array to using
differently-sized "slotted" objects, with performance registering much
better as a result. Some of that was due to the lack of boundschecks
on the arrays, but another portion was probably due to the reduced
size of an object + reference versus object + reference + array.

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

Daniel Hicks

unread,
Nov 19, 2009, 10:02:06 PM11/19/09
to JVM Languages
The SLOTS case is a little surprising, in that the call should have
been quickly inlined and reduced to the array addressing case. The
array-addressing case is also a bit surprising -- a good JIT should
have been able to pull the array bounds check out of the loop,
reducing the loop body to the equivalent of the getfield case.

I'm not sure what you were trying to test with the exception case --
exceptions only become a factor if they prevent some JIT optimization,
and nothing there that would do that.

A good JIT can flatten the invokeinterface case pretty well, though in
more complex scenarios there might have to be a check that the actual
class is the same as last time. (A little surprised that the JIT you
used did so well on this case but not so well on the array case.)

segoe

unread,
Nov 20, 2009, 3:49:54 PM11/20/09
to JVM Languages
I'm not sure what you were trying to test with the exception case --
exceptions only become a factor if they prevent some JIT optimization,
and nothing there that would do that.

My experience is that exceptions kill performance from the point
the first exception is raised, and it is really noticeable. In my
language
(ast interpreter, lisp-like), i've tried to add a debugger with stack
traces/restarts based
on exceptions.

Things went like this: (server VM)
(fib 30)

naive, no exceptions: 0.484 msec
exceptions catched, but none thrown: 0.515 msec
after the first 3 or 4 exceptions: 0.720 msec (not kidding)

After that point, the performance suffered consistently without
mattering
if exceptions were anymore thrown or not.




John Cowan

unread,
Nov 20, 2009, 9:01:35 PM11/20/09
to jvm-la...@googlegroups.com
On Fri, Nov 20, 2009 at 3:49 PM, segoe <se...@hotmail.es> wrote:

> My experience is that exceptions kill performance from the point
> the first exception is raised, and it is really noticeable. In my
> language
> (ast interpreter, lisp-like), i've tried to add a debugger with stack
> traces/restarts based
> on exceptions.

If you are using exceptions as restarts, then you should override
fillInStackTrace() in those classes to do nothing. That's the real
time-killer.

>
> Things went like this: (server VM)
> (fib 30)
>
> naive, no exceptions: 0.484 msec
> exceptions catched, but none thrown: 0.515 msec
> after the first 3 or 4 exceptions: 0.720 msec (not kidding)
>
> After that point, the performance suffered consistently without
> mattering
> if exceptions were anymore thrown or not.
>
>
>
>
> --
>
> You received this message because you are subscribed to the Google Groups "JVM Languages" group.
> To post to this group, send email to jvm-la...@googlegroups.com.
> To unsubscribe from this group, send email to jvm-language...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/jvm-languages?hl=.
>
>
>



--
GMail doesn't have rotating .sigs, but you can see mine at
http://www.ccil.org/~cowan/signatures

Daniel Hicks

unread,
Nov 21, 2009, 8:13:28 AM11/21/09
to JVM Languages
Good point! I never would have thought of that.

segoe

unread,
Nov 21, 2009, 12:34:39 PM11/21/09
to JVM Languages
You probably made my day.
I'll test and report later.

On 21 nov, 03:01, John Cowan <johnwco...@gmail.com> wrote:
> On Fri, Nov 20, 2009 at 3:49 PM, segoe <se...@hotmail.es> wrote:
> > My experience is that exceptions kill performance from the point
> > the first exception is raised, and it is really noticeable. In my
> > language
> > (ast interpreter, lisp-like), i've tried to add a debugger with stack
> > traces/restarts based
> > on exceptions.
>
> If you are using exceptions as restarts, then you should override
> fillInStackTrace() in those classes to do nothing.  That's the real
> time-killer.
>
>
>
>
>
> > Things went like this: (server VM)
> > (fib 30)
>
> > naive, no exceptions: 0.484 msec
> > exceptions catched, but none thrown: 0.515 msec
> > after the first 3 or 4 exceptions: 0.720 msec (not kidding)
>
> > After that point, the performance suffered consistently without
> > mattering
> > if exceptions were anymore thrown or not.
>
> > --
>
> > You received this message because you are subscribed to the Google Groups "JVM Languages" group.
> > To post to this group, send email to jvm-la...@googlegroups.com.
> > To unsubscribe from this group, send email to jvm-language...@googlegroups.com.
> > For more options, visit this group athttp://groups.google.com/group/jvm-languages?hl=.

Rémi Forax

unread,
Nov 21, 2009, 12:58:55 PM11/21/09
to segoe, jvm-la...@googlegroups.com
You can also read that blog:
http://blogs.sun.com/jrose/entry/longjumps_considered_inexpensive

R�mi

Le 21/11/2009 18:34, segoe a �crit :

segoe

unread,
Nov 21, 2009, 1:13:53 PM11/21/09
to JVM Languages
Unfortunately there is no change and times remain the same. (latest
JDK)

segoe

unread,
Nov 21, 2009, 2:05:48 PM11/21/09
to JVM Languages

Ok, got it to work. There is still some overhead but is minimal.
It seems the trick is not only to null fillInStackTrace() but also
caching exceptions
making sure to reuse the same instance.

logi...@gmail.com

unread,
Nov 21, 2009, 2:59:04 PM11/21/09
to jvm-la...@googlegroups.com
In a lisp interpreter we got amazing time increase in our catch/throw benchmarks
like 100(0?)s times faster.

Question:

@Override
public Throwable fillInStackTrace()
{
return this;
}

Is returning 'null' a better idea than 'this' ?

segoe

unread,
Nov 21, 2009, 4:43:10 PM11/21/09
to JVM Languages
It doesn't seem to make a difference here.
Reply all
Reply to author
Forward
0 new messages