【问题标题】:Does the JVM optimize aliased variables?JVM 是否优化了别名变量?
【发布时间】:2012-03-09 04:24:00
【问题描述】:

我很好奇。如果我创建了一些局部变量,它们仅用作其他变量的别名(也就是说,一个新的“别名”变量只是简单地分配了一些其他变量的值,而别名的赋值永远不会改变),JVM 是否通过以下方式优化它直接使用原始变量?

例如,假设我正在编写一个(不可变的)四元数类(quaternion 是一个具有四个值的向量)。我编写了一个multiply() 方法,它采用另一个Quaternion,将两者相乘,然后返回结果Quaternion。为了节省打字和提高可读性,我在执行实际计算之前创建了几个别名:

public class Quaternion {

    private double[] qValues;

    public Quaternion(double q0, double q1, double q2, double q3) {
        qValues = new double[] {q0, q1, q2, q3};
    }

    // ...snip...

    public Quaternion multiply(Quaternion other) {
        double a1 = qValues[0],
                 b1 = qValues[1],
                 c1 = qValues[2],
                 d1 = qValues[3],

                 a2 = other.qValues[0],
                 b2 = other.qValues[1],
                 c2 = other.qValues[2],
                 d2 = other.qValues[3];

        return new Quaternion(
                    a1*a2 - b1*b2 - c1*c2 - d1*d2,
                    a1*b2 + b1*a2 + c1*d2 - d1*c2,
                    a1*c2 - b1*d2 + c1*a2 + d1*b2,
                    a1*d2 + b1*c2 - c1*b2 + d1*a2
                );
    }

}

那么,在这种情况下,JVM 是否会取消a1b1 等,而直接使用qValues[n]other.qValues[n]

【问题讨论】:

    标签: java variables optimization jvm


    【解决方案1】:

    没有像您在 Java 中描述的那样的别名。当您将一个内存位置的值分配给一个新变量时,JVM 会复制该值。如果要创建别名,则在计算期间从另一个线程更改底层数组会改变结果。这在您的示例中不会发生,因为您明确告诉 JVM 先复制这些值。

    如果您担心性能,请不要担心。程序正确性胜过所有性能问题。任何更快产生错误结果的程序都是毫无价值的。我并不是说直接在计算中访问数组必然会产生不正确的结果,因为我还没有看到其余的代码,但我说这种类型的微优化不值得你先找到一个性能问题,然后执行时序测试以验证问题所在。

    【讨论】:

    • 纠正了“别名”这一点,但他的代码不是线程安全的,与是否使用数组值的本地副本无关。
    • 这真的取决于...snip... 包含的内容。 ;) 鉴于这个问题是关于变量别名而不是多线程,我怀疑这不是问题的重点。
    • 假设我们有类似foo(int[] a, int[] b) { a[0..n] = b[5] + 1;} 的东西(伪代码,但应该很明显)。显然ab 可以引用内存中的相同数据,但此时有两个不同的符号名称。这不是别名的定义吗?
    • 按照@Voo 所说的,我并不是要暗示该语言中存在实际的别名机制,而是变量的行为类似于别名。我想也许因为 JVM 的用法很明显,它可能会以某种方式对其进行优化。
    • @Voo - int[] a 表示a 持有一个对象的引用(想想C 中的指针)——一个整数数组。如果b 指向同一个对象,则意味着b 具有引用的副本。别名意味着更改b 中的引用——而不是它指向的底层对象——也会更改a 中的引用。这在 Java 中是不可能的。
    【解决方案2】:

    javac 编译器不会这样做。像这样反汇编一段简单的代码:

        int a = 1;
        int b = a;
        System.out.println("" + (a - b));
    

    演出:

       0: iconst_1      
       1: istore_1      
       2: iload_1       
       3: istore_2
       ...
    

    但这就是解释器将要执行的(甚至解释器有时也可以做一些基本的优化)。 JIT 编译器将处理这些类型的优化和许多其他优化;对于你的方法,它甚至小到可以内联,所以一旦 JIT 启动,你甚至不会得到方法调用开销。

    (例如,在我的示例中,JIT 可以非常轻松地进行常量传播,只需取消变量和计算,只需使用 "" + 0 作为 println() 方法的参数调用。)

    但是,最后,只需遵循 JIT 黑客常说的一句话:编写可维护的代码。不用担心 JIT 会做什么或不会做什么。

    (注意:关于变量不是“别名”,而是原始值的副本,David 是正确的。)

    【讨论】:

      【解决方案3】:

      正如其他答案所指出的,Java 中没有别名变量的概念,并且变量的值存储在每个声明的变量中。

      使用局部变量来存储数组的值以供将来计算是一个更好的主意,因为它使代码更具可读性,并消除了对数组的额外读取。

      话虽如此,创建局部变量确实会增加线程中为该方法分配的 Java 堆栈帧的大小。在这个特定问题中这不是问题,但是更多数量的局部变量会增加执行所需的堆栈大小。如果涉及递归,这将特别重要。

      【讨论】:

        【解决方案4】:

        您的程序应该可以运行,但您的问题的答案是否定的。 JVM 不会将a1 视为qValues[0] 的别名,而是将后者的值复制到前者。

        检查这个很好的参考:http://www.yoda.arachsys.com/java/passing.html

        【讨论】:

        • 您引用的参考文献与问题无关。 -1.
        • 请详细说明您的陈述。你对这个问题的理解是什么?
        • 问题是关于别名的。参考是关于参数传递的。
        • 好吧,正如所有答案所指出的那样,Java 中不存在别名概念。 derefed先生之所以提出这个问题,是因为他当时还不太了解Java中的参数传递机制。这就是为什么我把参考放在那里。我不明白为什么这与问题无关,为什么我的回答被否决了
        • 没错。 Java中没有别名的概念。所以没有引用来描述它。所以 any 引用将是无关紧要的。他一直在谈论局部变量。他对参数传递只字未提。那是你自己的、无关紧要的贡献。
        猜你喜欢
        • 2011-09-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-03-15
        相关资源
        最近更新 更多