簡體   English   中英

是什么導致兩種構建static_vectors向量的方式之間的性能差異如此之大?

[英]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,282s1m31,387s仍然顯示相似的差異。 確實,第一個實現可以擺脫它只是復制更多字節的解釋。 但是問題是,為什么類似的基准測試將容量提高到16,卻僅顯示2m46,733s2m45,128s 我希望看到相同的11s差異。

首先,我們必須認識到兩種方法實際上並不等效。 我們稱它們為fooAfooB

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永遠不會處於無效狀態,因為它從未更改。

這是什么意思?

在成功情況下, fooAfooB的行為是相同的(無例外),但是在引發異常時它們的行為有所不同。 結果,編譯器無法對它們兩者應用相同的優化, 也無法刪除諸如resize類的行為。 這導致fooB更快。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM