Java local vs instance variable access speed

大憨熊 提交于 2020-01-18 04:58:24

问题


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?


回答1:


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



回答2:


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.)




回答3:


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.




回答4:


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.




回答5:


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.




回答6:


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.




回答7:


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..



来源:https://stackoverflow.com/questions/21613098/java-local-vs-instance-variable-access-speed

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!