2010-05-16 85 views
25

什么是根据效率编写循环的最佳方法: 方式)我应该优化还是让编译器来做这件事?

/*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()一次的好处。
期待您的回答。

+8

请注意'firstString.size()'只会调用一次,所以不值得将它移出循环。 – 2010-05-16 17:02:25

+0

或者你可以使用迭代器并完全避免这个问题。 – 2010-05-16 19:21:57

+3

请勿使用“无符号”;不能保证“std :: size_t”将与所有平台上的无符号相同(例如,它可以是“无符号长整数”)。相反,使用“std :: size_t”。 – 2010-05-17 03:05:58

回答

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 
} 
+18

踢自己现在从未想过在11年内这样做! – 2010-05-16 17:07:45

+0

+1不知何故,我总是错过了可以在for循环中初始化多个变量的事实。 – NotMe 2010-05-16 18:33:18

+0

在迭代器中非常方便,如果你不想使用for_each;) – knittl 2010-05-16 18:46:26

15

我会先用第一个版本,仅仅是因为它看起来更清洁,更容易输入。然后,您可以对其进行配置,以查看是否需要进一步优化。

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

inline size_t size() const 
{ 
    return _internal_data_member_representing_size; 
} 

那么编译器应该能够内联函数,eliding函数调用。我编译器的标准容器的实现都是这样做的。

3

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

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

17

一般来说,让编译器去做。关注你正在做的算法的复杂性而不是微观优化。

但是请注意,你的两个例子并非语义相同 - 如果循环体,改变第二串的大小,两个循环不会重复的次数是相同的。出于这个原因,编译器可能无法执行您正在讨论的特定优化。

+7

+1表示这两段代码不执行相同的操作。 – 2010-05-16 16:44:51

7

一个好的编译器如何优化你的代码?一点也不,因为它不能确定size()有任何副作用。如果size()有任何代码依赖的副作用,那么在可能的编译器优化之后,它们现在会消失。

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

+3

即使使用可变字符串,使size()成为O(1)操作也很容易。 – sepp2k 2010-05-16 16:25:44

+0

@ sepp2k:是的,但你不知何故需要在字符串内容改变之后懒散地评估大小(即使用脏标志)。因此它不是_always_ O(1),而不可变的字符串可以在创建时计算它们的大小。不过,这个问题并不重要。 – 2010-05-16 16:31:09

+2

@JohannesRudolph:是吗?更新每个破坏性操作的长度会出现什么问题?例如。当附加一个大小为n的字符串时,“length + = n”。在执行操作时,是否有操作在O(1)时间内无法轻松更新长度? (是的,这是无关的,只是问)。 – sepp2k 2010-05-16 16:37:54

1

以下是我的看法。表演和风格都很重要,你必须在两者之间进行选择。

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

6

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

+0

通常,我会说写一个最清洁的代码可以让优化器变得更容易。 – 2010-05-18 06:49:28

+0

此答案中的链接目前指向一个实际上没有文章的博客。 – cHao 2012-10-31 16:01:23

2

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

你不应该。任何涉及

  • 呼叫一个未知功能或
  • 呼叫可能被重写

的方法是难的C++编译器优化。你可能会很幸运,但你不能指望它。

不过,因为你找到的第一个版本简单,更易于阅读和理解,你应该写的代码完全是在简单的例子所示的方式,在循环中调用size()。您应该考虑第二个版本,其中只有当您的应用程序太慢时以及您有测量结果显示此循环是瓶颈时,才会有额外的变量将常见呼叫从循环中移出,

+4

C++没有“未知函数”或“已知函数”。 – 2010-05-16 16:57:44

+0

也许编译器可以知道STL字符串的size()函数没有效果,因为编译器可以提供它们自己的STL实现。 – Kamchatka 2010-05-16 18:57:47

+0

但std :: string是std :: basic_string的一个特例,它是一个模板,所以它已经具有可用的定义。 – 2010-05-17 03:15:26

3

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

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

+0

+1用于指出*算法>编码*的性能。 – 2010-05-17 03:19:42

1

除非您有证据(通过分析器获得)证明这部分代码是瓶颈,否则不应优化代码。不必要的代码优化只会浪费你的时间,它不会改善任何事情。

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

+1

你也不应该悲观代码。你也可以浪费数小时来试图引用Knuth和优化技术的进步,只是为了获得0.000%的性能提升。 – peterchen 2010-05-17 08:50:39

+0

我越来越认为,“从不微观优化”阵营与“自由微观优化”一样错误。你永远不应该永远不说。 – 2010-05-26 20:08:24

+0

@John Dibling:我的意思是说,你应该只根据工具提供的数据(profilers)优化事情,而不是盲目地通过“直觉”或其他方式来优化。如果你认为循环会变得很慢,你应该测量它需要多少时间,以及通过修改事情获得多少额外时间。另外,看看更大的图片是有意义的。功能性能提升200%没有意义,只占总程序执行时间的0.5%以下。 – SigTerm 2010-05-26 20:43:31

1

方法(b)如果你只是想写一些可能不会比(a)方式更糟的东西,并且可能更快,那么没有什么不对。它也使得它更清晰,你知道字符串的大小将保持不变。

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

(另外,在first式(b)中的变量是不必要的;用于初始化表达式的代码只运行一次)

0

对于“标准::的size_t尺寸()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++){ 
     // ... 
} 

另外,我要指出,你应该使用“标准::为size_t”而不是“无符号“,因为”std :: size_t“的类型可能因平台而异,而使用”unsigned“会导致”std :: size_t“类型为”无符号长“类型的平台出现转折和错误“unsigned int”。

1
  1. 的时间是多少%的时间在for而不是// do something? (不要猜测 - 对其进行抽样。)如果是10%,那么其他地方的问题可能会更大。

  2. 大家都说“现在编译器非常聪明。” 那么他们并不比编写它们的穷人更聪明。 你也需要很聪明。也许编译器可以优化它但为什么诱惑它不?