Java本地vs实例变量访问速度

Jos*_*ams 12 java variables optimization performance

所以我的问题是关于Java中的变量访问速度.今天在我的"CS"(如果你可以称之为)中,老师提出了类似下面的列表示例:

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
    // ...
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:电话head.setNext(head)会慢dummy.setNext(dummy)吗?即使它不明显.我很想知道这个,因为head很明显,类的实例变量和虚拟是本地的,所以本地访问会更快吗?

Jos*_*ams 20

好的,我已经编写了一个微基准测试(由@Joni和@MattBall建议),这里是每个本地变量和一个实例变量的1 x 1000000000次访问的结果:

Average time for instance variable access: 5.08E-4
Average time for local variable access: 4.96E-4
Run Code Online (Sandbox Code Playgroud)

每次10 x 1000000000次访问:

Average time for instance variable access:4.723E-4
Average time for local variable access:4.631E-4
Run Code Online (Sandbox Code Playgroud)

对于100 x 1000000000访问每个:

Average time for instance variable access: 5.050300000000002E-4
Average time for local variable access: 5.002400000000001E-4
Run Code Online (Sandbox Code Playgroud)

因此,似乎局部变量访问确实比实例var访问更快(即使两者都指向同一个对象).

注意:我不想找到这个,因为我想要优化的东西,这只是纯粹的兴趣.

PS这是微基准的代码:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Joseph:我认为你从这些数字中得出了完全错误的结论.什么是数字实际上表明这一点的是:**有两个选择之间的性能没有显著差异**您的测量值之间的差异是如此的渺小,这是很好任何基准的误差范围内. (10认同)
  • 应该注意的是,如果存在差异,这种小的"微不足道"的事情(如缓存对齐)可能会导致结果失败. (3认同)

Hot*_*cks 14

通常,对(this对象的)实例变量的访问需要aload_0(加载this到堆栈顶部)后跟getfield.引用局部变量只需aload_n要将值从堆栈中指定的位置拉出.

此外,getfield必须引用类定义以确定值的存储位置(偏移量).这可能是几个额外的硬件指令.

即使使用JITC,本地引用(通常为零/一个硬件操作)也不太可能比实例字段引用(必须至少一个操作,可能是2-3)慢.

(并不是说这一点很重要 - 两者的速度都很好,差异只会在非常奇怪的情况下变得很重要.)


aas*_*asu 7

就像在评论中一样,我认为所花费的时间不一样.我认为你可能指的是Java SE代码库中更好的例证.例如,在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 */
    }
}
Run Code Online (Sandbox Code Playgroud)

在上面的代码中,value是一个实例变量,并且由于存在一个while 循环 ,其中value将要访问各个元素,因此它们将它从堆中引入堆栈(局部变量),从而进行优化.

您还可以查看Jon Skeet,Vivin和其他几个人在此答案中分享的知识.

  • 这是一个有趣的观点,它表明实际上存在差异.虽然这个字符串代码可能很旧,但可能会有一些JIT编译器没有在编写时没有进行的优化. (2认同)
  • 当仅为此优化设置本地时,我想知道有多少访问值得(与已经拥有本地的 OP 不同)。 (2认同)