std::矢量推进速度

std::vector pushing speed?

本文关键字:速度 std      更新时间:2023-10-16

我正在使用vector来管理我的大型结构数据。但突然间,当我发现vector源代码时,我非常惊讶地看到下面的一些代码:

inline void push_back(const _Ty& _X)
    {insert(end(), _X); }
//...
void insert(iterator _P, size_type _M, const _Ty& _X)
    {
//////////////////////////////////////////////////////////////
        iterator _S = allocator.allocate(_N, (void *)0);
        iterator _Q = _Ucopy(_First, _P, _S);
        _Ufill(_Q, _M, _X);
        _Ucopy(_P, _Last, _Q + _M);
        _Destroy(_First, _Last);
        allocator.deallocate(_First, _End - _First);
//////////////////////////////////////////////////////////////
    }

这是"销毁"然后重新分配其整个矢量数据的片段代码。这太烦人了,因为我的结构很大,一个向量必须管理数百个元素,而我只使用vector::operator []vector::push_back(),尤其是pushing back占用了我大部分的程序时间(这很耗时)。在我的情况下,有没有比std::vector更好的容器可以执行得更快,而我尝试了谷歌,但没有运气?

每次向量超过其当前容量时,分配复制删除(或C++11中的分配移动删除)只发生一次余复杂性是常数。

您可以使用向量的reserve()成员函数预先分配向量的容量。

在添加元素之前保留所需的所有空间会解决您的问题吗?

如果知道最终的数据大小,可以使用reserve预先分配向量所需的内存。这将删除所有重新分配和复制。

如果你不知道确切的大小,根据你对传入数据的了解做出有根据的猜测。

只有当矢量大小超过其容量时,才会发生重新定位。如果你提前(甚至大致)知道你的向量将包含多少个项目,你可以使用它的储备成员来预先分配足够的容量。您还可以以可控的方式触发重新分配,即使在填充向量时也是如此,以减少调用向量的次数,从而获得更好的性能。

现在,如果您想要有保证的恒定时间插入,那么有一些容器可以用于此操作,但需要权衡(例如,std::list将分配大量的小内存块,这些内存块最终可能不会比您当前矢量使用的速度快,因为new非常慢,内存使用也会更重要,并且您会失去随机访问,但每次插入所需的时间肯定与其他插入所需时间大致相同)。

向量是一个动态扩展的容器,但它的内容必须在一个连续的内存块中(我记得,这在C++11之前是未指定的,但每个人都这样做了)。因此,如果您的向量已经包含8个元素,并且向量的当前容量为8个元素。那么,当您推送第9个元素时,容器必须为更大的容量分配新的空间,复制容器中已经存在的8个元素、将第9个元素复制到新的空间中,然后销毁旧的。如果你使用的是C++11,并且你的元素支持移动构造,那么8个副本可以变成8个移动。或者,如果你想承担管理指针的开销,你可以存储指向元素的指针,而不是元素(复制指针很便宜,但现在你需要处理寿命问题)。

至于哪个容器比向量快,这是一个很大的开放式问题。它取决于各种访问/操作模式。std::list被提到作为候选。O(1)添加到列表的末尾,但可能比向量使用的摊销O(1。您失去了对容器的随机访问权限。

是的,push_back()在大多数时候都是常数,但当size()到达capacity()时,需要重新分配整个向量。

如果您想要恒定时间插入,则必须尝试std::liststd::deque。特别是std::deque在容器的末尾提供了正确的插入性能,同时在其接口中接近向量。

从cpp引用中提取:

因此,它们提供了与矢量类似的功能,但在序列的开始处,而不仅仅是在序列的结束处,它们还具有元素的有效插入和删除。但是,与向量不同,deques不能保证将其所有元素存储在连续的存储位置,因此不允许通过偏移指向元素的指针来直接访问。