我应该优化还是让编译器这样做?

时间:2010-05-16 16:13:54

标签: c++ optimization performance

根据效率编写循环的首选方法是什么: 方式a)

   /*here I'm hoping that compiler will optimize this  
 code and won't be calling size every time it iterates through this loop*/
    for (unsigned i = firstString.size(); i < anotherString.size(), ++i)
    {
    //do something
    }

或者我应该这样做: 方式b)

unsigned first = firstString.size();
unsigned second = anotherString.size();

现在我可以写:

    for (unsigned i = first; i < second, ++i)
    {
    //do something
    }

第二种方式在我看来更糟糕的选择有两个原因:范围污染和详细程度,但它的优点是确保每个对象都会调用一次size()。
期待您的回答。

13 个答案:

答案 0 :(得分:62)

我通常把这段代码写成:

/* i and size are local to the loop */
for (size_t i = firstString.size(), size = anotherString.size(); i < size; ++i) {
  //do something
}

这样我就不会污染父作用域,也不会为每次循环迭代调用anotherString.size()

这对迭代器特别有用:

for(some_generic_type<T>::forward_iterator it = collection.begin(), end = collection.end();
    it != end; ++it) {
   // do something with *it
}

答案 1 :(得分:17)

通常,让编译器执行此操作。专注于你正在做的事情的算法复杂性,而不是微观优化。

但是,请注意,您的两个示例在语义上不相同 - 如果循环体改变第二个字符串的大小,则两个循环将不会迭代相同的次数。因此,编译器可能无法执行您正在讨论的特定优化。

答案 2 :(得分:15)

我会首先使用第一个版本,因为它看起来更干净,更容易打字。然后,您可以对其进行分析,以查看是否需要对任何内容进行更优化。

但我非常怀疑第一个版本会导致显着的性能下降。如果容器像这样实现size()

inline size_t size() const
{
    return _internal_data_member_representing_size;
}

然后编译器应该能够内联函数,省略函数调用。我的编译器对标准容器的实现都是这样做的。

答案 3 :(得分:7)

优秀的编译器如何优化您的代码?完全没有,因为无法确定size()是否有任何副作用。如果size()有代码所依赖的任何副作用,那么在可能的编译器优化之后它们就会消失。

从编译器的角度来看,这种优化实际上并不安全,您需要自己完成。自己动手并不意味着你需要引入另外两个局部变量。根据您的大小实现,它可能是O(1)操作。如果size也是内联声明的,那么你也可以省去函数调用,使size()的调用与本地成员访问一样好。

答案 4 :(得分:6)

Don't pre-optimize您的代码。如果遇到性能问题,请使用分析器查找,否则会浪费开发时间。只需编写最简单/最干净的代码即可。

答案 5 :(得分:3)

这是你应该自己测试的事情之一。运行循环10,000或甚至100,000次迭代,看看有什么区别(如果有的话)。

这应该告诉你你想知道的一切。

答案 6 :(得分:3)

我的建议是让无关紧要的优化渗透到你的风格中。我的意思是,如果你学习了一种更优化的做事方式,并且你看不出它的任何缺点(就可维护性,可读性等而言)那么你也可以采用它。

但不要痴迷。对于您测量过的非常小的代码段,应该保存牺牲可维护性的优化,并且KNOW将对您的应用程序产生重大影响。当您决定进行优化时,请记住为工作选择正确的算法通常比严格的代码重要得多。

答案 7 :(得分:2)

  

我希望编译器能优化这个......

你不应该。任何涉及

的事情
  • 调用未知函数
  • 调用可能被覆盖的方法

很难让C ++编译器进行优化。你可能会很幸运,但你不能指望它。

尽管如此,因为您发现第一个版本更简单,更易于阅读和理解,您应该完全按照简单示例中显示的方式编写代码,并调用{{1} } 在循环。您应该考虑第二个版本,其中有额外的变量将公共呼叫拉出循环,仅当您的应用程序太慢并且如果您有测量结果显示此循环是瓶颈。

答案 8 :(得分:1)

这是我看待它的方式。表演和风格都很重要,你必须在两者之间做出选择。

您可以尝试一下,看看是否有性能损失。如果有不可接受的性能损失,请选择第二个选项,否则请随意选择样式。

答案 9 :(得分:1)

您不应该优化您的代码,除非您有一个证明(通过分析器获得)这部分代码是瓶颈。不必要的代码优化只会浪费你的时间,它不会改进任何东西。

您可以浪费数小时来尝试改善一个循环,但只能获得0.001%的性能提升。 如果您担心性能 - 请使用分析器。

答案 10 :(得分:1)

如果你只是想写一些可能不比(a)更糟糕的东西,并且可能更快,那么方式(b)没有什么问题。更清楚的是,您知道字符串的大小将保持不变。

编译器可能会或可能不会发现size将保持不变;为了以防万一,您可以自己执行此优化。如果我怀疑我写的代码会运行很多,我肯定会这样做,即使我不确定它会是一个大问题。它非常简单,只需要花费不超过10秒的时间来思考它,它不太可能减慢速度,而且,如果没有其他的话,几乎肯定会使未经优化的构建运行得更快一些。

(样式(b)中的first变量也是不必要的; init表达式的代码只运行一次。)

答案 11 :(得分:1)

  1. for// do something相比,花费了多少时间? (不要猜测 - 对其进行抽样。)如果是&lt; 10%你可能在其他地方有更大的问题。

  2. 每个人都说“这些日子编译器非常聪明。” 嗯,他们并不比写作他们的穷人更聪明。 你也需要聪明。也许编译器可以优化它,但为什么不诱惑呢?

答案 12 :(得分:0)

对于“std :: size_t size()const”成员函数,它不仅是O(1),而且还被声明为“const”,因此可以被编译器自动拉出循环,它可能不会无所谓。也就是说,我不会指望编译器将它从循环中删除,并且我认为在函数不是常数或O(1)的情况下进入循环中的调用是一个好习惯。 )。另外,我认为将值赋给变量会导致代码更具可读性。但是,我不建议您进行任何过早的优化,否则会导致代码难以阅读。但是,我认为以下代码更具可读性,因为在循环中读取的内容较少:

 std::size_t firststrlen = firststr.size();
 std::size_t secondstrlen = secondstr.size();
 for ( std::size_t i = firststrlen; i < secondstrlen; i++ ){
      // ...
 }

另外,我应该指出你应该使用“std :: size_t”而不是“unsigned”,因为“std :: size_t”的类型可能因平台而异,并且使用“unsigned”可以导致对于“std :: size_t”类型为“unsigned long”而非“unsigned int”的平台上的trunctations和错误。