C++ for循环优化问题

6

我在VC++中有以下代码:

for (int i = (a - 1) * b; i < a * b && i < someObject->someFunction(); i++)
{
    // ...
}

据我所知,编译器会优化所有这些算术操作,并且它们不会在每次循环中执行,但我不确定它们是否能够判断上述函数每次返回相同的值,因此不需要每次调用。

是将所有计算保存到变量中更好,还是只依靠编译器优化以获得更易读的代码?

int start = (a - 1) * b;
int expra = a * b;
int exprb = someObject->someFunction();
for (int i = startl i < expra && i < exprb; i++)
{
    // ...
}

1
@Nawaz 只要 i 是整数,就不应该有任何区别。 - Timbo
2
@Nawaz:敢问你能找到一个不执行该优化的编译器吗? - Jon Purdy
@Nawaz:胡说八道。任何一个理智的编译器在2011年都会省略复制。 - Lightness Races in Orbit
1
感谢所有的回答!我会选择保存函数结果并保持表达式中的算术运算,因为保存它们只会创建不必要的变量名。 - sekmet64
@sekmet64:我同意。你可能会让代码变得过于复杂和难以阅读,从而抵消了优化的好处。 - Lightness Races in Orbit
显示剩余11条评论
6个回答

7
简短回答:这取决于情况。如果编译器能够推断每次运行someObject->someFunction()和缓存结果后产生相同的效果,则允许(但不保证)这样做。是否可能进行这种静态分析取决于您的程序:具体来说,someObject的静态类型是什么,它的动态类型预期是什么,以及someFunction()实际上执行什么操作,它是否是virtual等。
通常,如果只需要执行一次,请以这样的方式编写代码,使得它只能执行一次,从而避免担心编译器可能会做什么:
int start = (a - 1) * b;
int expra = a * b;
int exprb = someObject->someFunction();
for (int i = start; i < expra && i < exprb; i++)
    // ...

或者,如果你喜欢简洁:

for (int i = (a - 1) * b, expra = a * b, exprb = someObject->someFunction();
     i < expra && i < exprb; i++)
    // ...

@CashCow:我没有看到有人点踩,所以可能是在过去几分钟内更改了。 - Jon Purdy
关于编译器,我认为在gcc上使用pure属性会帮助编译器优化调用。显然这是非可移植的。 - Matthieu M.

6

根据我的经验,VC++编译器只有在编译调用代码时能看到函数实现才会优化函数调用。因此将调用移至循环外是一个好主意。


5
如果一个函数与其调用者位于同一编译单元中,编译器通常可以推断出一些关于它的事实 - 例如,它的输出可能不会在后续调用中更改。然而,通常情况下并非如此。
在您的示例中,为这些简单算术表达式分配变量实际上并没有改变有关生成的对象代码的任何内容,而且在我看来,使代码难以阅读。除非您有一堆长表达式无法合理地放入一两行内,否则应避免使用临时变量 - 即使只是为了减少命名空间污染。
使用临时变量意味着程序员需要进行重大的管理开销,以使它们保持分开并避免意外副作用。这也使得重用代码片段更加困难。
另一方面,将函数的结果分配给变量可以帮助编译器更好地优化您的代码,明确避免多个函数调用。
个人而言,我会选择这样做:
int expr = someObject->someFunction();
for (int i = (a - 1) * b; i < a * b && i < expr; i++)
{
    // ...
}

1

编译器无法假设您的函数每次返回相同的值。想象一下,如果您的对象是一个套接字,编译器怎么可能知道它的输出是什么?

此外,编译器在这种循环中可以进行的优化强烈依赖于a和b是否声明为const以及它们是否为局部变量。通过先进的优化方案,它可能能够推断出a和b在循环中或在您的函数中都没有被修改(再次想象一下,您的对象是否持有对它们的某些引用)。

总之,选择您代码的第二个版本吧!


0

编译器很可能每次都会调用该函数。

如果您关心代码的可读性,那么考虑使用以下方式:

int maxindex = min (expra, exprb);
for (i=start; i<maxindex; i++)

我的看法是,长行并不能提高可读性。

编写短行并采取多步骤以获得结果,不会影响性能,这正是我们使用编译器的原因。


是的,通常在良好分解的代码上执行优化更简单。 - Greg

0

实际上,您可能正在询问编译器是否会内联函数someFunction(),以及它是否会在每个循环中看到someObject是相同的实例,如果两者都是,则可能“缓存”返回值并不断重新评估它。

这在很大程度上取决于您使用的优化设置,无论是VC++还是任何其他编译器,尽管我不确定VC++是否提供了与gnu一样多的标志。

我经常发现程序员依赖编译器来优化他们可以轻松优化的事情,这让我感到难以置信。如果您知道它每次评估时都会评估相同的表达式,请将其移动到for循环的第一部分:

只需这样做,不要依赖编译器:

for (int i = (a - 1) * b, iMax = someObject->someFunction();
       i < a * b && i < iMax; ++i) 
{
  // body
}

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