关于c ++:由于没有以对数次数调用realloc,std :: vector的性能是否差?

Is the poor performance of std::vector due to not calling realloc a logarithmic number of times?

编辑:我又添加了两个基准,比较realloc与c数组的使用情况,reserve()与std::vector的使用情况。从最后的分析来看,realloc影响很大,即使只调用了30次。检查文档我想这是因为realloc可以返回一个全新的指针,复制旧的指针。为了完成这个场景,我还添加了用于在初始化期间完全分配数组的代码和图形。与reserve()的区别是有形的。

编译标志:只有图中描述的优化,用g++编译,其他什么都没有。

原始问题:

当我添加10亿个整数,第二个代码比使用向量的代码快得多,特别是在优化打开的情况下,我做了一个std::vector与一个新的/删除数组的基准。

我怀疑这是向量内部调用realloc太多次造成的。如果向量在每次填充时不增长一倍,就会出现这种情况(这里数字2没有特殊之处,重要的是它的大小以几何形式增长)。在这种情况下,调用realloc将只是O(log n),而不是O(n)

如果这就是导致第一个代码缓慢的原因,那么如何告诉std::vector几何增长呢?

请注意,在这种情况下,调用reserve一次是有效的,但在更一般的情况下,这种情况下的push_back的数目并不预先知道。

enter image description here

黑线

1
2
3
4
5
6
7
8
9
10
11
#include<vector>

int main(int argc, char * argv[]) {
    const unsigned long long size = 1000000000;

    std::vector <int> b(size);
    for(int i = 0; i < size; i++) {
        b[i]=i;
    }    
    return 0;
}

蓝线

1
2
3
4
5
6
7
8
9
10
11
#include<vector>

int main(int argc, char * argv[]) {
    const int size = 1000000000;    
    std::vector <int> b;
    for(int i = 0; i < size; i++) {
        b.push_back(i);
    }    

    return 0;
}

绿线

1
2
3
4
5
6
7
8
9
10
11
12
#include<vector>

int main(int argc, char * argv[]) {
    const int size = 1000000000;
    std::vector <int> b;
    b.reserve(size);
    for(int i = 0; i < size; i++) {
        b.push_back(i);
    }    

    return 0;
}

红线

1
2
3
4
5
6
7
8
9
int main(int argc, char * argv[]) {
    const int size = 1000000000;
    int * a = new int [size];
    for(int i = 0; i < size; i++) {
        a[i] = i;
    }
    delete [] a;  
    return 0;
}

橙色线

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<vector>

int main(int argc, char * argv[]) {
    const unsigned long long size = 1000000000;
    int * a = (int *)malloc(size*sizeof(int));
    int next_power = 1;
    for(int i = 0; i < size; i++) {
        a[i] = i;
        if(i == next_power - 1) {
            next_power *= 2;
            a=(int*)realloc(a,next_power*sizeof(int));
        }
    }
    free(a);
    return 0;
}

enter image description here

编辑:检查.capacity(),正如建议的那样,我们发现增长确实是指数级的。为什么矢量会这么慢?


优化后的C样式数组被优化为零。

论上帝之箭:

1
2
xorl %eax, %eax
retq

这就是程序。

每当你有一个程序优化到接近0秒,你应该考虑这种可能性。

优化器看到您对分配的内存没有做任何操作,注意到未使用的分配内存可能没有任何副作用,并且消除了分配。

写给记忆,然后再也不读,也没有任何副作用。

相比之下,编译器很难证明向量的分配是无用的。也许编译器开发人员可以教它识别未使用的std向量,就像他们识别未使用的raw c数组一样,但这种优化实际上是一个小问题,在我的经验中,它会导致许多分析问题。

请注意,在任何优化级别上具有保留的向量与未优化的C样式版本的速度基本相同。

在C样式的代码中,唯一需要优化的就是"不要做任何事情"。在向量代码中,未优化的版本充满了额外的堆栈帧和调试检查,以确保不会超出界限(如果超出界限,则会完全崩溃)。

