3

为了促进良好的编程习惯,提高我的代码效率(阅读:“我和我兄弟在为一些代码争论”),我向有经验的程序员提出这个问题:

哪个代码块“更好”?对于那些懒得阅读代码的人来说,是否值得在 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 次。我知道这真的没那么重要,这主要是为了帮助我准确了解条件语句的成本。(阅读:我只是想知道我是否正确。)

4

8 回答 8

9

第一个代码块更简洁、更高效,因为在整个迭代过程中检查 numOfBlocks > 3 要么为真要么为假。

第二个代码块避免了代码重复,因此可能带来的风险较小。但是,它在概念上更复杂。

第二块可以通过添加来改进

bool increaseChargeTotal = (numOfBlocks > 3)

在循环之前,然后使用这个布尔变量而不是循环内的实际检查,强调在迭代期间它不会改变的事实。

就个人而言,在这种情况下,我会投票支持第一个选项(重复循环),因为循环体很小,这清楚地表明条件在循环之外;此外,它更有效,可能符合“使常见情况快速”的模式。

于 2009-06-29T04:38:37.097 回答
8

在所有其他条件相同的情况下,拥有两个单独的循环通常会更快,因为您只进行一次测试而不是循环的每次迭代。由于管道停顿和分支错误预测,每次迭代循环内的分支通常会显着减慢您的速度;但是,由于分支总是以相同的方式进行,因此 CPU 几乎肯定会为每次迭代正确预测分支,除了前几次,假设您使用的 CPU 具有分支预测功能(我不确定是否使用了 ARM 芯片在 iPhone 中有一个分支预测器单元)。

但是,要考虑的另一件事是代码大小:两个循环方法会生成更多代码,尤其是在循环体的其余部分很大的情况下。这不仅会增加程序目标代码的大小,还会损害指令缓存性能——您将获得更多的缓存未命中。

考虑到所有因素,除非代码是您应用程序中的重大瓶颈,否则我会选择循环内部的分支,因为它会导致代码更清晰,并且不会违反不要重复自己的原则。如果您对其中一个循环进行了更改而忘记更改双循环版本中的另一个循环,那么您将面临一个痛苦的世界。

于 2009-06-29T04:47:31.810 回答
8

如果不定义您对“更好”的要求,就无法回答这个问题。是运行时效率吗?编译大小?代码可读性?代码可维护性?代码可移植性?代码可重用性?算法证明?开发效率?(请对我错过的任何流行测量发表评论。)

有时绝对的运行时效率才是最重要的,但并不像人们通常想象的那样频繁,正如您在问题中所认可的那样——但这至少很容易测试!通常它是所有这些问题的混合体,你最终必须做出主观判断。

这里的每个答案都应用了这些方面的个人组合,人们经常陷入激烈的圣战,因为每个人都是正确的——在正确的情况下。这些方法最终都是错误的。唯一正确的方法是定义什么对你很重要,然后根据它来衡量

于 2009-06-29T04:59:04.590 回答
5

我会选择第二个选项。如果循环中的所有逻辑都完全不同,那么制作 2 个 for 循环是有意义的,但情况是有些逻辑是相同的,有些是基于条件的附加逻辑。所以第二种选择更干净。

第一个选项会更快,但速度稍快,而且我只会在发现那里存在瓶颈时才使用它。

于 2009-06-29T04:48:41.780 回答
5

您可能会在方法开始/结束时在无意义且不必要的 [blocksToKill retain]/[blocksToKill release] 上浪费更多时间,而不是执行几十个比较所花费的时间。无需保留数组,因为您返回后将不再需要它,并且在此之前它永远不会被清理。

恕我直言,代码重复是导致错误的主要原因,应尽可能避免。

添加 Jens 建议以使用快速枚举和 Antti 建议以使用明确命名的布尔值,您会得到如下内容:

    - (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;
    }

如果你完成了你的项目并且如果你的程序运行得不够快(两个大的 if),那么你可以分析它并找到热点,然后你可以确定你花在考虑那个分支上的几微秒是否值得考虑——当然是现在根本不值得考虑,这意味着唯一的考虑是哪个更具可读性/可维护性。

于 2009-06-29T05:50:09.050 回答
4

我的投票强烈支持第二个区块。

第二个块明确了逻辑上的区别,并共享相同的循环结构。它更具可读性和可维护性。

第一个块是过早优化的一个例子。

至于使用 bool 来“保存”所有这些 LTE 比较——在这种情况下,我认为它不会有帮助,机器语言可能需要完全相同数量和大小的指令。

于 2009-06-29T04:49:56.007 回答
3

“if”测试的开销是少量的 CPU 指令;不到一微秒。除非您认为循环将运行数十万次以响应用户输入,否则这只是在噪音中消失了。所以我会选择第二种解决方案,因为代码更小更容易理解。

不过,无论哪种情况,我都会将循环更改为

for (temp in blocksToKill) { ... }

这比手动获取数组的每个元素更清晰,而且速度更快。

于 2009-06-29T04:49:57.753 回答
0

可读性(以及可维护性)可以而且应该以性能的名义牺牲,但是当且仅当确定性能是一个问题时

第二个块更具可读性,除非/直到速度成为问题,否则它会更好(在我看来)。在测试您的应用程序期间,如果您发现此循环导致无法接受的性能,那么请尽一切努力使其更快,即使它变得更难维护。但是,除非你必须这样做,否则不要这样做。

于 2009-06-29T05:16:06.067 回答