考虑以下两种在Java中编写循环的方法,以查看列表是否包含给定值:
boolean found = false;
for(int i = 0; i < list.length && !found; i++)
{
if(list[i] == testVal)
found = true;
}
boolean found = false;
for(int i = 0; i < list.length && !found; i++)
{
found = (list[i] == testVal);
}
这两个是等价的,但我总是使用样式1,因为1)我觉得它更具可读性; 2)我假设将found
重新分配给false
数百次感觉就像需要更多时间。我想知道:第二个假设是真的吗?
break;
块中添加if
语句,但我不在乎。同样,这个问题是关于表现,而不是风格。答案 0 :(得分:8)
好吧,只需写一个微观基准:
import java.util.*; public class Test { private static int[] list = new int[] {1, 2, 3, 4, 5, 6, 7, 8, 9} ; private static int testVal = 6; public static boolean version1() { boolean found = false; for(int i = 0; i < list.length && !found; i++) { if(list[i] == testVal) found = true; } return found; } public static boolean version2() { boolean found = false; for(int i = 0; i < list.length && !found; i++) { found = (list[i] == testVal); } return found; } public static void main(String[] args) { // warm up for (int i=0; i<100000000; i++) { version1(); version2(); } long time = System.currentTimeMillis(); for (int i=0; i<100000000; i++) { version1(); } System.out.println("Version1:" + (System.currentTimeMillis() - time)); time = System.currentTimeMillis(); for (int i=0; i@lt;100000000; i++) { version2(); } System.out.println("Version2:" + (System.currentTimeMillis() - time)); } }
在我的机器上,版本1似乎要快一点:
版本1:5236
版本2:5477
(但是在1亿次迭代中这是0.2秒。我不关心这个。)
如果查看生成的字节码,版本2中还有两条指令可能导致执行时间更长:
public static boolean version1(); Code: 0: iconst_0 1: istore_0 2: iconst_0 3: istore_1 4: iload_1 5: getstatic #2; //Field list:[I 8: arraylength 9: if_icmpge 35 12: iload_0 13: ifne 35 16: getstatic #2; //Field list:[I 19: iload_1 20: iaload 21: getstatic #3; //Field testVal:I 24: if_icmpne 29 27: iconst_1 28: istore_0 29: iinc 1, 1 32: goto 4 35: iload_0 36: ireturn public static boolean version2(); Code: 0: iconst_0 1: istore_0 2: iconst_0 3: istore_1 4: iload_1 5: getstatic #2; //Field list:[I 8: arraylength 9: if_icmpge 39 12: iload_0 13: ifne 39 16: getstatic #2; //Field list:[I 19: iload_1 20: iaload 21: getstatic #3; //Field testVal:I 24: if_icmpne 31 27: iconst_1 28: goto 32 31: iconst_0 32: istore_0 33: iinc 1, 1 36: goto 4 39: iload_0 40: ireturn
答案 1 :(得分:3)
关于nitpicks角落的评论:
如果您真的关注绝对性能,那么暂停和删除“&amp;&amp;!found”将在理论上为#1提供更好的性能。每次迭代都要担心两个二进制运算。
如果你想在不使用休息的情况下获得优化,那么
boolean notFound = true;
for(int i = 0; notFound && i < list.length; i++)
{
if(list[i] == testVal)
notFound = false;
}
在平均情况下比现有选项#1运行得更快。
当然这是个人偏好,但我宁愿永远不要在for循环的头部进行任何额外的评估。我发现它在阅读代码时会引起混淆,因为它很容易被遗漏。如果我无法使用break / continue获得所需的行为,我将使用while或do / while循环。
答案 2 :(得分:2)
实际上,由于pipeline,“if”会使您的程序速度下降超过作业。
答案 3 :(得分:1)
这取决于您使用的编译器,因为不同的编译器可能会进行不同的优化。
答案 4 :(得分:1)
我相信风格2的速度要快得多 - 比如说大约1个时钟周期。
但是,如果我正在处理它,我会将其重写为以下内容:
for(i=0; i<list.length && list[i]!=testval; i++);
boolean found = (i!=list.length);
答案 5 :(得分:1)
在我看来,如果你希望在列表结尾之前找到你的价值,那么你最好用#2 - 因为它会在循环条件中找到!假设你放弃了第一个选项(唯一明智的东西,IMO),那么伪装配将看起来像:
选项1:
start:
CMP i, list.length
JE end
CMP list[i], testval
JE equal
JMP start
equal:
MOV true, found
end:
选项2:
start:
CMP i, list.length
JE end
CMP true, found
JE end
CMP list[i], testval
JE equal
JNE notequal
equal:
MOV true, found
JMP start
notequal:
MOV false, found
JMP start
end:
我认为选项1在这里更优越,因为它的指令少了1/3。当然,这是没有优化的 - 但这是编译器和特定情况(在此之后发现了什么?我们可以一起优化它吗?)。
答案 6 :(得分:1)
这是另一种风格
for(int i = 0; i < list.length; i++)
{
if(list[i] == testVal)
return true;
}
return false;
答案 7 :(得分:1)
从性能的角度来看,我认为这两种选择都有待改进。
考虑每次迭代进行多少次测试(几乎总是跳转),并尽量减少数量。
Matt的解决方案是,当找到答案时返回,将测试次数从三次(循环迭代器,在循环中找到测试,实际比较)减少到两次。做“发现”测试基本上两次是浪费。
我不确定经典但有些模糊不清的向后循环技巧是否是Java中的一个胜利,而且在阅读JVM代码时也不够热,无论如何都要解决它。
答案 8 :(得分:0)
我想说在98%的系统中,没关系。差异(如果有的话)几乎不可察觉,除非该循环是代码的主要部分并且运行了多次思考。
编辑:这是假设它尚未被编译器优化。
答案 9 :(得分:0)
任何体面的编译器都会在循环的持续时间内保留在寄存器中,因此成本绝对可以忽略不计。
如果第二种样式没有分支,那么它会更好,因为CPU的管道不会中断那么多......但这取决于编译器如何使用指令集。
答案 10 :(得分:0)
这只能在对性能非常敏感的代码(模拟器,模拟器,视频编码软件等)中进行测量,在这种情况下,您可能希望手动检查生成的代码,以确保编译器实际生成合理的代码。
答案 11 :(得分:0)
可以肯定的是,您应该编译两个版本(比如Sun的最新编译器)并使用适当的工具检查生成的字节码......这是唯一可靠的方法,确切地知道,其他一切都是疯狂猜测。
答案 12 :(得分:0)
boolean found = false;
for(int i = 0; i < list.length && !found; i++)
{
if(list[i] == testVal)
found = true;
}
我在块中没有看到中断语句。
除此之外,我更喜欢这种风格。它提高了可读性,从而提高了维护者误读和错误修复的可能性。