请注意,在Linux系统上,分配大量内存除了处理虚拟内存表之外什么都不做。只有当记忆被触动时,它才会真正为你找到一些零维的物理记忆。

在没有保留的情况下,std向量必须猜测一个初始的小尺寸,调整它的大小并复制它,然后重复。这会导致50%的性能损失,这在我看来是合理的。

有了储备,它实际上起到了作用。这项工作只需不到5秒。

通过后推添加到向量中会导致向量几何增长。几何增长导致每一份数据的渐进平均值为2-3份。

至于realloc,std::vector不进行realloc。它分配一个新的缓冲区,并复制旧数据,然后丢弃旧数据。

realloc尝试增大缓冲区,如果不能,则按位复制缓冲区。

这比std vector可以管理按位可复制类型更有效。我敢打赌,realloc版本实际上从不复制;总是有内存空间来增加向量(在真正的程序中,情况可能不是这样)。

std库分配器中缺少realloc是一个小缺陷。您必须为它发明一个新的API,因为您希望它可以用于非位拷贝(比如"尝试增加分配的内存",如果失败,则由您增加分配)。


when I add 1 billion integers and the second code is dramatically faster than the one using the vector

那是。。。完全不足为奇。您的一个案例涉及一个动态大小的容器,该容器必须重新调整以适应其负载,另一个案例涉及一个固定大小的容器,而不是。后者只需要做更少的工作,没有分支,没有额外的分配。公平比较如下:

1
2
3
4
std::vector<int> b(size);
for(int i = 0; i < size; i++) {
    b[i] = i;
}

现在,这与您的数组示例所做的操作相同(几乎是-new int[size]默认值初始化所有int的,而std::vector(size)零则初始化所有int的,所以它仍然需要更多的工作)。

把这两者比较起来真的没有意义。如果固定大小的int数组适合您的用例,那么就使用它。如果没有,那么就不要。您要么需要一个动态大小的容器,要么不需要。如果这样做了,那么执行比固定大小解决方案慢的解决方案是您含蓄地放弃的。


If this is what causes the slowness of the first code, how can I tell std::vector to grow geometrically?

std::vector已经被授权以几何形式增长,这是保持O(1)摊销push_back复杂性的唯一方法。


Is the poor performance of std::vector due to not calling realloc a logarithmic number of times?

<罢工>你的测试既不支持这个结论,也不能证明相反的结论。但是,我假设重新分配被称为线性次数,除非有相反的证据。< /打击>

更新:你的新测试显然是反对你的非对数再分配假设的证据。

I suspect that this is caused by the vector internally calling realloc too many times.

更新:您的新测试表明,有些差异是由于重新分配…但不是全部。我怀疑剩余部分是由于优化器能够证明(但仅在非增长的情况下)数组值是未使用的,并且选择不循环并写入它们。如果您要确保实际使用写入的值,那么我希望非增长数组具有与保留向量类似的优化性能。

优化构建中的差异(保留代码和非保留向量之间)很可能是由于执行了更多的重新分配(与保留数组的不重新分配相比)。重新分配的数量是否太多是情景和主观的。减少重新分配的缺点是由于过度分配而浪费了更多的空间。

请注意,大型数组重新分配的成本主要来自元素的复制,而不是内存分配本身。

在未优化的构建中,由于函数调用没有内联扩展,可能会有额外的线性开销。

how can I tell std::vector to grow geometrically?

标准要求几何增长。没有办法也没有必要告诉std::vector使用几何增长。

Note that calling reserve once would work in this case but not in the more general case in which the number of push_back is not known in advance.

然而,一般情况下,事先不知道push_back的数目,即非增长阵列甚至不是一个选项,因此其性能与一般情况无关。


