根据效率编写循环的首选方法是什么: 方式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()。
期待您的回答。
答案 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)
for
与// do something
相比,花费了多少时间? (不要猜测 - 对其进行抽样。)如果是&lt; 10%你可能在其他地方有更大的问题。
每个人都说“这些日子编译器非常聪明。” 嗯,他们并不比写作他们的穷人更聪明。 你也需要聪明。也许编译器可以优化它,但为什么不诱惑呢?
答案 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和错误。