【问题标题】:Is a single loop with several statements and conditions better than several simple loops?包含多个语句和条件的单个循环是否比多个简单循环更好?
【发布时间】:2016-12-18 16:29:52
【问题描述】:

我正在创建一个非常简单的程序,该程序使用贪心算法确定您需要多少硬币才能将零钱返还给客户。 该算法非常明显,您只需要确定您可以使用哪个更大的硬币,从零钱中减去它的值并更新硬币计数器。

我想到了两个非常相似的实现。

注意:changeInt 是变化,乘以 100 转化为整数。

1) 单个“复杂”循环

while(changeInt != 0) {
       if(changeInt - 25 >= 0){
           changeInt -= 25;
           coins++;
       }
       else if(changeInt - 10 >= 0){
           changeInt -= 10;
           coins++;
       }
       else if(changeInt - 5 >= 0){
           changeInt -= 5;
           coins++;
       }
       else if(changeInt - 1 >= 0){
           changeInt -= 1;
           coins++;
       }

   }

2) 多个简单循环

    while(changeInt - 25 >= 0) 
   {
       changeInt -= 25;
       coins++;
   }
    while(changeInt - 10 >= 0)
   {
       changeInt -= 10;
       coins++;
   }

    while(changeInt - 5 >= 0)
   {
       changeInt -= 5;
       coins++;
   }

    while(changeInt - 1 >= 0)
   {
       changeInt -= 1;
       coins++;
   }

现在,我知道两种情况下的性能可能相似,因为算法相同,但我想知道哪种方法更好。

单循环是我的第一个想法,后来我想到了第二种方法,直觉上我觉得更好。

我并不真正关心我的确切场景,我对一般场景更感兴趣(几个简单的循环与几个更复杂的循环)

1) 就性能而言,哪种方法更好?

2) 差异是否明显,至少在处理大量数字时?

3) 一种方法是否比另一种方法更具可读性? (不知道我是否可以在这里问)

谢谢!

【问题讨论】:

  • @mauroSabella:为什么是chageInt - X >= 0 而不是changeInt >= X?它们是等价的,所以这真的只是一个风格问题吗?你为什么选择这样做?
  • @trincot 你是什么意思?我得到相同的结果。为什么说算法不一样?据我所知,两者都以完全相同的顺序做同样的事情。
  • 你的作业是不是跑得太慢了,或者你为什么关心初步优化?专注于编写 可读* 代码,直到您真正遇到问题。然后分析您的代码并仅优化热点。
  • 就个人而言,我发现第二个代码更具可读性,但是 YMMV。
  • @mauroSabella 这不仅仅是比较的风格问题。对于像这样的小数字,这不是问题,但对于大数字,减法可能会使值超出类型的范围。直接对比一下,更容易阅读,对处理器的工作量更少,更安全。双赢。

标签: c algorithm performance readability


【解决方案1】:

正如其他人所提到的,第二种方法更可取,因为它使用较少的比较。

更简洁、更简洁的方法是使用除法和模数:

int current = changeInt;
coins += current / 25;
current %= 25;
coins += current / 10;
current %= 10;
coins += current / 5;
current %= 5;
coins += current;

虽然 div 和 mod 运算符比减法更昂贵,但对于较大的 changeInt 值可能更快,并且没有分支。

【讨论】:

  • 注意current < 0时功能不同。
【解决方案2】:

如果您必须在您描述的循环方法之间进行选择,则第二种方法更可取(略有不同)。它更干净,并且主要避免了不必要的测试。

这是细微的变化......

while(changeInt >= 25) {
   changeInt -= 25;
   coins++;
}

while(changeInt >= 10) {
   changeInt -= 10;
   coins++;
}

while(changeInt >= 5) {
   changeInt -= 5;
   coins++;
}

while(changeInt > 0) {
   changeInt -= 1;
   coins++;
}

