为了促进良好的编程习惯并提高我的代码效率(阅读:“我的兄弟和我正在争论一些代码”),我向有经验的程序员提出这个问题:
哪个代码块“更好”? 对于那些无法阅读代码的人来说,是否值得在for循环中设置一个条件来减少冗余代码的数量,而不是将它放在外面并制作2个for循环?这两段代码都有效,问题是效率与可读性。
- (NSInteger)eliminateGroup {
NSMutableArray *blocksToKill = [[NSMutableArray arrayWithCapacity:rowCapacity*rowCapacity] retain];
NSInteger numOfBlocks = (NSInteger)[self countChargeOfGroup:blocksToKill];
Block *temp;
NSInteger chargeTotal = 0;
//Start paying attention here
if (numOfBlocks > 3)
for (NSUInteger i = 0; i < [blocksToKill count]; i++) {
temp = (Block *)[blocksToKill objectAtIndex:i];
chargeTotal += temp.charge;
[temp eliminate];
temp.beenCounted = NO;
}
}
else {
for (NSUInteger i = 0; i < [blocksToKill count]; i++) {
temp = (Block *)[blocksToKill objectAtIndex:i];
temp.beenCounted = NO;
}
}
[blocksToKill release];
return chargeTotal;
}
或者...
- (NSInteger)eliminateGroup {
NSMutableArray *blocksToKill = [[NSMutableArray arrayWithCapacity:rowCapacity*rowCapacity] retain];
NSInteger numOfBlocks = (NSInteger)[self countChargeOfGroup:blocksToKill];
Block *temp;
NSInteger chargeTotal = 0;
//Start paying attention here
for (NSUInteger i = 0; i < [blocksToKill count]; i++) {
temp = (Block *)[blocksToKill objectAtIndex:i];
if (numOfBlocks > 3) {
chargeTotal += temp.charge;
[temp eliminate];
}
temp.beenCounted = NO;
}
[blocksToKill release];
return chargeTotal;
}
请记住,这是针对游戏的。当用户双击屏幕时,该方法被调用,并且for循环通常在1到15次迭代之间运行,最多64次。我明白这并不重要,这主要是为了帮助我准确理解条件语句的成本。 (阅读:我只是想知道我是不对。)
答案 0 :(得分:9)
第一个代码块更干净,效率更高,因为检查numOfBlocks&gt; 3在整个迭代过程中都是真或假。
第二个代码块避免了代码重复,因此可能会带来较小的风险。但是,它在概念上更复杂。
可以通过添加
来改进第二个块bool increaseChargeTotal = (numOfBlocks > 3)
在循环之前然后使用这个布尔变量而不是循环中的实际检查,强调在迭代期间它不会改变的事实。
就个人而言,在这种情况下,我会投票给第一个选项(重复循环),因为循环体很小,这清楚地表明条件在循环外部;此外,它更有效,可能符合“使常见情况更快”的模式。
答案 1 :(得分:8)
所有其他条件相同,拥有两个单独的循环通常会更快,因为您只进行一次测试而不是循环的每次迭代。由于管道停滞和分支错误预测,每次迭代循环内的分支通常会使您显着减慢;但是,由于分支总是以相同的方式运行,因此假设您使用的是具有分支预测的CPU(我不确定是否使用了ARM芯片),CPU几乎肯定会为每次迭代正确地预测分支,除了前几个在iPhone中有一个分支预测器单元。)
然而,要考虑的另一件事是代码大小:两个循环方法会生成更多代码,特别是如果循环体的其余部分很大。这不仅增加了程序目标代码的大小,而且还会损害指令缓存性能 - 您将获得更多缓存未命中。
考虑到所有事情,除非代码是你的应用程序中的一个重要瓶颈,否则我会选择循环中的分支,因为它会导致更清晰的代码,并且它不会违反don't repeat yourself principle。如果你对一次循环进行了更改而忘记在双循环版本中更改另一个循环,那么你就会陷入一个受伤的世界。
答案 2 :(得分:8)
如果没有定义“更好”的要求,就无法回答这个问题。是运行效率吗?编译大小?代码可读性?代码可维护性?代码可移植性?代码可重用性?算法可证明性?开发人员效率(请对我错过的任何常用测量结果发表评论。)
有时绝对的运行时效率是最重要的,但不像人们通常想象的那样频繁,因为你在问题中点头 - 但这至少很容易测试!通常它是所有这些问题的混合,你最终必须做出主观判断。
这里的每一个答案都是应用这些方面的个人组合,人们经常进入充满活力的圣战,因为每个人都是正确的 - 在适当的情况下。这些方法最终是错误的。唯一正确的方法是定义对您重要的内容,然后对其进行衡量。
答案 3 :(得分:5)
我会选择第二种选择。如果循环中的所有逻辑完全不同,那么为for循环制作2是有意义的,但是情况是一些逻辑是相同的,并且一些是基于条件的附加。所以第二种选择更清晰。
第一种选择会更快,但稍微如此,如果我发现那里存在瓶颈,我只会使用它。
答案 4 :(得分:5)
在方法开始/结束时,你可能会浪费更多时间在无意义和无意义的[blocksToKill retain] / [blocksToKill release]上,而不是执行几十次比较所花费的时间。没有必要保留阵列,因为你返回后不需要它,在此之前它永远不会被清理。
恕我直言,代码重复是导致错误的主要原因,应尽可能避免。添加Jens recomendation以使用快速枚举和Antti的recomendation来使用明确命名的布尔值,你会得到类似的东西:
- (NSInteger)eliminateGroup {
NSMutableArray *blocksToKill = [NSMutableArray arrayWithCapacity:rowCapacity*rowCapacity];
NSInteger numOfBlocks = (NSInteger)[self countChargeOfGroup:blocksToKill];
NSInteger chargeTotal = 0;
BOOL calculateAndEliminateBlocks = (numOfBlocks > 3);
for (Block* block in blocksToKill) {
if (calculateAndEliminateBlocks) {
chargeTotal += block.charge;
[block eliminate];
}
block.beenCounted = NO;
}
return chargeTotal;
}
如果你完成了你的项目并且你的程序运行速度不够快(两个大的ifs),那么你可以对它进行分析并找到热点,然后你可以确定你花在思考那个分支上的几微秒值得考虑一下 - 当然,现在根本不值得思考,这意味着唯一的考虑因素是更具可读性/可维护性。
答案 5 :(得分:4)
我的投票强烈反对第二个区块。
第二个块清楚地说明了逻辑中的差异,并共享相同的循环结构。它更具可读性和可维护性。
第一个块是过早优化的一个例子。
至于使用bool来“保存”所有这些LTE比较 - 在这种情况下,我认为它不会有所帮助,机器语言可能需要完全相同数量和大小的指令。
答案 6 :(得分:3)
“if”测试的开销是一些CPU指令;不到一微秒的方式。除非你认为循环将会响应用户输入而运行数十万次,否则噪音就会丢失。所以我会选择第二种解决方案,因为代码更小,更容易理解。
在任何一种情况下,我都会将循环更改为
for(temp in blocksToKill){...}
这既清洁阅读又比手动获取数组的每个元素快得多。
答案 7 :(得分:0)
可执行性(以及可维护性)可以而且应该以性能的名义牺牲,但是当且仅当确定性能是一个问题时。
第二个块更具可读性,除非/直到速度成为问题,否则它会更好(在我看来)。在测试你的应用程序时,如果你发现这个循环是造成不可接受的性能的原因,那么无论如何都要设法让它更快,即使它变得更难维护。但是在你必须这样做之前不要这样做。