java方法调用有多贵

时间:2011-06-27 15:12:48

标签: java performance methods call

我是初学者,我一直都认为重复代码是不好的。但是,似乎为了不这样做,通常需要额外的方法调用。假设我有以下课程

public class BinarySearchTree<E extends Comparable<E>>{
    private BinaryTree<E> root;
    private final BinaryTree<E> EMPTY = new BinaryTree<E>();
    private int count;
    private Comparator<E> ordering;

    public BinarySearchTree(Comparator<E> order){
        ordering = order;
        clear();
    }

    public void clear(){
        root = EMPTY;
        count = 0;
    }
}

将clear()方法中的两行复制并粘贴到构造函数中而不是调用实际方法,对我来说是否更为理想?如果是这样,它会产生多大的差异?如果我的构造函数进行了10次方法调用,每次调用只是将一个实例变量设置为一个值,该怎么办什么是最好的编程实践?

12 个答案:

答案 0 :(得分:67)

  

将clear()方法中的两行复制并粘贴到构造函数中而不是调用实际方法,对我来说是否更为理想?

编译器可以执行该优化。 JVM也是如此。编译器编写者和JVM作者使用的术语是“内联扩展”。

  

如果是这样,它会产生多大的差异?

测量它。通常,你会发现它没有任何区别。如果你认为这是一个性能热点,那你就是在错误的地方;这就是你需要测量它的原因。

  

如果我的构造函数进行了10次方法调用,每次调用只是将一个实例变量设置为值?

,该怎么办?

同样,这取决于生成的字节码和Java虚拟机执行的任何运行时优化。如果编译器/ JVM可以内联方法调用,它将执行优化以避免在运行时创建新堆栈帧的开销。

  

什么是最好的编程习惯?

避免过早优化。最佳做法是编写可读且设计良好的代码,然后针对应用程序中的性能热点进行优化。

答案 1 :(得分:18)

其他人对优化的看法绝对正确。

从性能的角度来看,没有理由来内联方法。如果这是性能问题,JVM中的JIT将内联它。在java中,方法调用非常接近自由,因此不值得考虑它。

话虽如此,这里有一个不同的问题。也就是说,错误的编程习惯,从构造函数中调用可重写的方法(即,不是finalstaticprivate的方法)。 (Effective Java,2nd Ed。,p.89,标题为“继承的设计和文档或禁止它”的项目)

如果有人添加一个名为BinarySearchTree的{​​{1}}子类,会使用以下代码覆盖所有公共方法,会发生什么:

LoggingBinarySearchTree

然后public void clear(){ this.callLog.addCall("clear"); super.clear(); } 永远不会被构建!问题是,当LoggingBinarySearchTree构造函数正在运行时,this.callLog将为null,但被调用的BinarySearchTree是被覆盖的clear,您将获得NullPointerException 1}}。

请注意,Java和C ++在这里有所不同:在C ++中,调用virtual方法的超类构造函数最终会调用超类中定义的那个,而不是被覆盖的类。在两种语言之间切换的人有时会忘记这一点。

考虑到这一点,我认为从构造函数调用时内联clear方法可能更简洁,但通常在Java中你应该继续使用所有方法你想要的电话。

答案 2 :(得分:5)

我肯定会保持原样。如果更改clear()逻辑怎么办?找到复制2行代码的所有地方是不切实际的。

答案 3 :(得分:3)

最佳做法是测量两次并切一次。

一旦浪费时间优化,你再也无法取回它了! (所以先测量它,然后问问自己是否值得优化。你节省多少实际时间?)

在这种情况下,Java VM可能已经在进行您正在讨论的优化。

答案 4 :(得分:3)

一般来说(作为初学者,这意味着永远!)你永远不应该像你正在考虑的那样进行微观优化。总是喜欢这样的事情的可读性。

为什么呢?因为编译器/热点会在运行中为您进行这些类型的优化,还有很多甚至更多。如果有的话,当你尝试沿着这些行进行优化时(虽然不是这种情况),你可能会让事情变得更慢。 Hotspot了解常见的编程习惯用法,如果你自己尝试进行优化,可能无法理解你想要做的事情,因此它无法对其进行优化。

还有更高的维护成本。如果你开始重复代码,那么维护将会更加努力,这可能比你想象的要麻烦得多!

顺便说一句,你可能会在你的编码生活中得到一些你需要进行低级别优化的点 - 但是如果你达到这些点,你肯定会知道什么时候到来。如果不这样做,如果需要,您可以随时返回并进行优化。

答案 5 :(得分:2)

方法调用的 cost 是堆栈帧的创建(和处理)以及一些额外的字节代码表达式,如果您需要将值传递给方法。

答案 6 :(得分:1)

我遵循的模式是这个方法是否满足以下条件之一:

  • 在此课程之外使用此方法会有帮助吗?
  • 在其他方法中使用此方法会有帮助吗?
  • 每次需要时重写这个都会令人沮丧吗?
  • 使用一些参数可以提高方法的多功能性吗?

如果以上任何一种情况属实,那么它应该用它自己的方法包装。

答案 7 :(得分:1)

保持clear()方法有助于提高可读性。拥有不可维护的代码会更加昂贵。

答案 8 :(得分:1)

优化编译器通常可以很好地消除这些“额外”操作的冗余;在许多情况下,“优化”代码和代码之间的区别只是按照您想要的方式编写,并且通过优化编译器运行是没有的;也就是说,优化编译器通常做得和你一样好,而且它可以在不造成源代码任何降级的情况下完成。事实上,很多时候,“手动优化”代码最终效率低,因为编译器在进行优化时会考虑很多事情。将代码保留为可读格式,不要担心以后的优化。

  

“过早优化是其根源   所有的邪恶。“ - 唐纳德克努特

答案 9 :(得分:0)

我不会担心方法调用,但方法的逻辑。如果它是关键系统,并且系统需要“快速”,那么我会考虑优化需要很长时间才能执行的代码。

答案 10 :(得分:0)

鉴于现代计算机的记忆,这是非常便宜的。将代码分解为方法总是更好,这样有人可以快速阅读最新情况。如果错误仅限于具有几行主体的单个方法,它还有助于缩小代码中的错误。

答案 11 :(得分:0)

正如其他人所说,方法调用的成本是微不足道的,因为编译器会为你优化它。

也就是说,从构造函数中对实例方法进行方法调用存在危险。您冒着以后更新实例方法的风险,因此它可能会尝试使用构造函数尚未启动的实例变量。也就是说,您不一定要将构造活动与构造函数分开。

另一个问题 - 你的clear()方法将root设置为EMPTY,它在创建对象时初始化。如果然后将节点添加到EMPTY,然后调用clear(),则不会重置根节点。这是你想要的行为吗?