它提供的主要优点是它有助于确保“changeInt - X”永远不会回绕。根据您在帖子中的描述,这不太可能成为问题,但如果类型从有符号整数更改为无符号整数,那么您可能会发现自己试图找出错误所在。

或者,您可能希望结合使用除法和取模运算符来计算变化并避免循环。

希望这会有所帮助。

【讨论】:

  • 感谢您的回答。你对你的变化是绝对正确的,因为你是关于使用模运算符的可能性。但是,我对使用一些复杂循环而不是许多简单循环的一般想法更感兴趣。我认为第二种方法更好,因为它总体上需要较少的比较(在我的情况下),但我真的不知道这如何影响性能。处理大量数字时是否有明显的差异(甚至很小),还是真的只是可读性问题? (当然,使用模数会更好,但我对比较感兴趣)
  • @mauroSabella:在您清楚地测量出一个比另一个对您的代码更有效之前,重要的是大 O 表示法和可读性。
【解决方案3】:

正如你所说,两者非常相似,如果我们谈论可读性,我更喜欢第一个(但这是一种主观意见)。

如果我们考虑性能,恕我直言,第二个比前一个快一点。我们可以尝试翻译到 ASM 进行详细比较:

1) 单个“复杂”循环(aprox ASM x86-64)

jmp
mov
sub
test
js
sub
add
jmp
mov
sub
test
js 
sub
add
jmp 
mov 
sub 
test
js  
sub 
add 
jmp 
mov 
sub 
test
js 
sub
add
cmp
jne

2) 多个简单循环(aprox ASM x86-64)

jmp
sub
add
mov
sub
test
jns
jmp
sub
add
mov
sub
test
jns
jmp
sub
add
mov
sub
test
jns
jmp
sub
add
mov
sub
test
jns

如果我们计算 x86-64 ASM 指令:

jmp: 4
mov: 4
sub: 8
test: 4
add: 4
js: 4
cmp: 1
jne: 1

对比

jmp: 4
mov: 4
sub: 8
test: 4
add: 4
jns: 4

然后总结一下:

js 4
cmp 1
jne 1

jns 4

而“js”与“jns”类似。

但这可能会随着其他编译器或架构而改变,即便如此,我认为第二个比第一个快一点。

【讨论】:

  • 虽然我尊重你的计数,但我认为这是错误的。当流水线化时,大多数指令在零时钟内有效执行。只有(有条件的)跳转/分支是昂贵的,因为处理器将采用 both 路径(并回滚错误的路径)。 循环内的条件太多总是会在某处搞砸管道。 (除了寄存器分配/缓存局部性等等)通常情况下,thin loop 将在 about 零时钟内执行循环体,并在更多时钟内执行循环机制.
  • 非常感谢,这正是我要问的。
  • @wildplasser 我认为你是对的,我简化了答案,但是如果你认为在你(非常重要的)考虑中,你的答案应该是一样的,因为第一个有很多循环条件。
【解决方案4】:

我认为没有理由为此需要循环。添加临时变量以防您需要保留原始值:

int tempChangeInt;

tempChangeInt = changeInt;
coins = changeInt / 25;
tempChangeInt = changeInt % 25;
if (tempChangeInt != 0)
{
    coins += tempChangeInt / 10;
    tempChangeInt = tempChangeInt % 10;
}
if (tempChangeInt != 0)
{
    if (tempChangeInt >= 5)
        coins += (tempChangeInt - 5) + 1;
    else
        coins += tempChangeInt;
}

【讨论】:

  • 我不需要这样做的循环。我用循环来说明我的问题是什么,即在几个复杂循环和几个简单循环之间哪种方法更好。
  • 虽然这可能是正确的,但它不能回答问题。
  • 注意tempChangeInt < 0时功能不同。
猜你喜欢
  • 1970-01-01
  • 2019-03-17
  • 1970-01-01
  • 2017-12-11
  • 1970-01-01
  • 2011-12-07
  • 2019-11-23
  • 1970-01-01
  • 2018-05-27
相关资源
最近更新 更多