【问题标题】:Java final performance/optimizationJava 最终性能/优化
【发布时间】:2017-01-24 02:21:42
【问题描述】:

所以我在不同的数据结构上运行了一些基准测试并注意到,当我将变量声明为 final 时,我得到了 10-20% 的性能提升。

这真的让我很惊讶。我认为 final 关键字纯粹用于限制变量的变化,优化会确定某个变量是否具有恒定值。

示例如下:

import javafx.scene.input.KeyCode;
import java.util.*;

public class Main {
    static /*final*/ int LOOPS = Integer.MAX_VALUE / 100;

    static /*final*/ KeyCode[] keyCodes = KeyCode.values();

    public static void main(String[] args) {
        long startTime;
        long endTime;

        testEnumSet(); //warmup
        startTime = System.nanoTime();
        testEnumSet();
        endTime = System.nanoTime();
        System.out.println("  EnumSet: " + (endTime - startTime) + "ns");
    }

    static /*final*/ EnumSet<KeyCode> enumSet = EnumSet.noneOf(KeyCode.class);
    static void testEnumSet() {
        for (int i = 0; i < LOOPS; i++) {
            /*final*/ KeyCode add = getRandomKeyCode();
            if(!enumSet.contains(add)) enumSet.add(add);

            /*final*/ KeyCode remove = getRandomKeyCode();
            if(enumSet.contains(remove)) enumSet.remove(remove);
        }
    }

    /*final*/ static Random random = new Random();
    static KeyCode getRandomKeyCode() {
        return keyCodes[random.nextInt(keyCodes.length)];
    }
}

最后:....EnumSet: 652 266 207ns
没有final:EnumSet: 802 121 596ns

这是始终可重复的!

为什么使用 final 的代码和不使用 final 的代码之间存在如此巨大的差异?为什么不优化?以及为什么无论如何 final 更快,生成的字节码有什么区别?

【问题讨论】:

  • @JarrodRoberson 评分最高的答案是没有性能提升,但我的基准测试显示约 20% 的性能提升,所以我不认为这是重复的。
  • 评分最高并不意味着总是正确,有时它只是意味着大多数人都错了。就像你的发现并不总是可重复的,final 并不总是能带来任何可衡量的收益。
  • 您在代码中使用了 static 最终字段。将其转换为最终实例字段(只需在main 中创建一个实例),效果可能会消失,因为静态字段参与常量折叠。实例字段 otoh 不是常量(从实例更改为实例),因此只能从冗余负载消除中受益,这仅依赖于非易失性而不是最终性。因此,如果您想对不可变对象进行基准测试而不仅仅是全局常量,那么您的基准测试就没有用处。
  • 是的,你是对的,虽然这不是我的基准测试的目标,但这只是我在旁边发现的东西,让我想知道。

标签: java optimization jvm final jit


【解决方案1】:

如果某些东西永远不会改变,您可以进行各种优化,例如内联实际值,而不是一遍又一遍地查找它。这只是您可以做的一件事,它很容易解释并带来最大的好处。

还有许多其他更深奥的事情发生,但影响要小得多。

如果您查看字节码,您会看到这一点,尤其是在 JIT 启动之后。

让整个班级final 可以有类似的好处。

也就是说,final 引用并不总是提供可衡量的 增益,这取决于参考的使用情况。在这种情况下 EnumSet 在引擎盖下做了很多特殊的调味料 在源头。不可变引用可能被内联为 那个。

还请注意,您看到的行为可能会在 JVM 的未来版本中消失,或者在其他 JVM 实现中不存在。任何事情都可能在您的领导下发生变化,因此不要依赖任何特定的实现。

Here is some more information in greater detail about all the idiomatic uses of final.

【讨论】:

  • 是的,但我认为 JVM 足够聪明,可以检测到一个变量基本上是最终的,如果它从未被修改(尤其是在如此简单的一段代码中)并且能够优化它。跨度>
  • 不,它不能预测未来,任何没有明确限制改变的东西都可能在未来的任何时候改变,仅仅因为它在最近 10 万次执行中没有改变并不意味着它不会为第 100,001 人。
  • 但是如果一个字段发生变化,这不是可以在编译时计算出来的吗?我的意思是,如果您尝试更改最终字段,编译器会注意到并阻止您。
  • 如果您不在运行时每次使用时都检查参考值,您怎么知道它发生了变化?这不是魔术,在很长一段时间内间接引用来获取值是昂贵的。有 volatile 关键字可以禁用跨线程的任何软缓存值。对这个主题的一些基础研究,对于 SO 来说太宽泛了,会告诉你你想知道什么。
  • HotSpot 擅长优化非final 变量,但此示例在全局可见的Random 实例上调用synchronized 方法,这意味着代码必须 重新检查其他线程进行的潜在中间修改。对于KeyCode[] keyCodes 变量,它意味着再次执行边界检查,这就是为什么对于这个变量,final 的影响最大的原因。然后,int LOOPS = Integer.MAX_VALUE / 100 在声明为final 时是一个编译时常量。对于所有其他变量,影响相当小。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-12-17
  • 2013-05-08
  • 1970-01-01
  • 2019-05-01
  • 1970-01-01
相关资源
最近更新 更多