web-dev-qa-db-fra.com

Quelle est la façon la plus efficace d'ajouter un std :: vector à la fin d'un autre?

Soit v1 le vecteur cible, v2 doit être ajouté à l'arrière de celui-ci.

Je fais maintenant:

v1.reserve(v1.size() + v2.size()); 
copy(v2.begin(), v2.end(), back_inserter(v1));

Est-ce le moyen le plus efficace? Ou cela peut-il être fait simplement en copiant un morceau de mémoire? Merci!

48
Alex Jenter

Après beaucoup de discussions (et un commentaire raisonnable de Matthieu M. et villintehaspam), je changerai ma suggestion en

v1.insert( v1.end(), v2.begin(), v2.end() );

Je vais garder l'ancienne suggestion ici:

v1.reserve( v1.size() + v2.size() ); 
v1.insert( v1.end(), v2.begin(), v2.end() );

Il y a quelques raisons de le faire de cette dernière manière, bien qu'aucune ne soit suffisamment solide:

  • il n'y a aucune garantie quant à quelle taille le vecteur sera réaffecté - par ex. si la taille de la somme est 1025, elle peut être réaffectée à 2048 - en fonction de la mise en œuvre. Il n'y a pas non plus une telle garantie pour reserve, mais pour une implémentation spécifique, cela pourrait être vrai. Si vous cherchez un goulot d'étranglement, il pourrait être difficile de vérifier cela.
  • la réserve indique clairement nos intentions - l'optimisation peut être plus efficace dans ce cas (la réserve pourrait préparer le cache dans une mise en œuvre de premier ordre).
  • de plus, avec reserve nous avons une garantie C++ Standard qu'il n'y aura qu'une seule réallocation, tandis que insert peut être implémenté de manière inefficace et faire plusieurs réallocations (également quelque chose à tester avec une implémentation particulière).
61
Kornel Kisielewicz

Probablement mieux et plus simple d'utiliser une méthode dédiée: vector.insert

v1.insert(v1.end(), v2.begin(), v2.end());

Comme Michael le mentionne, à moins que les itérateurs ne soient des itérateurs d'entrée, le vecteur déterminera la taille requise et copiera les données ajoutées en une seule fois avec une complexité linéaire.

22
UncleBens

J'ai simplement fait une mesure rapide des performances avec le code suivant et

v1.insert( v1.end(), v2.begin(), v2.end() );

semble être le bon choix (comme déjà indiqué ci-dessus). Néanmoins, vous trouverez les performances signalées ci-dessous.

Code de test:

#include <vector>
#include <string>

#include <boost/timer/timer.hpp>

//==============================================================================
// 
//==============================================================================

/// Returns a vector containing the sequence [ 0, ... , n-1 ].
inline std::vector<int> _range(const int n)
{
    std::vector<int> tmp(n);
    for(int i=0; i<n; i++)
        tmp[i] = i;
    return tmp;
}

