La mauvaise performance de std:: vector est-elle due au fait de ne pas appeler realloc un nombre logarithmique de fois?

EDIT: j'ai ajouté deux autres benchmarks, pour comparer l'utilisation de realloc avec le tableau C et de reserve() avec le std::vector. De la dernière analyse il semble que realloc influence beaucoup, même si appelé seulement 30 fois. Vérifier la documentation je suppose que c'est dû au fait que realloc peut retourner un pointeur complètement nouveau, en copiant l'ancien. Pour compléter le scénario j'ai également ajouté le code et le graphique pour allouer complètement le tableau pendant le initialisation. La différence avec reserve() est tangible.

compilent les options: seulement l'optimisation décrite dans le graphique, compilant avec g++ et rien de plus.

question originale:

j'ai fait un benchmark de std::vector vs un nouveau tableau/supprimer, quand j'ajoute 1 milliard d'entiers et le deuxième code est considérablement plus rapide que celui qui utilise le vecteur, en particulier avec l'optimisation activée.

je soupçonne que cela est causé par le vecteur appel interne realloc beaucoup trop de fois. Ce serait le cas si vector ne se développe pas en doublant sa taille à chaque fois qu'il est rempli (ici le nombre 2 n'a rien de spécial, ce qui importe est que sa taille se développe géométriquement). Dans ce cas, les appels à realloc seraient seulement O(log n) au lieu de O(n) .

si c'est ce qui cause la lenteur du premier code, Comment puis-je dire std:: vecteur de croissance géométrique?

Notez que l'appel de réserve, une fois fonctionnerait dans ce cas, mais pas dans le cas plus général où le nombre de push_back n'est pas connu à l'avance.

enter image description here

ligne noire

#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;
}

ligne bleue

#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;
}

ligne verte

#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;
}

ligne rouge

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;
}

ligne orange

#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

EDIT: en cochant la case .capacity() , comme l'a suggéré, nous avons vu que la croissance est exponentielle. Alors pourquoi le vecteur est si lent?

4
demandé sur Nisba 2018-04-02 19:36:51

5 réponses

le tableau de style C optimisé est optimisé à rien.

Sur godbolt :

xorl %eax, %eax
retq

, c'est le programme.

chaque fois que vous avez un programme optimisé à près de 0s vous devriez considérer cette possibilité.

l'optimiseur voit que vous ne faites rien avec la mémoire allouée, note que la mémoire d'allocation non utilisée peut avoir zéro effets secondaires, et élimine les allocation.

et écrire à la mémoire puis Ne jamais le lire a également zéro effets secondaires.

en comparaison, le compilateur a de la difficulté à prouver que l'allocation du vecteur est inutile. Probablement les développeurs de compilateurs pourraient lui apprendre à reconnaître les vecteurs std inutilisés comme ils reconnaissent les matrices c brutes inutilisées, mais cette optimisation est vraiment un cas de coin, et il provoque beaucoup de problèmes de profilage dans mon expérience.

noter que le vector-with-reserve à tout niveau d'optimisation est essentiellement la même vitesse que la version de style C non optimisée.

dans le code de style C, la seule chose à optimiser est"ne faites rien". Dans le code vectoriel, la version non optimisée est remplie de cadres de pile supplémentaires et de vérifications de débogage pour s'assurer que vous ne dépassez pas les limites (et que vous ne vous écrasez pas proprement si vous le faites).

notez que sur un système Linux, allouer d'énormes morceaux de mémoire ne fait que jouer avec la table mémoire virtuelle. Ce n'est que lorsque la mémoire est touchée qu'elle trouve une mémoire physique nulle pour vous.

sans réserve, le vecteur std doit deviner une petite taille initiale, le redimensionner une copie, et répéter. Cela entraîne une perte de rendement de 50%, ce qui me semble raisonnable.

Avec réserve, en effet, il fait le travail. Le travail prend un peu moins de 5s.

ajouter à vector via push back fait qu'il pousse géométriquement. La croissance géométrique résulte en une moyenne asymptotique de 2-3 copies de chaque morceau de données étant faites.


Comme pour le realloc, std::vector ne pas realloc. Il alloue un nouveau tampon, et copie les anciennes données, puis écarte l'ancienne.

Realloc tente de faire pousser le buffer, et s'il ne peut pas, il copie le buffer en bits.

qui est plus efficace que le vecteur std peut gérer les types de copie bitwise. Je parie que la version realloc ne copie jamais; il y a toujours de l'espace mémoire pour faire pousser le vecteur (dans un vrai programme, ce n'est peut-être pas le cas).

Le manque de realloc en std bibliothèque allocateurs est un défaut mineur. Vous devez inventer une nouvelle API pour cela, parce que vous voulez qu'elle fonctionne pour la copie non-bitwise (quelque chose comme "essayer de développer la mémoire allouée", qui si elle échoue laisse à vous de faire croître l'allocation).

15
répondu Yakk - Adam Nevraumont 2018-04-02 17:45:14

quand j'ajoute 1 milliard d'entiers et le deuxième code est dramatiquement plus rapide que celui qui utilise le vecteur

C'est-à-dire... complètement surprenant. L'un de vos cas concerne un conteneur de taille dynamique qui doit être réajusté en fonction de sa charge, et l'autre concerne un conteneur de taille fixe qui ne l'est pas. Ce dernier doit simplement faire beaucoup moins de travail, pas de ramification, pas d'allocations supplémentaires. La juste comparaison serait:

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

