对于循环,检查循环外部向量的大小是否更快?

时间:2018-04-01 07:08:29

标签: c++ for-loop

我的问题是关于时间优化。 这样做for循环更快:

std::vector<int> myVec = {0,1,2,3};
for(int i = 0; i < myVec.size(); i++){}

或者最好先预先计算尺寸吗?

std::vector<int> myVec = {0,1,2,3};
int myVecSize = myVec.size();
for(int i = 0; i < myVecSize ; i++){}

我不仅在这里想知道纯粹的时间执行,还在于它是否会以某种方式导致某些问题

5 个答案:

答案 0 :(得分:4)

通常,编译器需要在循环的每次迭代中调用size函数。 C ++没有函数纯度的概念,除非编译器具有函数定义的可见性,否则它不能假定函数没有副作用(如日志语句)。

现在stl向量在头文件中定义,因此编译器确实具有可见性。现在给出size的简单性,它可​​以很好地内联和优化。

答案 1 :(得分:2)

根本不重要。优化编译器将删除那两行或三行,它们什么都不做。

严重。如果编译器可以推断出一个容器是否在循环中没有被改变,那么它将进行你手动完成的优化。为了帮助编译器应用优化,您甚至可以声明容器是常量(例如vector):

const std::vector<int> myVec = {0,1,2,3};

答案 2 :(得分:2)

一般来说,答案取决于您的意见。但是,假设您循环遍历标准容器的元素(如std::vector<int>),那么它还取决于循环的作用。

如果循环执行任何调整向量大小的操作,那么在每次迭代时将索引与大小进行比较是必要的,因为调整向量大小会使所有迭代器无效,并且当索引不再有效时可能导致使用索引(导致未定义的行为)。

作为一个粗略的规则,如果你没有在循环体中调整向量的大小,通常使用迭代器比使用索引更好。使用索引需要向量的operator[]()函数在每次调用时找到感兴趣的元素,而迭代器提供对特定元素的更直接访问

  // case where vector is not resized

 for (std::vector<int>::iterator i = myVec.begin(), end = myVec.end();  i != end; ++i)
 {
      //    do something with the iterator i
 }

在C ++ 11及更高版本中,如果循环体没有调整容器的大小,只需使用一个适用于每个元素的基于范围的for循环

for (auto &element : myVec)
{
     // do something with element
} 

答案 3 :(得分:1)

根据我的经验,我会使用没有myVecSize变量的第一个选项,因为它是多余的,并且使代码的可读性和可读性更低。另外我认为上面提到的这些原因在编码中比执行时间差异更重要,你可能甚至都不会注意到。

答案 4 :(得分:0)

像往常一样,它取决于: - )

如果向量是局部变量,那么足够聪明的编译器应该能够推断出在循环体中大小不会改变,即使调用具有隐藏实现的函数。

如果向量尚未在本地创建(例如,它已经传递给带引用的函数 - 如果是const引用则不相关)那么编译器必须是偏执的以及任何无法完全理解的内容循环的主体(即任何不可能内联到基本操作的东西)理论上可以改变向量的大小,因此生成的代码必须在每次迭代时检查可能改变的大小和重定位。

请注意,即使在本地创建了向量的情况下,编译器也必须假定任何有关向量对象地址的外部函数可能会立即甚至在将来改变对象...例如:

void foo() {
    std::vector<int> myvec{1,2,3,4};
    printf("%p\n", (void *)&myvec); // Address of the vector object
    for (int i=0; i<int(myvec.size()); i++) {
        printf("Hello, world (%i)\n", int(myvec.size()));
        printf("Hello again (%i)\n", int(myvec.size()));
    }
}

编译器不能假设循环将被执行4次,并且它甚至不能假设在printf函数的第一次和第二次调用之间大小没有改变。 原因是对象的地址已经传递给循环外部调用中的全局外部函数,并且此函数可能已经更改了对象,甚至可以存储该地址以在以后的调用中对其进行变更。

在上面的情况下,编译器可以使用特殊标记printf来知道这是一个“行为良好”的函数,它不会做那样的事情......但是如果不是{{1}这是一个通用的用户定义的printf函数,其主体在编译时是未知的,那么必须使用“偏执模式”。

遗憾的是,没有可移植的C ++方法来声明函数是无副作用的,并向编译器保证这种不好的东西不会发生。