这不是将几何增长与算术(或任何其他)增长进行比较。它比较了预先分配所有必要的空间,以便根据需要扩大空间。因此,让我们首先将std::vector与一些实际使用几何增长的代码进行比较,并以使用几何增长的方式使用这两种代码1。所以,这里有一个简单的类,它可以进行几何增长:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class my_vect {
    int *data;
    size_t current_used;
    size_t current_alloc;
public:

    my_vect()
        : data(nullptr)
        , current_used(0)
        , current_alloc(0)
    {}

    void push_back(int val) {
        if (nullptr == data) {
            data = new int[1];
            current_alloc = 1;
        }
        else if (current_used == current_alloc)  {
            int *temp = new int[current_alloc * 2];
            for (size_t i=0; i<current_used; i++)
                temp[i] = data[i];
            swap(temp, data);
            delete [] temp;
            current_alloc *= 2;
        }
        data[current_used++] = val;
    }

    int &at(size_t index) {
        if (index >= current_used)
            throw bad_index();
        return data[index];
    }

    int &operator[](size_t index) {
        return data[index];
    }

    ~my_vect() { delete [] data; }
};

…这里有一些代码来执行它(并对std::vector执行相同的操作):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
int main() {
    std::locale out("");
    std::cout.imbue(out);
    using namespace std::chrono;
    std::cout <<"my_vect
"
;
    for (int size = 100; size <= 1000000000; size *= 10) {
        auto start = high_resolution_clock::now();

        my_vect b;
        for(int i = 0; i < size; i++) {
            b.push_back(i);
        }    

        auto stop = high_resolution_clock::now();

        std::cout <<"Size:" << std::setw(15) << size <<", Time:" << std::setw(15) << duration_cast<microseconds>(stop-start).count() <<" us
"
;
    }

    std::cout <<"
std::vector
"
;

    for (int size = 100; size <= 1000000000; size *= 10) {
        auto start = high_resolution_clock::now();

        std::vector<int> b;
        for (int i = 0; i < size; i++) {
            b.push_back(i);
        }

        auto stop = high_resolution_clock::now();

        std::cout <<"Size:" << std::setw(15) << size <<", Time:" << std::setw(15) << duration_cast<microseconds>(stop - start).count() <<" us
"
;
    }
}

我用g++ -std=c++14 -O3 my_vect.cpp编译了这个。当我执行该操作时,会得到以下结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
my_vect
Size:             100, Time:               8 us
Size:           1,000, Time:              23 us
Size:          10,000, Time:             141 us
Size:         100,000, Time:             950 us
Size:       1,000,000, Time:           8,040 us
Size:      10,000,000, Time:          51,404 us
Size:     100,000,000, Time:         442,713 us
Size:   1,000,000,000, Time:       7,936,013 us

std::vector
Size:             100, Time:              40 us
Size:           1,000, Time:               4 us
Size:          10,000, Time:              29 us
Size:         100,000, Time:             426 us
Size:       1,000,000, Time:           3,730 us
Size:      10,000,000, Time:          41,294 us
Size:     100,000,000, Time:         363,942 us
Size:   1,000,000,000, Time:       5,200,545 us

毫无疑问,我可以优化my_vect以跟上std::vector(例如,最初为256个项目分配空间可能是一个很大的帮助)。我没有尝试做足够的运行(和统计分析)来确定std::vector确实比my_vect快。尽管如此,这似乎表明,当我们将苹果与苹果进行比较时,我们得到的结果至少是大致可比的(例如,在相对较小的常数因子内)。

1。作为旁注,我觉得有必要指出,这仍然不能真正地将苹果与苹果进行比较——但至少只要我们只是将std::vectorint进行比较,许多明显的差异就基本上被掩盖了。


