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

2022-09-02 05:31:51

所以我的问题是关于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
    // ...
}

我的问题是:呼叫会比慢吗?即使它不明显。我想知道这一点,因为显然是类的实例var和虚拟是本地的,所以本地访问会更快吗?head.setNext(head)dummy.setNext(dummy)head


答案 1

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

Average time for instance variable access: 5.08E-4
Average time for local variable access: 4.96E-4

对于每个 10 x 1000000000 次访问:

Average time for instance variable access:4.723E-4
Average time for local variable access:4.631E-4

对于每个 100 x 10000000000 次访问:

Average time for instance variable access: 5.050300000000002E-4
Average time for local variable access: 5.002400000000001E-4

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

注意:我不想找出来,因为我想优化一些东西,这只是纯粹的兴趣。

附言:这是微基准测试的代码:

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

通常,对(对象的)实例变量的访问需要一个(加载到堆栈的顶部),后跟 。引用局部变量只需要将值从其在堆栈中的分配位置拉出。thisaload_0thisgetfieldaload_n

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

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

(并不是说这很重要 - 两者的速度都相当不错,只有在非常奇怪的情况下,差异才会变得显着。