[英]What causes so big performance difference between two ways of building vector of static_vectors?
我編寫了一個基准測試,用於測試將boost :: container :: static_vector修改和推入std :: vector的兩種方法。 他們似乎以奇怪的方式表演。
第一個:
typedef boost::container::static_vector<int, 4> Bar;
void foo(Bar& bar, std::vector<Bar>& bars)
{
const auto oldSize = bar.size();
bar.emplace_back(4);
bars.emplace_back(bar);
bar.resize(oldSize);
}
它修改本地版本,將其復制到引導程序上,然后將本地版本還原為原始狀態。
第二個:
typedef boost::container::static_vector<int, 4> Bar;
void foo(Bar& bar, std::vector<Bar>& bars)
{
bars.emplace_back(bar);
bars.back().emplace_back(4);
}
無需修改和還原本地版本,只需直接在vector上修改其副本即可。
我的理由是,兩個版本中的vector::emplace_back
都應具有相同的性能,因為它們vector::emplace_back
復制固定大小的對象。 此外,兩個foo實現static_vector::emplace_back
一次調用static_vector::emplace_back
。 resize
似乎很便宜。 它應該歸結為檢查oldSize
是否小於當前bar
尺寸(始終是)並覆蓋內部bar
尺寸變量。 顯然,這里沒有析構函數調用。 第二個模擬調用vector::back
,它處理堆內存。
總而言之,我希望兩個版本都具有相同的性能,甚至可能在第一個版本方面稍有優勢(看到它只處理堆內存一次,與第二個相反)。 然而,簡單的基准測試顯示了完全其他的結果。
基准測試:
std::vector<Bar> bars;
for(int i = 0; i < 100; ++i)
{
bars.clear();
Bar bar = {5, 6, 7};
for(int j = 0; j < 10000000; ++j)
foo(bar, bars);
}
第一:
real 0m8,044s
user 0m7,846s
sys 0m0,152s
第二:
real 0m5,754s
user 0m5,559s
sys 0m0,184s
問題是,這種巨大的差異從何而來?
奇怪的是,更改static_vector
容量似乎可以縮小此差距。 例如
typedef boost::container::static_vector<int, 16> Bar;
消除任何時差。 為什么較小的static_vector
容量在這兩種方式之間static_vector
如此大的區別,即使較大的static_vector
似乎不受此問題影響?
我將g ++ 8.3.0與-Ofast -std=c++17 -flto
標志-Ofast -std=c++17 -flto
使用。
編輯:
這是更長的基准測試,具有更大的static_vector
容量(8):
std::vector<Bar> bars;
for(int i = 0; i < 1000; ++i)
{
bars.clear();
Bar bar = {5, 6, 7, 8, 9, 10, 11};
for(int j = 0; j < 10000000; ++j)
foo(bar, bars);
}
1m42,282s
和1m31,387s
仍然顯示相似的差異。 確實,第一個實現可以擺脫它只是復制更多字節的解釋。 但是問題是,為什么類似的基准測試將容量提高到16,卻僅顯示2m46,733s
和2m45,128s
。 我希望看到相同的11s差異。
首先,我們必須認識到兩種方法實際上並不等效。 我們稱它們為fooA
和fooB
。
typedef boost::container::static_vector<int, 4> Bar;
void fooA(Bar& bar, std::vector<Bar>& bars)
{
const auto oldSize = bar.size();
bar.emplace_back(4);
bars.emplace_back(bar);
bar.resize(oldSize);
}
void fooB(Bar& bar, std::vector<Bar>& bars)
{
bars.emplace_back(bar);
bars.back().emplace_back(4);
}
fooA
當我們調用fooA
,會發生以下情況:
bar
的大小 4
附加到bar
的末尾。 如果失敗,則會引發異常,並且該函數將不添加任何內容而直接退出bars
。 bar
可能處於無效狀態。 bar
被復制到bars
bar
恢復到其原始狀態。 fooB
fooB
在成功情況下具有相同的行為,但在失敗情況下具有不同的行為 。
bar
復制到bars
bars
的末尾將4
附加到新向量上。 如果失敗,則拋出異常,函數退出(但在bars
的末尾附加了一些內容)。 bar
永遠不會處於無效狀態,因為它從未更改。 在成功情況下, fooA
和fooB
的行為是相同的(無例外),但是在引發異常時它們的行為有所不同。 結果,編譯器無法對它們兩者應用相同的優化, 也無法刪除諸如resize
類的行為。 這導致fooB
更快。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.