【问题标题】:Java collection performance when comparing items比较项目时的 Java 收集性能
【发布时间】:2019-07-08 09:13:21
【问题描述】:

来自 C/C++ 的人提出的基本性能问题。

我正在使用一个集合 (ArrayDeque) 来简单地按身份保存、添加、删除项目。我知道合同是让集合在检查相等性时使用equals(),例如在remove(obj) 期间,但在我的情况下,我想使用引用语义(如 IdentityHashMap 但不需要地图)。所以我很高兴知道我永远不会覆盖集合中保存的任何对象(声明为保存接口)上的equals()

来自本机编程的我不禁问自己,remove(obj) 的编译代码是否会遍历项目并在 Object.equals() 上执行 虚拟调用 只是为了比较地址?由于我正在存储接口引用,因此无法(?)使用final 优化它,因此编译器不会费心进行无用的调用(即内联它们) - 但现在我领先于自己,因为它可能无论如何都不需要这种优化,并且 JVM 有其他方法(去虚拟化?)在这种情况下生成最佳代码。

假设我的代码需要首先考虑这方面可以获得的优化级别 - 我的理解正确吗?这种情况下有什么好的设计

【问题讨论】:

  • 如果重复调用 Object::equals,JIT 编译器会内联您的调用 ==> 不要打扰。
  • @assylias 但这很棘手,对吧?就编译器所知,该集合可能包含多个派生最多的类实例,其中一些可能会覆盖equals(),而另一些则不会。因此,不可能生成不会单独查看每个项目的代码 - 内联需要一个需要在编译时表达的假设
  • 内联确实需要 JVM 的编译时假设 - 这是 JIT 编译的重点:它使用运行时可用的所有信息。您可以使用以下选项运行程序以查看该方法是否以及何时内联:-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
  • 我的意思是,我确定你看到了,是“不需要内联”,呵呵。
  • Java 的整体理念是您不需要关心这些细节。我猜你所说的 virtual call 是指传统的 C++ 风格。 Here are the (quite overwhelming and startling) low-level details.

标签: java performance collections equals identity


【解决方案1】:

使方法final 不会避免虚拟调用,因为无论如何都会使用invokevirtual 操作码,并且JVM 无法判断该方法是否是最终方法。

好消息是,如果 JVM 看不到方法在类路径中的任何位置被覆盖,那么 JVM 可能能够内联它或避免虚拟调用,这样您的性能会随着程序的运行而提高。

【讨论】:

【解决方案2】:

当你使用remove方法时,它会调用equals方法进行比较。理想情况下,您应该重写 equals 和 hashcode 方法来使用这些方法。否则会发生类型检查和地址比较的默认实现。强烈建议在使用 Collections 方法时定义 equals 和 hashcode 方法的实现。 关于性能,是的,你是对的 - 集合中的所有对象都将被线性扫描,直到 JVM 遇到正确的匹配。这是一个线性搜索,因此这个移除操作的时间复杂度将花费 O(n) 时间。

【讨论】:

  • 你为什么说我应该覆盖equals和hashcode?我想要的是身份平等。
  • 对于身份相等,您需要告诉 Java 应该如何比较特定类的对象。一个类可能有多个字段。在这种情况下,Java 不知道如何比较对象。默认情况下,java 只会比较地址并继续。但是,如果您希望 Java 比较这些值,那么您应该告诉 Java 比较逻辑。这个比较逻辑可以在equals和hashcode方法中指定。
  • 尊敬的,你不知道身份平等是什么意思。这意味着确切地比较地址。所以,对于身份相等,你不需要告诉 Java 任何东西。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-24
  • 2015-10-22
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
相关资源
最近更新 更多