我在这里问我的看法是否真实。我原本以为定义vector<T> v(size_t someSize, T init_value)
会调用像vector<T>::reserve
这样的函数,而不是vector<T>::push_back
。我在这里找到了一些与此相关的讨论:std::vector push_back is bottleneck,但这个想法略有不同。
进行一些实验,我注意到vector<T> v(size_t someSize, T init_value)
一直称::push_back
。这是真的?我有以下报告使用uftrace
(https://github.com/namhyung/uftrace)。
Avg total Min total Max total Function
========== ========== ========== ====================================
858.323 ms 858.323 ms 858.323 ms main
618.245 ms 618.245 ms 618.245 ms sortKaway
234.795 ms 234.795 ms 234.795 ms std::sort
72.752 us 72.752 us 72.752 us std::vector::_M_fill_initialize
65.788 us 49.551 us 82.026 us std::vector::vector
20.292 us 11.387 us 68.629 us std::vector::_M_emplace_back_aux
18.722 us 17.263 us 20.181 us std::equal
18.472 us 18.472 us 18.472 us std::vector::~vector
17.891 us 10.002 us 102.079 us std::vector::push_back // push_back?!
vector<T>::reserve
最终还会打电话给vector<t>::push_back
吗? vector
有更快的版本吗?
以上是原帖。经过一些评论,我测试了一个简单的版本,并意识到我完全错了。
#include <vector>
#include <functional>
#include <queue>
#include <cassert>
using namespace std; // for the time being
int main () {
vector<int> v(10, 0);
return 0;
}
这实际上导致以下,不涉及std::vector<T>::push_back
。
# Function Call Graph for 'main' (session: 9ce7f6bb33885ff7)
=============== BACKTRACE ===============
backtrace #0: hit 1, time 12.710 us
[0] main (0x4009c6)
========== FUNCTION CALL GRAPH ==========
12.710 us : (1) main
0.591 us : +-(1) std::allocator::allocator
0.096 us : | (1) __gnu_cxx::new_allocator::new_allocator
: |
6.880 us : +-(1) std::vector::vector
4.338 us : | +-(1) std::_Vector_base::_Vector_base
0.680 us : | | +-(1) std::_Vector_base::_Vector_impl::_Vector_impl
0.445 us : | | | (1) std::allocator::allocator
0.095 us : | | | (1) __gnu_cxx::new_allocator::new_allocator
: | | |
3.294 us : | | +-(1) std::_Vector_base::_M_create_storage
3.073 us : | | (1) std::_Vector_base::_M_allocate
2.849 us : | | (1) std::allocator_traits::allocate
2.623 us : | | (1) __gnu_cxx::new_allocator::allocate
0.095 us : | | +-(1) __gnu_cxx::new_allocator::max_size
: | | |
1.867 us : | | +-(1) operator new
: | |
2.183 us : | +-(1) std::vector::_M_fill_initialize
0.095 us : | +-(1) std::_Vector_base::_M_get_Tp_allocator
: | |
1.660 us : | +-(1) std::__uninitialized_fill_n_a
1.441 us : | (1) std::uninitialized_fill_n
1.215 us : | (1) std::__uninitialized_fill_n::__uninit_fill_n
0.988 us : | (1) std::fill_n
0.445 us : | +-(1) std::__niter_base
0.096 us : | | (1) std::_Iter_base::_S_base
: | |
0.133 us : | +-(1) std::__fill_n_a
对困惑感到抱歉。是的,库实现按预期工作,如果使用push_back
构建,则不涉及initial size
。
P,看起来你回答了自己的问题!我有点困惑了一会儿。假设,我可以想象使用vector's
和reserve
的push_backs
填充构造函数的一些模糊的情况,但绝对不是像GNU标准库中伴随GCC那样的高质量实现。我假设,假设一个模糊的矢量实现可能以这种方式实现,但实际上完全不可能实现任何体面的实现。
相反,这是差不多二十年前,但我试图实现我的std::vector
版本,以期与其性能相匹配。这不仅仅是一些愚蠢的练习,但诱惑是因为我们有一个软件开发工具包,并希望使用一些基本的C ++容器,但它的目标是允许人们使用不同的软件为我们的软件编写插件编译器(以及不同的标准库实现)比我们使用的编译器。所以我们无法安全地在这些上下文中使用std::vector
,因为我们的版本可能与插件编写者不匹配。我们被迫不情愿地为SDK推出自己的容器。
相反,我发现std::vector
在难以匹配的方式上非常有效,特别是对于具有琐碎的ctors和dtors的普通旧数据类型。这是十多年前的事情,但我发现在MSVC 5或6中使用填充构造函数与vector<int>
(忘记哪一个)实际上转换为与使用memset
相同的反汇编,就像我的天真版本一样,只是循环遍历事物并使用放置无论他们是否是POD,他们都是新的,没有。范围ctor也有效地转换为POD的超快memcpy
。这正是使得矢量如此难以为我击败的原因,至少在那时候。在没有深入了解类型特征和特殊套管POD的情况下,我无法真正匹配POD的矢量性能。我可以将它与UDT匹配,但我们的大多数性能关键代码都倾向于使用POD。
因此,当我进行这些测试时,今天流行的矢量实现可能同样有效,并且我希望能够保证你的矢量实现很可能是快速的。我希望它做的最后一件事是使用push_backs
实现填充或范围ctors。