Joseph Adams
Joseph Adams

Reputation: 982

Java local vs instance variable access speed

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

Answers (7)

HesNotTheStig
HesNotTheStig

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

Joseph Adams
Joseph Adams

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

Hot Licks
Hot Licks

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

Joni
Joni

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

Boann
Boann

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

aasu
aasu

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

Niels Bech Nielsen
Niels Bech Nielsen

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

Related Questions