Cela fait maintenant la même chose que votre exemple de tableau (bien, presque - new int[size] par défaut-initialise tous les int s tandis que std::vector<int>(size) zéro-initialise les, donc c'est encore plus de travail).

il n'est pas vraiment logique de comparer ces deux-là. Si le tableau de taille fixe int s'adapte à votre cas d'utilisation, alors utilisez-le. Si ce n'est pas le cas, alors ne le fais pas. Vous avez besoin d'un conteneur de taille dynamique ou pas. Si vous le faites, exécution plus lente qu'une solution de taille fixe est quelque chose que vous abandonnez implicitement.


si c'est ce qui cause la lenteur du premier code, Comment puis-je dire std::vector à se développer géométriquement?

std::vector est déjà mandaté pour croître géométriquement déjà, c'est la seule façon de maintenir O(1) amorti push_back complexité.

5
répondu Barry 2018-04-02 17:08:36

Est la mauvaise performance de std::vector en raison de ne pas appeler le realloc logarithmique nombre de fois?

Votre test n'appuie pas cette conclusion et ne prouve pas le contraire. Cependant, je suppose que la réaffectation est appelée nombre linéaire de fois à moins qu'il n'y ait des preuves contraires.

mise à jour: votre nouveau test est apparemment une preuve contre votre réallocation Non-logarithmique hypothèse.

je soupçonne que cela est causé par le vecteur appel interne realloc beaucoup trop de fois.

mise à jour: votre nouveau test montre que une partie de la différence est due à des réaffectations... mais pas toutes. Je soupçonne que le reste est dû au fait que optimizer a été en mesure de prouver (mais seulement dans le cas de la non-croissance) que les valeurs du tableau ne sont pas utilisées, et a choisi de ne pas boucle et écrire à tous les. Si vous deviez vous assurer que les valeurs écrites sont effectivement utilisées, alors je m'attendrais à ce que le tableau sans croissance ait des performances optimisées similaires à celles du vecteur de réservation.

la différence (entre le code de réservation et le vecteur de non-réservation) dans la construction optimisée est très probablement due à faire des réallocations plus (comparé à des réallocations Non du tableau réservé). Si le nombre de réaffectations est trop est de la situation et subjective. L'inconvénient de faire moins de réaffectations est plus de gaspillage d'espace en raison de la surallocation.

noter que le coût de réattribution de grands tableaux vient principalement de la copie d'éléments, plutôt que l'attribution de mémoire elle-même.

dans la construction non optimisée, il y a probablement des frais généraux linéaires supplémentaires en raison d'appels de fonction qui n'ont pas été élargis en ligne.

comment dire std:: vecteur de se développer géométriquement?

la croissance géométrique est requise par la norme. Il n'y a aucun moyen, et pas besoin de dire std::vector pour utiliser la croissance géométrique.

Notez que l'appel de réserve, une fois fonctionnerait dans ce cas, mais pas dans le cas plus général où le nombre de push_back n'est pas connu à l'avance.

Cependant, un cas général dans lequel le nombre de push_back n'est pas connu à l'avance est un cas où le tableau Non-croissant n'est même pas une option et donc sa performance est hors de propos pour ce cas général.

3
répondu user2079303 2018-04-02 17:59:35

il ne s'agit pas de comparer la croissance géométrique à la croissance arithmétique (ou autre). Il s'agit de comparer la pré-allocation de tout l'espace nécessaire à la croissance de l'espace selon les besoins. Donc, commençons par comparer std::vector à un code qui utilise réellement la croissance géométrique, et utiliser les deux de manière à mettre la croissance géométrique à utiliser 1 . Donc, voici une classe simple qui fait la croissance géométrique:

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; }
};

...et voici quelques codes pour l'exercer (et faites la même chose avec std::vector ):

int main() { 
    std::locale out("");
    std::cout.imbue(out);
    using namespace std::chrono;
    std::cout << "my_vect\n";
    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\n";
    }

    std::cout << "\nstd::vector\n";

    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\n";
    }
}

j'ai compilé avec g++ -std=c++14 -O3 my_vect.cpp . Quand j'exécute cela, j'obtiens ce résultat:

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

je pourrais sans doute optimiser le my_vect pour suivre le std::vector (par exemple, allouer initialement de l'espace pour, disons, 256 articles serait probablement une assez grande aide). Je n'ai pas essayé de faire assez d'essais (et d'analyse statistique) pour être sûr que std::vector est vraiment de manière fiable plus rapide que my_vect l'un ou l'autre. Néanmoins, cela semble indiquer que lorsque nous comparons des pommes à des pommes, nous obtenons des résultats qui sont au moins à peu près comparables (p. ex., à l'intérieur d'un facteur constant et assez faible l'un de l'autre).


1. Comme note d'accompagnement, je me sens obligé de souligner que cela ne compare toujours pas vraiment des pommes aux pommes--mais au moins aussi longtemps que nous sommes seulement instantiating std::vector plus int , de nombreuses différences évidentes sont essentiellement couverts.

2
répondu Jerry Coffin 2018-04-02 17:56:31

Ce poste sont les

  1. classes d'emballage sur realloc , mremap pour fournir la fonctionnalité de réaffectation.
  2. une classe vectorielle personnalisée.
  3. Un test de performance.

// 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. essai de Performance.

tous les essais de performance sont effectués sur:

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)

compilé en utilisant clang++ -std=c++17 -lbenchmark -lpthread -Ofast main.cc

la commande que j'ai utilisée pour exécuter ce test:

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

Voici la sortie de Google benchmark test:

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
1
répondu JiaHao Xu 2018-05-06 12:39:59