[英]Is `std::vector<primitive>::clear()` a constant time operation?
[英]std::vector::clear() takes more time after code refactoring
我有一個過程,該過程用從另一個數組獲取的值填充一些數組。 它看起來類似於以下代碼:
// Point 0
ptrlistVector.clear();
// Point 1
ptrlistVector.resize(50);
const size_t s = ptrlistVector.size();
// Point 2
for (ObjectList::iterator j = objList.begin(); j != objList.end(); ++j)
{
for (UINT i = 0; i < s; ++i)
{
ptrlistVector[i].push_back(&(*j));
}
}
// Point 3
實際上,“ push_back”行中有更復雜的代碼-我將不同的值推送到列表中。 該值取決於某些條件。
聲明和定義:
typedef std::list<void*> ObjectPtrList;
typedef std::vector<ObjectPtrList> PtrListVector;
typedef std::list<std::string> ObjectList;
ObjectList objList;
PtrListVector ptrlistVector;
我測量了兩點之間的時間,平均而言,1-0點花費0.02秒,而3-2點花費0.05秒。 我試圖重構循環並發現一些奇怪的行為。 我將以下循環替換為:
for (UINT i = 0; i < s; ++i)
{
for (ObjectList::iterator j = objList.begin(); j != objList.end(); ++j)
{
ptrlistVector[i].push_back(&(*j));
}
}
之后,時間發生了變化。 3-2點花費0.035秒,但是clear()調用(1-0點)現在花費0.45(!!!),這比上一次要大得多。
我使用MSVC 10.0,在“調試”和“發布”模式下,結果大致相同。 在“釋放”模式下,時間差異不是很大,但是無論如何,第二秒的時間會更大。
誰能解釋一下為什么更改循環后的clear()調用需要更多時間?
下面的代碼是我用於性能測試的控制台應用程序。
#include "stdafx.h"
#include <windows.h>
#include <vector>
#include <list>
#include <cstdio>
#include <cassert>
#include <string>
int _tmain(int argc, _TCHAR* argv[])
{
typedef std::list<void*> ObjectPtrList;
typedef std::vector<ObjectPtrList> PtrListVector;
typedef std::list<std::string> ObjectList;
ObjectList objList;
objList.insert(objList.begin(), 500, std::string());
PtrListVector ptrlistVector;
LARGE_INTEGER __counters[10];
double __totals[10] = { 0 };
UINT __counter = 0;
BOOL bRes;
LARGE_INTEGER __freq;
bRes = QueryPerformanceFrequency(&__freq);
assert(bRes);
for (int k = 0; k < 500; ++k)
{
// Point 0
bRes = QueryPerformanceCounter(&__counters[0]);
ptrlistVector.clear();
// Point 1
bRes = QueryPerformanceCounter(&__counters[1]);
ptrlistVector.resize(50);
const size_t s = ptrlistVector.size();
// Point 2
bRes = QueryPerformanceCounter(&__counters[2]);
/*
// original
for (ObjectList::iterator j = objList.begin(); j != objList.end(); ++j)
{
for (UINT i = 0; i < s; ++i)
{
ptrlistVector[i].push_back(&(*j));
}
}
/*/
for (UINT i = 0; i < s; ++i) // refactored
{
for (ObjectList::iterator j = objList.begin(); j != objList.end(); ++j)
{
ptrlistVector[i].push_back(&(*j));
}
}
//*/
// Point 3
bRes = QueryPerformanceCounter(&__counters[3]);
__counter += 1;
__totals[1] += 1.0 * (__counters[1].QuadPart - __counters[0].QuadPart) / __freq.QuadPart;
__totals[2] += 1.0 * (__counters[2].QuadPart - __counters[1].QuadPart) / __freq.QuadPart;
__totals[3] += 1.0 * (__counters[3].QuadPart - __counters[2].QuadPart) / __freq.QuadPart;
__totals[4] += 1.0 * (__counters[3].QuadPart - __counters[0].QuadPart) / __freq.QuadPart;
printf("%s: %.4f %.4f %.4f = %.4f\n",
__FUNCTION__,
__totals[1]/__counter,
__totals[2]/__counter,
__totals[3]/__counter,
__totals[4]/__counter);
}
return 0;
}
我想在此答案前加一個免責聲明-這是一個推測,因為我沒有運行問題中的代碼,也沒有查看所涉及的實際庫實現。 但是,我認為這為問題中描述的時間上的任何統計學上的顯着差異概述了一個可能的解釋。 但是,請記住,此時此刻是推測。
清除列表向量所花費時間的差異可能是由於使用堆的方式以及當堆正在處理銷毀列表時釋放的列表元素時正在進行的工作。 我認為當列表元素被第二種循環類型釋放時,堆中可能還有更多工作要做。 我只能猜測(我沒有逐步瀏覽庫代碼)。
在第一種循環方式中,每個列表在每次循環迭代中添加一個元素; 換句話說,循環迭代0
在每個列表中放置一個元素,然后循環迭代1
在每個列表中放置另一個元素,依此類推。
在第二個示例(其中clear()
操作花費更長的時間)中,每個列表都是單獨建立的; 換句話說,將填充ptrlistVector[0]
的列表,然后填充ptrlistVector[1]
,依此類推。
我猜想對於第一個循環樣式,特定列表上的每個元素都不連續(在地址空間中)與列表中的其他元素連續。 那是因為在特定列表上的任何兩個push_back()
操作之間的時間內,發生了50
其他分配,以將元素添加到其他列表。
但是,我猜想在第二種循環樣式中,特定列表中的元素或多或少是連續的,因為這是分配發生的順序。
現在,讓我們考慮一下銷毀列表時可能意味着什么(清除包含列表的向量時會發生這種情況)。 對於元素在地址空間中連續的列表,堆可能會花費大量時間來合並那些相鄰的空閑塊。 但是,當具有一堆不相鄰的元素的列表釋放其元素時,釋放的內存塊將不相鄰,因此不會發生合並。 直到我們到達最后(或最后幾個)列表時,堆才可能開始看到可以合並的相鄰可用內存塊。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.