[英]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.