web-dev-qa-db-fra.com

Insérer ou Push_back à la fin d'un std :: vector?

Existe-t-il une différence de performances entre les deux méthodes ci-dessous pour insérer de nouveaux éléments à la fin d'un std::vector:

Méthode 1

std::vector<int> vec = { 1 };
vec.Push_back(2);
vec.Push_back(3);
vec.Push_back(4);
vec.Push_back(5);

Méthode 2

std::vector<int> vec = { 1 };
int arr[] = { 2,3,4,5 };
vec.insert(std::end(vec), std::begin(arr), std::end(arr));

Personnellement, j'aime méthode 2 car il est agréable et concis et insère tous les nouveaux éléments d'un tableau en une seule fois. Mais

  • y a-t-il une différence de performance?
  • Après tout, ils font la même chose. Non?

Mettre à jour

La raison pour laquelle je n'initialise pas le vecteur avec tous les éléments, pour commencer, c'est que dans mon programme j'ajoute les éléments restants en fonction d'une condition.

16
jignatius

Après tout, ils font la même chose. Non?

Non, ils sont différents. La première méthode utilisant std::vector::Push_back subira plusieurs réallocations par rapport à std::vector::insert .

Le insert allouera de la mémoire en interne, selon le courant std::vector::capacity avant de copier la plage. Voir la discussion suivante pour en savoir plus:

Est-ce que std :: vector :: insert reserve par définition?


Mais y a-t-il une différence de performances?

Pour la raison expliquée ci-dessus, la deuxième méthode montrerait une légère amélioration des performances. Par exemple, voir la marque benck rapide ci-dessous, en utilisant http://quick-bench.com:

Voir référence en ligne

enter image description here

Ou écrivez un programme de test pour mesurer les performances (comme @ Quelqu'un de programmeur mentionné dans les commentaires). Voici un exemple de programme de test:

#include <iostream>
#include <chrono>
#include <algorithm>
#include <vector>
using namespace std::chrono;

class Timer final
{
private:
    time_point<high_resolution_clock> _startTime;

public:
    Timer() noexcept
        : _startTime{ high_resolution_clock::now() }
    {}
    ~Timer() noexcept {  Stop(); }
    void Stop() noexcept
    {
        const auto endTime = high_resolution_clock::now();
        const auto start = time_point_cast<microseconds>(_startTime).time_since_Epoch();
        const auto end = time_point_cast<microseconds>(endTime).time_since_Epoch();
        const auto durationTaken = end - start;
        const auto duration_ms = durationTaken * 0.001;
        std::cout << durationTaken.count() << "us (" << duration_ms.count() << "ms)\n";
    }
};
// Method 1: Push_back
void Push_back()
{
    std::cout << "Push_backing:    ";
    Timer time{};
    for (auto i{ 0ULL }; i < 1000'000; ++i)
    {
        std::vector<int> vec = { 1 };
        vec.Push_back(2);
        vec.Push_back(3);
        vec.Push_back(4);
        vec.Push_back(5);
    }
}
// Method 2: insert_range
void insert_range()
{
    std::cout << "range-inserting: ";
    Timer time{};
    for (auto i{ 0ULL }; i < 1000'000; ++i)
    {
        std::vector<int> vec = { 1 };
        int arr[] = { 2,3,4,5 };
        vec.insert(std::end(vec), std::cbegin(arr), std::cend(arr));
    }
}

int main()
{
    Push_back();
    insert_range();
    return 0;
}

création de version avec mon système (MSVS2019: /Ox/std: c ++ 17, AMD Ryzen 7 2700x(8 cœurs, 3,70 GHz), x64 Windows 10)

// Build - 1
Push_backing:    285199us (285.199ms)
range-inserting: 103388us (103.388ms)

// Build - 2
Push_backing:    280378us (280.378ms)
range-inserting: 104032us (104.032ms)

// Build - 3
Push_backing:    281818us (281.818ms)
range-inserting: 102803us (102.803ms)

Qui montre pour le scénario donné, std::vector::insert ing concerne 2.7 fois plus rapide que std::vector::Push_back.

Voir ce que les autres compilateurs ( clang 8.0 et gcc 9.2) veulent dire, selon leurs implémentations: https://godbolt.org/z/DQrq51

16
JeJo

Il peut y avoir une différence entre les deux approches si le vecteur doit se réallouer.

Votre deuxième méthode, appelant une fois la fonction membre insert() avec une plage d'itérateurs:

vec.insert(std::end(vec), std::begin(arr), std::end(arr));

serait en mesure de fournir l'optimisation de l'allocation de toute la mémoire nécessaire à l'insertion des éléments en un seul coup puisque insert() obtient itérateurs d'accès aléatoire, c'est-à-dire qu'il faut un temps constant pour connaître la taille de la plage, donc toute l'allocation de mémoire peut être effectuée avant de copier les éléments, et aucune réallocation pendant l'appel ne suivrait.

Votre première méthode, les appels individuels à la fonction membre Push_back(), peut déclencher plusieurs réallocations, selon le nombre d'éléments à insérer et la mémoire initialement réservée au vecteur.

Notez que l'optimisation expliquée ci-dessus peut ne pas être disponible pour forward ou itérateurs bidirectionnels car il faudrait un temps linéaire dans la taille de la plage pour connaître le nombre d'éléments à insérer . Cependant, le temps nécessaire pour plusieurs allocations de mémoire éclipse probablement le temps nécessaire pour calculer la longueur de la plage pour ces cas, donc ils implémentent probablement toujours cette optimisation. Pour itérateurs d'entrée, cette optimisation n'est même pas possible car ce sont des itérateurs à un seul passage.

12
眠りネロク

Le principal facteur contributif sera les réaffectations. vector doit faire de la place pour de nouveaux éléments.

Considérez ces 3 sinppets.

 //pushback
 std::vector<int> vec = {1};
 vec.Push_back(2);
 vec.Push_back(3);
 vec.Push_back(4);
 vec.Push_back(5);

 //insert
 std::vector<int> vec = {1};
 int arr[] = {2,3,4,5};
 vec.insert(std::end(vec), std::begin(arr), std::end(arr));


 //cosntruct
 std::vector<int> vec = {1,2,3,4,5};

enter image description here

Pour confirmer les réallocations entrant dans l'image, après avoir ajouté une vec.reserve(5) dans les versions pushback et insert, nous obtenons les résultats ci-dessous.

enter image description here

5
Gaurav Sehgal

Push_back insère un seul élément, donc dans le pire des cas, vous pouvez rencontrer plusieurs réallocations.

Pour les besoins de l'exemple, considérons le cas où la capacité initiale est de 2 et augmente d'un facteur 2 à chaque réallocation. ensuite

std::vector<int> vec = { 1 }; 
vec.Push_back(2);             
vec.Push_back(3);                 // need to reallocate, capacity is 4
vec.Push_back(4);                   
vec.Push_back(5);                  // need to reallocate, capacity is 8

Vous pouvez bien sûr éviter les réaffectations inutiles en appelant

vec.reserve(num_elements_to_Push);

Cependant, si vous insérez de toute façon à partir d'un tableau, la façon la plus idomatique est d'utiliser insert.

4
idclev 463035818