2014-08-27 34 views
3

我有一個std :: vector,我需要經常循環。我看到這樣做的兩種方式是正確的方法來檢查一個循環內的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年

+7

任何像樣的編譯器*應*優化第二個;但唯一確定的方法是衡量它。 'for(auto&x:myVec)x = 0;'?或'std :: fill(myVec.begin(),myVec.end(),0)'? – 2014-08-27 16:31:44

+0

謝謝,我發佈的原因是因爲我喜歡瞭解大多數編譯器的行爲,而不僅僅是我目前使用的行爲。分析只告訴我有關當前編譯器 – 2014-08-27 16:34:13

+0

[循環中vector :: size()的性能問題]的重複可能性(http://stackoverflow.com/questions/3901630/performance-issue-for-vectorsize-in-a -loop) – quantdev 2014-08-27 16:34:30

回答

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; 
} 

編譯這個循環體(86版):

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; 
} 

Com堆到這,每次通過循環重新計算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) 
+0

+1的速度慢得多,以便進行實際測試。 – beerboy 2014-09-10 23:32:33

+0

謝謝,相反,標記爲答案 – 2014-09-25 15:10:36

0

如前所述here因此它並不真正的問題你使用哪一個的vector<T>::size複雜性必須對所有的編譯器常數。

+5

不一定,複雜度是不變的,但它不必像在本地存儲一樣。 – CashCow 2014-08-27 16:35:28

0

在任何體面的現代C++編譯器中,兩個版本在性能方面沒有任何差別,因爲優化器會優化任何惡化。儘管如此,我使用以下版本:

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

+1,但請使用'for(size_t i = 0,sz = myVec.size(); i TemplateRex 2014-08-27 18:42:12

+0

任何像樣的編譯器都會優化差異並不是真的。就像涉及表演的任何問題一樣,在做出像這樣的陳述之前,你應該真的測量。 – mattnewport 2014-09-10 21:50:56

+0

@mattnewport哦,這是非常非常真實的。只是碰巧你沒有線索。 – 101010 2014-09-10 21:55:18

0

獲取矢量的大小始終是恆定的時間。

如果您的算法可能效率較低,那麼您對每個索引使用myVec[i]。它會提取指針並每次添加'i'。指針算術很可能會超過它的性能,如果你使用vectoriterator,因爲這可能會作爲一個指針來實現,它可能會勝過你的循環。

如果您所有的值設置爲0你也許可以超越甚至用一個函數調用,而不是一個循環,在這種情況下

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

1

我喜歡寫我的圈像第一種情況。對於第二種情況和std::vector::size(),您可能會在編譯器優化版本中支付一些額外的負載,但是當您開始處理更復雜的數據結構時,這些簡單的負載可能會變成昂貴的樹查找。

即使首選,上下文有時也會要求您在第二個表單中編寫循環。第一種情況提示,由於容器大小被檢查一次,容器的大小沒有發生突變。閱讀第二種情況時,每次迭代檢查容器大小,這向用戶提示主體可能會改變容器的大小。

如果您在循環體中對容器進行變異,則使用第二個表單和註釋來改變容器並且想檢查它的大小。否則,更喜歡第一個。

相關問題