这篇文章包括

  • reallocmremap上包装类,以提供重新分配功能。
  • 自定义向量类。
  • 性能测试。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    // C++17
    #include <benchmark/benchmark.h> // Googleo benchmark lib, for benchmark.

    #include <new>     // For std::bad_alloc.
    #include <memory>  // For std::allocator_traits, std::uninitialized_move.
    #include <cstdlib> // For C heap management API.
    #include <cstddef> // For std::size_t, std::max_align_t.
    #include <cassert> // For assert.
    #include <utility> // For std::forward, std::declval,

    namespace linux {
    #include <sys/mman.h> // For mmap, mremap, munmap.
    #include <errno.h>    // For errno.
    auto get_errno() noexcept {
        return errno;
    }
    }

    /*
     * Allocators.
     * These allocators will have non-standard compliant behavior if the type T's cp ctor has side effect.
     */


    // class mrealloc are usefull for allocating small space for
    // std::vector.
    //
    // Can prevent copy of data and memory fragmentation if there's enough
    // continous memory at the original place.
    template <class T>
    struct mrealloc {
        using pointer = T*;
        using value_type = T;

        auto allocate(std::size_t len) {
            if (auto ret = std::malloc(len))
                return static_cast<pointer>(ret);
            else
                throw std::bad_alloc();
        }
        auto reallocate(pointer old_ptr, std::size_t old_len, std::size_t len) {
            if (auto ret = std::realloc(old_ptr, len))
                return static_cast<pointer>(ret);
            else
                throw std::bad_alloc();
        }
        void deallocate(void *ptr, std::size_t len) noexcept {
            std::free(ptr);
        }
    };

    // class mmaprealloc is suitable for large memory use.
    //
    // It will be usefull for situation that std::vector can grow to a huge
    // size.
    //
    // User can call reserve without worrying wasting a lot of memory.
    //
    // It can prevent data copy and memory fragmentation at any time.
    template <class T>
    struct mmaprealloc {
        using pointer = T*;
        using value_type = T;

        auto allocate(std::size_t len) const
        {
            return allocate_impl(len, MAP_PRIVATE | MAP_ANONYMOUS);
        }
        auto reallocate(pointer old_ptr, std::size_t old_len, std::size_t len) const
        {
            return reallocate_impl(old_ptr, old_len, len, MREMAP_MAYMOVE);
        }
        void deallocate(pointer ptr, std::size_t len) const noexcept
        {
            assert(linux::munmap(ptr, len) == 0);
        }
      protected:
        auto allocate_impl(std::size_t _len, int flags) const
        {
            if (auto ret = linux::mmap(nullptr, get_proper_size(_len), PROT_READ | PROT_WRITE, flags, -1, 0))
                return static_cast<pointer>(ret);
            else
                fail(EAGAIN | ENOMEM);
        }
        auto reallocate_impl(pointer old_ptr, std::size_t old_len, std::size_t _len, int flags) const
        {
            if (auto ret = linux::mremap(old_ptr, old_len, get_proper_size(_len), flags))
                return static_cast<pointer>(ret);
            else
                fail(EAGAIN | ENOMEM);
        }

        static inline constexpr const std::size_t magic_num = 4096 - 1;
        static inline auto get_proper_size(std::size_t len) noexcept -> std::size_t {
            return round_to_pagesize(len);
        }
        static inline auto round_to_pagesize(std::size_t len) noexcept -> std::size_t {
            return (len + magic_num) & ~magic_num;
        }

        static inline void fail(int assert_val)
        {
            auto _errno = linux::get_errno();
            assert(_errno == assert_val);
            throw std::bad_alloc();
        }
    };

    template <class T>
    struct mmaprealloc_populate_ver: mmaprealloc<T> {
        auto allocate(size_t len) const
        {
            return mmaprealloc<T>::allocate_impl(len, MAP_PRIVATE | MAP_ANONYMOUS | MAP_POPULATE);
        }
    };

    namespace impl {
    struct disambiguation_t2 {};
    struct disambiguation_t1 {
        constexpr operator disambiguation_t2() const noexcept { return {}; }
    };
    template <class Alloc>
    static constexpr auto has_reallocate(disambiguation_t1) noexcept -> decltype(&Alloc::reallocate, bool{}) { return true; }
    template <class Alloc>
    static constexpr bool has_reallocate(disambiguation_t2) noexcept { return false; }
    template <class Alloc>
    static inline constexpr const bool has_reallocate_v = has_reallocate<Alloc>(disambiguation_t1{});
    } /* impl */

    template <class Alloc>
    struct allocator_traits: public std::allocator_traits<Alloc> {
        using Base = std::allocator_traits<Alloc>;
        using value_type = typename Base::value_type;
        using pointer = typename Base::pointer;
        using size_t = typename Base::size_type;

        static auto reallocate(Alloc &alloc, pointer prev_ptr, size_t prev_len, size_t new_len) {
            if constexpr(impl::has_reallocate_v<Alloc>)
                return alloc.reallocate(prev_ptr, prev_len, new_len);
            else {
                auto new_ptr = Base::allocate(alloc, new_len);

                // Move existing array
                for(auto _prev_ptr = prev_ptr, _new_ptr = new_ptr; _prev_ptr != prev_ptr + prev_len; ++_prev_ptr, ++_new_ptr) {
                    new (_new_ptr) value_type(std::move(*_prev_ptr));
                    _new_ptr->~value_type();
                }
                Base::deallocate(alloc, prev_ptr, prev_len);

                return new_ptr;
            }
        }
    };

    template <class T, class Alloc = std::allocator<T>>
    struct vector: protected Alloc {
        using alloc_traits = allocator_traits<Alloc>;
        using pointer = typename alloc_traits::pointer;
        using size_t = typename alloc_traits::size_type;
        pointer ptr = nullptr;
        size_t last = 0;
        size_t avail = 0;

        ~vector() noexcept {
            alloc_traits::deallocate(*this, ptr, avail);
        }

        template <class ...Args>
        void emplace_back(Args &&...args) {
            if (last == avail)
                double_the_size();
            alloc_traits::construct(*this, &ptr[last++], std::forward<Args>(args)...);
        }
        void double_the_size() {
            if (__builtin_expect(!!(avail), true)) {
                avail <<= 1;
                ptr = alloc_traits::reallocate(*this, ptr, last, avail);
            } else {
                avail = 1 << 4;
                ptr = alloc_traits::allocate(*this, avail);
            }
        }
    };

    template <class T>
    static void BM_vector(benchmark::State &state) {
        for(auto _: state) {
            T c;
            for(auto i = state.range(0); --i >= 0; )
                c.emplace_back((char)i);
        }
    }

    static constexpr const auto one_GB = 1 << 30;

    BENCHMARK_TEMPLATE(BM_vector, vector<char>)                                ->Range(1 << 3, one_GB);
    BENCHMARK_TEMPLATE(BM_vector, vector<char, mrealloc<char>>)                ->Range(1 << 3, one_GB);
    BENCHMARK_TEMPLATE(BM_vector, vector<char, mmaprealloc<char>>)             ->Range(1 << 3, one_GB);
    BENCHMARK_TEMPLATE(BM_vector, vector<char, mmaprealloc_populate_ver<char>>)->Range(1 << 3, one_GB);
    BENCHMARK_MAIN();
  • 性能测试。
  • 所有性能测试都在以下位置完成:

    1
    Debian 9.4, Linux version 4.9.0-6-amd64 (debian-kernel@lists.debian.org)(gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) ) #1 SMP Debian 4.9.82-1+deb9u3 (2018-03-02)

    使用clang++ -std=c++17 -lbenchmark -lpthread -Ofast main.cc
    编译

    我用于运行此测试的命令:

    1
    2
    sudo cpupower frequency-set --governor performance
    ./a.out

    以下是谷歌基准测试的输出:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    Run on (8 X 1600 MHz CPU s)
    CPU Caches:
      L1 Data 32K (x4)
      L1 Instruction 32K (x4)
      L2 Unified 256K (x4)
      L3 Unified 6144K (x1)
    ----------------------------------------------------------------------------------------------------------
    Benchmark                                                                   Time           CPU Iterations
    ----------------------------------------------------------------------------------------------------------
    BM_vector<vector<char>>/8                                                  58 ns         58 ns   11476934
    BM_vector<vector<char>>/64                                                324 ns        324 ns    2225396
    BM_vector<vector<char>>/512                                              1527 ns       1527 ns     453629
    BM_vector<vector<char>>/4096                                             7196 ns       7196 ns      96695
    BM_vector<vector<char>>/32768                                           50145 ns      50140 ns      13655
    BM_vector<vector<char>>/262144                                         549821 ns     549825 ns       1245
    BM_vector<vector<char>>/2097152                                       5007342 ns    5006393 ns        146
    BM_vector<vector<char>>/16777216                                     42873349 ns   42873462 ns         15
    BM_vector<vector<char>>/134217728                                   336225619 ns  336097218 ns          2
    BM_vector<vector<char>>/1073741824                                 2642934606 ns 2642803281 ns          1
    BM_vector<vector<char, mrealloc<char>>>/8                                  55 ns         55 ns   12914365
    BM_vector<vector<char, mrealloc<char>>>/64                                266 ns        266 ns    2591225
    BM_vector<vector<char, mrealloc<char>>>/512                              1229 ns       1229 ns     567505
    BM_vector<vector<char, mrealloc<char>>>/4096                             6903 ns       6903 ns     102752
    BM_vector<vector<char, mrealloc<char>>>/32768                           48522 ns      48523 ns      14409
    BM_vector<vector<char, mrealloc<char>>>/262144                         399470 ns     399368 ns       1783
    BM_vector<vector<char, mrealloc<char>>>/2097152                       3048578 ns    3048619 ns        229
    BM_vector<vector<char, mrealloc<char>>>/16777216                     24426934 ns   24421774 ns         29
    BM_vector<vector<char, mrealloc<char>>>/134217728                   262355961 ns  262357084 ns          3
    BM_vector<vector<char, mrealloc<char>>>/1073741824                 2092577020 ns 2092317044 ns          1
    BM_vector<vector<char, mmaprealloc<char>>>/8                             4285 ns       4285 ns     161498
    BM_vector<vector<char, mmaprealloc<char>>>/64                            5485 ns       5485 ns     125375
    BM_vector<vector<char, mmaprealloc<char>>>/512                           8571 ns       8569 ns      80345
    BM_vector<vector<char, mmaprealloc<char>>>/4096                         24248 ns      24248 ns      28655
    BM_vector<vector<char, mmaprealloc<char>>>/32768                       165021 ns     165011 ns       4421
    BM_vector<vector<char, mmaprealloc<char>>>/262144                     1177041 ns    1177048 ns        557
    BM_vector<vector<char, mmaprealloc<char>>>/2097152                    9229860 ns    9230023 ns         74
    BM_vector<vector<char, mmaprealloc<char>>>/16777216                  75425704 ns   75426431 ns          9
    BM_vector<vector<char, mmaprealloc<char>>>/134217728                607661012 ns  607662273 ns          1
    BM_vector<vector<char, mmaprealloc<char>>>/1073741824              4871003928 ns 4870588050 ns          1
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/8                3956 ns       3956 ns     175037
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/64               5087 ns       5086 ns     133944
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/512              8662 ns       8662 ns      80579
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/4096            23883 ns      23883 ns      29265
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/32768          158374 ns     158376 ns       4444
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/262144        1171514 ns    1171522 ns        593
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/2097152       9297357 ns    9293770 ns         74
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/16777216     75140789 ns   75141057 ns          9
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/134217728   636359403 ns  636368640 ns          1
    BM_vector<vector<char, mmaprealloc_populate_ver<char>>>/1073741824 4865103542 ns 4864582150 ns          1