Reputation: 982
So my question is about variable accessing speed in Java. Today in my "CS" (if you can call it that) the teacher presented a similar example to the following of a List:
public class ListExample<T> {
private Node<T> head;
private Node<T> tail;
private class Node<T> { /* ... */ }
public void append(T content) {
if (!isEmpty()) {
Node<T> dummy = new Node<T>(content);
head = dummy;
tail = dummy;
head.setNext(head);
// or this
dummy.setNext(dummy);
} else { /* ... */ }
}
// more methods
// ...
}
My question is: Would the call to head.setNext(head)
be slower than dummy.setNext(dummy)
? Even if it's not noticeable.
I was wondering this since head
is obviously and instance var of the class and dummy is local, so would the local access be faster?
Upvotes: 20
Views: 12673
Reputation: 566
I can assure you that whatever performance gains one might gain from this will be offset by the headache of looking at confusingly written code. Let the compiler figure this out. I will concede that all things being equal, the local variable is probably slightly faster, if only because there are fewer bytecode instructions involved. However, who is to say that future versions of the JVM won't change this?
In short, write code that is easy to read first. If, after that, you have a performance concern, profile.
Upvotes: -1
Reputation: 982
Ok, I've written a micro-benchmark (as suggested by @Joni & @MattBall) and here are the results for 1 x 1000000000 accesses for each a local and an instance variable:
Average time for instance variable access: 5.08E-4
Average time for local variable access: 4.96E-4
For 10 x 1000000000 accesses each:
Average time for instance variable access:4.723E-4
Average time for local variable access:4.631E-4
For 100 x 1000000000 accesses each:
Average time for instance variable access: 5.050300000000002E-4
Average time for local variable access: 5.002400000000001E-4
So it seems that local variable accesses are indeed faster that instance var accesses (even if both point to the same object).
Note: I didn't want to find this out, because of something I wanted to optimize, it was just pure interest.
P.S. Here is the code for the micro-benchmark:
public class AccessBenchmark {
private final long N = 1000000000;
private static final int M = 1;
private LocalClass instanceVar;
private class LocalClass {
public void someFunc() {}
}
public double testInstanceVar() {
// System.out.println("Running instance variable benchmark:");
instanceVar = new LocalClass();
long start = System.currentTimeMillis();
for (int i = 0; i < N; i++) {
instanceVar.someFunc();
}
long elapsed = System.currentTimeMillis() - start;
double avg = (elapsed * 1000.0) / N;
// System.out.println("elapsed time = " + elapsed + "ms");
// System.out.println(avg + " microseconds per execution");
return avg;
}
public double testLocalVar() {
// System.out.println("Running local variable benchmark:");
instanceVar = new LocalClass();
LocalClass localVar = instanceVar;
long start = System.currentTimeMillis();
for (int i = 0 ; i < N; i++) {
localVar.someFunc();
}
long elapsed = System.currentTimeMillis() - start;
double avg = (elapsed * 1000.0) / N;
// System.out.println("elapsed time = " + elapsed + "ms");
// System.out.println(avg + " microseconds per execution");
return avg;
}
public static void main(String[] args) {
AccessBenchmark bench;
double[] avgInstance = new double[M];
double[] avgLocal = new double[M];
for (int i = 0; i < M; i++) {
bench = new AccessBenchmark();
avgInstance[i] = bench.testInstanceVar();
avgLocal[i] = bench.testLocalVar();
System.gc();
}
double sumInstance = 0.0;
for (double d : avgInstance) sumInstance += d;
System.out.println("Average time for instance variable access: " + sumInstance / M);
double sumLocal = 0.0;
for (double d : avgLocal) sumLocal += d;
System.out.println("Average time for local variable access: " + sumLocal / M);
}
}
Upvotes: 24
Reputation: 47759
In general, an access to an instance variable (of the this
object) requires an aload_0
(to load this
to the top of the stack) followed by getfield
. Referencing a local variable requires only the aload_n
to pull the value out of its assigned location in the stack.
Further, getfield
must reference the class definition to determine where in the class (what offset) the value is stored. This could be several additional hardware instructions.
Even with a JITC it's unlikely that the local reference (which would normally be zero/one hardware operation) would ever be slower than the instance field reference (which would have to be at least one operation, maybe 2-3).
(Not that this matters all that much -- the speed of both is quite good, and the difference could only become significant in very bizarre circumstances.)
Upvotes: 16
Reputation: 111389
From a micro architecture perspective, reading a local variable may be cheaper because it's likely in a register or at least in the CPU cache. In general reading an instance variable may cause an expensive cache miss. In this case though the variable was just written, so it will likely be in the cache anyway. You could write a micro benchmark to find if there's any difference.
Upvotes: 4
Reputation: 50061
I think using dummy
might be at the very most, 1 cycle faster, assuming it was left in a register, but it depends on the specific CPU architecture, and what setNext
looks like, and the JVM you're using, and it's really unpredictable how the code might look once in its final JIT'd form. The JVM could potentially see that head == dummy, and if so, the executed code for both cases would be identical. This is much, much too tiny a case to worry about.
Upvotes: -1
Reputation: 472
Like in the comments, I don't think there's difference in the time taken. I think what you might be referring to is better exemplified in Java SE codebase. For example, in java.lang.String
:
public void getBytes(int srcBegin, int srcEnd, byte dst[], int dstBegin) {
//some code you can check out
char[] val = value;
while (i < n) {
dst[j++] = (byte)val[i++]; /* avoid getfield opcode */
}
}
In the above code, value
is an instance variable and since there was a while
loop where individual elements of value
were going to be accessed, they brought it from the heap to the stack (local variable) thus optimizing.
You can also check out knowledge shared by Jon Skeet, Vivin and few others on this answer.
Upvotes: 10
Reputation: 4859
When in doubt look at the byte code generated
public void append(java.lang.Object);
Code:
0: new #2; //class ListExample$Node
3: dup
4: aload_0
5: aload_1
6: invokespecial #3; //Method ListExample$Node."<init>":(LListExample;Ljava/lang/Object;)V
9: astore_2
10: aload_0
11: aload_2
12: putfield #4; //Field head:LListExample$Node;
15: aload_0
16: aload_2
17: putfield #5; //Field tail:LListExample$Node;
20: aload_0
21: getfield #4; //Field head:LListExample$Node;
24: aload_0
25: getfield #4; //Field head:LListExample$Node;
28: invokevirtual #6; //Method ListExample$Node.setNext:(LListExample$Node;)V
31: aload_2
32: aload_2
33: invokevirtual #6; //Method ListExample$Node.setNext:(LListExample$Node;)V
36: return
}
Either you get aload followed by getfield or 2 x aload. Seems to me they would be identical..
Upvotes: -2