在函数中我需要在向量中存储一些整数。该函数被多次调用。我知道它们不到10,但每次调用函数的数量都是可变的。有更好的表现有什么选择?
在示例中我发现了这个:
std::vector<int> list(10)
std::vector<int>::iterator it=list.begin();
unsigned int nume_of_elements_stored;
for ( ... iterate on some structures ... ){
if (... a specific condition ...){
*it= integer from structures ;
it++;
nume_of_elements_stored++;
}
}
慢于:
std::vector<int> list;
unsigned int num_of_elements_stored(0);
for ( ... iterate on some structures ... ){
if (... a specific condition ...){
list.push_back( integer from structures )
}
}
num_of_elements_stored=list.size();
答案 0 :(得分:4)
我要在这里走一条非常不酷的路线。有被钉十字架的风险,我建议std::vector
在这里不是那么好。例外情况是,如果你对内存分配器感到幸运,并通过分配器获得那个时间局部性,那么创建和销毁一堆通常无法提供的少量vectors
。
<强>等待!强>
在人们杀了我之前,我想说vector
很棒,一般来说,它是最全面的数据结构之一。但是当你在一个紧密的循环中反复创建一堆小小的vectors
时,你正在寻找这样的热点(希望有一个分析器),这就是这种直接用法的地方vector
可以咬你。
问题在于它是一个堆分配的结构(基本上是一个动态数组),当我们处理这样的少量阵列时,我们真的想要使用经常缓存的内存我们可以在堆栈的顶部分配/免费这么便宜。
缓解此问题的一种方法是在重复调用中重复使用相同的向量。将其存储在外部来电者功能的范围内并通过引用传递,clear
,进行push_backs
,冲洗并重复。值得注意的是clear
并没有释放向量中的任何内存,因此它保留了以前的容量(当我们想要重用相同的内存并播放时间局部性时,这里很有用)。
但在这里我们可以玩到那个堆栈。作为一个简化的例子(使用C风格的代码,它在C ++中不是非常犹太,甚至是异常安全的烦恼,但更容易说明):
int stack_mem[32];
int num = 0;
int cap = 32;
int* ptr = stack_mem;
for ( ... iterate on some structures ... )
{
if (... a specific condition ...)
{
if (num == cap)
{
cap *= 2;
int* new_ptr = static_cast<int*>(malloc(cap * sizeof(int)));
memcpy(new_ptr, ptr, num * sizeof(int));
if (ptr != stack_mem)
free(ptr);
ptr = new_ptr;
}
ptr[num++] = your_int;
}
}
if (ptr != stack_mem)
free(ptr);
当然,如果你使用这样的东西,你应该将它正确地包装在一个可重用的类模板中,该模板进行边界检查,不使用memcpy
,具有异常安全性,正式的push_back方法, emplace_back,copy ctor,move ctor,swap,可能是fill ctor,range ctor,erase,range erase,insert,range insert,size,empty,iterators / begin / end,使用placement new来避免需要复制赋值或默认ctor,等
解决方案在N <= 32
时使用堆栈(可以使用适合您的常见需求的不同数字),然后在超出时切换到堆。这使得它可以有效地处理您的常见案例场景,但在N
可能在某些病态情况下可能很大的情况下,也不仅仅是在那些罕见的情况下变得无用。这使得它在某种程度上可以与C中的可变长度数组相比(我实际上希望我们在C ++中有这样的东西,至少在std::dynarray
可用之前),但没有堆栈溢出趋势,因为在极少数情况下它会切换到堆场景。
我将所有这些符合标准的形式与基于此想法的结构应用于接受<T, FixedN>
的类模板,现在使用它几乎与vector
一样多,因为我处理了这么多案例像这样,重复创建了很少的阵列,在绝大多数常见情况下,它应该适合堆叠(但总是具有那些极为罕见的特殊可能性)。它消除了许多我在地图上与内存相关的探查器热点。
......但是应用这个基本想法可能会给你带来很大的推动力。如果它在你的测量中得到回报,你可以将这种努力应用到将其包装到保存C ++对象语义的安全容器之上,并且我认为在你的情况下它应该相当多。
答案 1 :(得分:2)
我可能会选择中间立场:
std::vector<int> list;
list.reserve(10);
......其余的可能就像你的第二个版本。然而,说实话,这可能会让人怀疑这是否真的会产生很大的不同。
答案 2 :(得分:0)
如果使用静态向量,它将只分配一次。 第一个示例工作得更慢,因为它分配和销毁每个调用的向量。