如何判断Java代码的效率

4
我刚意识到我不知道如何从计算角度判断一段Java代码是否高效。有时候阅读几个源代码,我会感觉到正在阅读的代码非常低效,有时候则相反。您能列出基本的规则并解释为什么它们如此重要吗?
编辑 - 我的问题与JVM的Java实现相关,因此涉及Java分配问题、字符串管理、异常处理、线程同步等问题。
提前致谢。
附注-请不要字面理解“一行”。

你为什么觉得这是低效的,相反的呢?请在你的问题中加入一些例子。 - Thorbjørn Ravn Andersen
5个回答

7

基本的一行规则?好的,这里是:

避免不必要的计算。

如何做到呢?很抱歉,没有简短的答案。 :(

嗯,人们在大学里学习计算机科学中的算法和数据结构有其原因……也许你应该上一门关于算法/数据结构的课程。


我不确定你所说的“从计算视角”是什么意思(它似乎暗示了算法问题),但是假设你指的是类似于分析性能的技巧,请尝试以下方法:

  • 运行程序,然后突然暂停它,并查看它停在哪里。多次这样做;无论它在哪里停止最多,都是瓶颈,而它停止的频率表明它是多么糟糕的瓶颈。

  • 避免装箱/拆箱(在 intInteger 之间进行转换等);特别是避免 Integer[]List<Integer> 和其他内部存储原始类型数组的对象。

  • 提取公共代码(有时是速度问题,有时是可读性问题)

  • 避免使用 String 操作循环;改用 StringBuilder/StringBuffer。(简而言之,在不需要创建和/或复制数据时避免这样做。)

如果想到其他方法,我会再添加的。


@Mehrdad:你说得对,我表述太简洁了。我会编辑问题的。 - Jack
@Jack:+1 当你暂停它时,查看堆栈上的每一行代码。"瓶颈"(我更喜欢称之为"排水口")可能是其中任何一行。此外,不要混淆"看起来效率低下"和"实际效率低下"。这是新手常犯的错误。 - Mike Dunlavey
@Mike:再次提醒,我并不是新手,请给出更具建设性的答案,而非“避免不必要的计算”。我只是试图弄清楚哪些结构会在代码中引入延迟。此外,我认为我已经很清楚地表达了我的问题:在最后看起来似乎效率低下的部分其实并不是(反之亦然)。如果您看到我标记为回答的那个问题,那就是我想要的答案。 - Jack
1
@Jack:顺便说一下,关于性能分析的文献基础非常薄弱。这里有来自SIGPLAN的gprof论文。我(谦虚咳嗽)写了SIGPLAN和Dr. Dobbs论文。从我的角度来看,文献薄弱的原因是这个主题实际上非常简单。 - Mike Dunlavey
@Jack:我最喜欢的边缘情况是无限(或接近无限)循环。如果你想等待,你可以测量它。单个堆栈样本无法测量它,但可以找到它。此外,这里有一个例子,说明了人工程序的问题。 - Mike Dunlavey
显示剩余7条评论

6

@Mehrdad:我在谈论专业软件开发,其中性能分析通常非常有用,可以找出瓶颈所在。如果我们谈论算法复杂度,那么性能分析当然与之无关,但为什么我们需要在这个线程中使用“java”标签呢? - Roman
谢谢Roman,这正是我现在所做的,而且它帮了我很多。然而,有时候做性能分析非常耗时,我更关注使用性能分析找到的经常出现的问题和解决方案。 - Jack

3
我赞同Mherdad的回答,确实没有“基本的一行规则”。 关于建议使用性能分析工具的答案,只有在您理解算法时间复杂度和大O符号时,性能分析才真正有用。来自维基百科文章的大O符号介绍如下:
“在数学、计算机科学及相关领域中,大O符号描述的是函数的增长速度或衡量算法时间复杂度的上界,指出当输入值趋近无穷大时,函数的增长速度如何。大O符号常常被用作算法复杂度的渐进上界。”
大O符号背后的思想是,它让你了解输入大小如何影响给定算法的执行时间。例如,考虑以下两种方法:
void linearFoo(List<String> strings){
    for(String s:strings){
      doSomethingWithString(s);
    }
}

void quadraticFoo(List<String> strings){
    for(String s:strings){
        for(String s1:strings){
            doSomethingWithTwoStrings(s,s1);
        }
    }
}

linearFoo 被称为 O(n),意味着它的时间复杂度随着输入大小 n(即 strings.size())线性增加。 quadraticFoo 被称为 O(n2),意味着执行 quadraticFoo 所需的时间是 strings.size() 的平方函数。

一旦你对程序的算法时间复杂度有了感觉,分析工具就会变得有用。例如,如果在分析期间发现一个方法通常需要 1 毫秒来处理固定的输入大小,如果该方法是 O(n),那么将输入大小加倍将导致执行时间为 2 毫秒(1 毫秒 = n,因此 2n = 2 毫秒)。但是,如果它是 O(n2),将输入大小加倍将意味着你的方法将需要大约 4 毫秒才能执行(1 毫秒 = n2,因此(2n)2 = 4 毫秒)。


2

你可以使用jconsole监控你的应用程序的死锁、内存泄漏、线程和堆。简而言之,你可以通过图表看到应用程序的性能。

输入图片说明


2

如果您真的需要遵循Java编程的规则清单,请查看Joshua Bloch的Effective Java一书。该书不仅提供性能指南,还提供了正确的Java编程方式的指导。


1
你提供的那本书以及我在http://www.cs.cmu.edu/~jch/java/speed.html上找到的内容,我认为回答了我的问题。 - Jack
欢迎Jack :) 这绝对是一本好书,可以改善你看待Java编程的方式。 - aseychell

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接