void test_perf_vector_append()
{
    const vector<int> testdata1 = _range(100000000);
    const vector<int> testdata2 = _range(100000000);

    vector<int> testdata;

    printf("--------------------------------------------------------------\n");
    printf(" METHOD:  Push_back()\n");
    printf("--------------------------------------------------------------\n");
    testdata.clear();
    { vector<int>().swap(testdata); }
    testdata = testdata1;
    {
        boost::timer::auto_cpu_timer t;
        for(size_t i=0; i<testdata2.size(); i++)
        {
            testdata.Push_back(testdata2[i]);
        }
    }

    printf("--------------------------------------------------------------\n");
    printf(" METHOD:  reserve() + Push_back()\n");
    printf("--------------------------------------------------------------\n");
    testdata.clear();
    { vector<int>().swap(testdata); }
    testdata = testdata1;
    {
        boost::timer::auto_cpu_timer t;
        testdata.reserve(testdata.size() + testdata2.size());
        for(size_t i=0; i<testdata2.size(); i++)
        {
            testdata.Push_back(testdata2[i]);
        }
    }

    printf("--------------------------------------------------------------\n");
    printf(" METHOD:  insert()\n");
    printf("--------------------------------------------------------------\n");
    testdata.clear();
    { vector<int>().swap(testdata); }
    testdata = testdata1;
    {
        boost::timer::auto_cpu_timer t;

        testdata.insert( testdata.end(), testdata2.begin(), testdata2.end() );
    }

    printf("--------------------------------------------------------------\n");
    printf(" METHOD:  reserve() + insert()\n");
    printf("--------------------------------------------------------------\n");
    testdata.clear();
    { vector<int>().swap(testdata); }
    testdata = testdata1;
    {
        boost::timer::auto_cpu_timer t;

        testdata.reserve( testdata.size() + testdata.size() ); 
        testdata.insert( testdata.end(), testdata2.begin(), testdata2.end() );
    }

    printf("--------------------------------------------------------------\n");
    printf(" METHOD:  copy() + back_inserter()\n");
    printf("--------------------------------------------------------------\n");
    testdata.clear();
    { vector<int>().swap(testdata); }
    testdata = testdata1;
    {
        boost::timer::auto_cpu_timer t;

        testdata.reserve(testdata.size() + testdata2.size()); 
        copy(testdata2.begin(), testdata2.end(), back_inserter(testdata));
    }

    printf("--------------------------------------------------------------\n");
    printf(" METHOD:  reserve() + copy() + back_inserter()\n");
    printf("--------------------------------------------------------------\n");
    testdata.clear();
    { vector<int>().swap(testdata); }
    testdata = testdata1;
    {
        boost::timer::auto_cpu_timer t;

        testdata.reserve(testdata.size() + testdata2.size()); 
        copy(testdata2.begin(), testdata2.end(), back_inserter(testdata));
    }

}

Avec Visual Studio 2008 SP1, x64, mode Release,/O2/LTCG, la sortie est la suivante:

--------------------------------------------------------------
 METHOD:  Push_back()
--------------------------------------------------------------
 0.933077s wall, 0.577204s user + 0.343202s system = 0.920406s CPU (98.6%)

--------------------------------------------------------------
 METHOD:  reserve() + Push_back()
--------------------------------------------------------------
 0.612753s wall, 0.452403s user + 0.171601s system = 0.624004s CPU (101.8%)

--------------------------------------------------------------
 METHOD:  insert()
--------------------------------------------------------------
 0.424065s wall, 0.280802s user + 0.140401s system = 0.421203s CPU (99.3%)

--------------------------------------------------------------
 METHOD:  reserve() + insert()
--------------------------------------------------------------
 0.637081s wall, 0.421203s user + 0.218401s system = 0.639604s CPU (100.4%)

--------------------------------------------------------------
 METHOD:  copy() + back_inserter()
--------------------------------------------------------------
 0.743658s wall, 0.639604s user + 0.109201s system = 0.748805s CPU (100.7%)

--------------------------------------------------------------
 METHOD:  reserve() + copy() + back_inserter()
--------------------------------------------------------------
 0.748560s wall, 0.624004s user + 0.124801s system = 0.748805s CPU (100.0%)
18
Stefan

Si vous utilisez Boost, vous pouvez télécharger la version de développement de la bibliothèque RangeEx depuis le coffre-fort Boost . Cette lib. a été accepté dans Boost il y a quelque temps, mais jusqu'à présent, il n'a pas été intégré à la distribution principale. Vous y trouverez un nouvel algorithme basé sur une plage qui fait exactement ce que vous voulez:

boost::Push_back(v1, v2);

En interne, cela fonctionne comme la réponse donnée par UncleBens, mais le code est plus concis et lisible.

7
Manuel

Si vous avez un vecteur de pod-types et que vous avez vraiment besoin de performances, vous pouvez utiliser memcpy, qui devrait être plus rapide que vector <>. Insert (...):

v2.resize(v1.size() + v2.size());
memcpy((void*)&v1.front(), (void*)&v2[v1.size()], sizeof(v1.front())*v1.size());

Mise à jour: Bien que je n'utilise cela que si les performances sont vraiment, vraiment, nécessaires, le code is est sûr pour les types de pod.

3
Viktor Sehr