正确的方法来检查循环内的std :: vector

时间:2014-08-27 16:27:55

标签: c++ c++11 vector stl std

我有一个std :: vector,我需要经常循环。我认为有两种方法可以做到这一点

第一种方式:

const size_t SIZE = myVec.size();
for (size_t i = 0; i < SIZE; i++)
{
    myVec[i] = 0;
}

第二种方式:

for (size_t i = 0; i < myVec.size(); i++)
{
    myVec[i] = 0;
}

第一个比第二个效率更高,或者现代编译器是否知道优化第二个实现以使其与第一个实现一样有效?

FWIW,我在Visual Studio 2013上。

5 个答案:

答案 0 :(得分:2)

即使使用现代编译器,第一个版本通常也会更快。优化器很难证明由于在循环体中写入位置的混叠而导致大小不会改变,因此在许多情况下,第二个版本将不得不重新计算每次循环迭代的大小。

我在Visual Studio 2013 Release中对此进行了测量,发现32位和64位代码的性能差异。两个版本都被std :: fill()轻松打败。这些测量值是1000次运行的平均值,包含1000万个元素向量(随着内存访问变得更加瓶颈,将元素数量增加到10亿度会略微降低性能差异。)

Method                   Time relative to uncached for loop
                         x86      x64

uncached for loop        1.00     1.00
cached for loop          0.70     0.98
std::fill()              0.42     0.57

循环代码的基线缓存大小:

const auto size = vec.size();
for (vector<int>::size_type i = 0; i < size; ++i) {
    vec[i] = val;
}

编译到此循环体(x86版本):

00B612C0  mov         ecx,dword ptr [esi]  
00B612C2  mov         dword ptr [ecx+eax*4],edi  
00B612C5  inc         eax  
00B612C6  cmp         eax,edx  
00B612C8  jb          forCachedSize+20h (0B612C0h)  

而不缓存矢量大小的版本:

for (vector<int>::size_type i = 0; i < vec.size(); ++i) {
    vec[i] = val;
}

编译为this,每次循环时重新计算vec.size():

00B612F0  mov         dword ptr [edx+eax*4],edi  
00B612F3  inc         eax  
00B612F4  mov         ecx,dword ptr [esi+4]            <-- Load vec.end()
00B612F7  mov         edx,dword ptr [esi]              <-- Load vec.begin()
00B612F9  sub         ecx,edx                          <-- ecx = vec.end() - vec.begin()
00B612FB  sar         ecx,2                            <-- exc = (vec.end() - vec.begin()) / sizeof(int)
00B612FE  cmp         eax,ecx  
00B61300  jb          forComputedSize+20h (0B612F0h)  

答案 1 :(得分:1)

我更喜欢像第一种情况一样编写循环。对于第二种情况和std::vector::size(),您可能会在编译器优化版本中支付一些额外的负载,但是当您开始使用更复杂的数据结构时,这些简单的负载可能会成为昂贵的树查找。

即使有偏好,上下文有时也需要您以第二种形式编写循环。第一种情况提示,由于容器大小被检查一次,因此容器的大小没有发生突变。当您阅读第二种情况时,每次迭代都会检查容器大小,这会向用户提示主体可能突变容器的大小。

如果要改变循环体中的容器,请使用第二个表单并注释您正在改变容器并想要检查其大小。否则,更喜欢第一个。

答案 2 :(得分:0)

如上所述here vector<T>::size的复杂性必须在所有编译器上保持不变,因此使用哪一个并不重要。

答案 3 :(得分:0)

在任何体面的现代C ++编译器中,两个版本在性能方面不会有任何差别,因为优化器会优化任何恶化。不过,我使用以下版本:

for (size_t i(0), ie(myVec.size()); i < ie; ++i) {
    // do stuff
}

答案 4 :(得分:0)

获取向量的大小始终是恒定时间。

如果您的算法效率较低,那么您对每个索引使用myVec[i]。它将提取指针并添加&#39; i&#39;每次都要这样。指针算法可能会因性能而失败,如果你使用vector&#39; iterator,因为它可能被实现为指针,它可能会胜过你的循环。 / p>

如果要将所有值设置为0,即使使用单个函数调用而不是循环,也可能表现优异,在这种情况下

myVec.assign( myVec.size(), 0 );