【问题标题】:Cost of using final fields使用 final 字段的成本
【发布时间】:2014-06-24 23:43:13
【问题描述】:

我们知道将字段设为 final 通常是一个好主意,因为我们获得了线程安全性和不变性,这使得代码更易于推理。我很好奇是否有相关的性能成本。

Java 内存模型保证final Field Semantics

只有在对象完全初始化后才能看到对该对象的引用的线程保证可以看到该对象的最终字段的正确初始化值。

这意味着对于这样的类

class X {
    X(int a) {
        this.a = a;
    }
    final int a;

    static X instance;
}   

每当线程 1 创建这样的实例时

X.instance = new X(43);
while (true) doSomethingEventuallyEvictingCache();

线程 2 看到了它

 while (X.instance == null) {
      doSomethingEventuallyEvictingCache();
 }
 System.out.println(X.instance.a);

它必须打印 43。如果没有 final 修饰符,JIT 或 CPU 可以重新排序存储(首先存储 X.instance,然后设置 a=43),线程 2 可以看到默认初始化值并打印 0而是。

当 JIT 看到 final 时,它显然会避免重新排序。但它也必须强制 CPU 服从命令。是否存在相关的性能损失?

【问题讨论】:

  • 所有最终字段都是自动可变的(保证可见性)
  • @Steve Visibility 得到保证,但 volatile 变量和 final 之间的性能成本完全不同。阅读期末考试不需要任何记忆障碍。

标签: java multithreading performance memory-fences


【解决方案1】:

是否存在相关的性能损失?

如果您查看 JIT 编译器的源代码,您会在文件 src/share/vm/opto/parse1.cpp 中找到以下关于最终成员变量的注释:

这个方法(它必须是Java规则的构造函数)写了一个final。在构造函数发布对新构造函数对象的引用之后,所有初始化的效果必须在任何代码之前提交到内存。与其等待发布,我们只需在此处阻止写入。我们不是只对那些需要完成的写入设置障碍,而是强制所有写入完成。

如果有 final 成员变量,编译器会发出额外的指令。最有可能的是,这些附加指令会导致性能损失。但目前尚不清楚这种影响是否对任何应用程序都很重要。

【讨论】:

  • Benchmarking 先生自己写了一篇关于决赛冻结惩罚的非常好的论文:shipilev.net/blog/2014/all-fields-are-final
  • +1 Most likely, these additional instructions cause a performance penalty. 在 X86 上,它会发出一个存储负载栅栏,这会导致轻微的性能下降,但收益可能会比那个打击更好。
  • @JohnVint - 是检查 asm 吗?如果是这样,这听起来太保守/不必要了。此答案中的链接源代码执行_exits.insert_mem_bar(Op_MemBarRelease, alloc_with_final());。在 x86 asm 中,释放屏障是免费的,不需要指令,只需阻止编译时重新排序。 mfence 实际上非常昂贵,尤其是在最近的英特尔(Skylake)上,它还阻止了乱序执行。 (Are loads and stores the only instructions that gets reordered?)。 (一个虚拟的lock 更便宜;IIRC HotSpot 可能会在需要 seq-cst 时使用它)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-02-16
  • 1970-01-01
  • 1970-01-01
  • 2019-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多