c++ - Insertar o push_back al final de un std:: vector?




performance c++11 (3)

¿Hay alguna diferencia en el rendimiento entre los dos métodos a continuación para insertar nuevos elementos al final de un std::vector :

Método 1

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

Método 2

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

Personalmente, me gusta el método 2 porque es agradable y conciso e inserta todos los elementos nuevos de una matriz de una sola vez. Pero

  • ¿Hay alguna diferencia en el rendimiento?
  • Después de todo, hacen lo mismo. Ellos no?

Actualizar

La razón por la que no estoy inicializando el vector con todos los elementos, para empezar, es que en mi programa estoy agregando los elementos restantes en función de una condición.


Después de todo, hacen lo mismo. Ellos no?

No. Son diferentes El primer método que usa std::vector::push_back sufrirá varias reasignaciones en comparación con std::vector::insert .

La insert asignará memoria internamente, de acuerdo con la std::vector::capacity actual de std::vector::capacity antes de copiar el rango. Vea la siguiente discusión para más información:

¿Std :: vector :: insert reserva por definición?

¿Pero hay alguna diferencia en el rendimiento?

Debido a la razón explicada anteriormente, el segundo método mostraría una ligera mejora en el rendimiento. Por ejemplo, vea la marca de referencia rápida a continuación, usando http://quick-bench.com :

Ver referencia en línea

O escriba un programa de prueba para medir el rendimiento (como lo mencionó @Some programmer dude en los comentarios). El siguiente es un programa de prueba de muestra:

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

lanzamiento de la construcción con mi sistema (MSVS2019: / Ox / std: c ++ 17 , AMD Ryzen 7 2700x (8 núcleos, 3.70 Ghz) , x64 Windows 10 )

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

Lo que se muestra para el escenario dado, std::vector::insert inserting es aproximadamente 2.7 veces más rápido que std::vector::push_back .

Vea lo que otros compiladores ( clang 8.0 y gcc 9.2 ) quieren decir, de acuerdo con sus implementaciones: https://godbolt.org/z/DQrq51


El principal factor contribuyente serán las reasignaciones. vector tiene que hacer espacio para nuevos elementos.

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

Para confirmar las reasignaciones que vec.reserve(5) en la imagen, después de agregar un vec.reserve(5) en versiones pushback e insert, obtenemos los siguientes resultados.


push_back inserta un solo elemento, por lo tanto, en el peor de los casos, puede encontrar múltiples reasignaciones.

Por el bien del ejemplo, considere el caso donde la capacidad inicial es 2 y aumenta en un factor de 2 en cada reasignación. Entonces

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

Por supuesto, puede evitar reasignaciones innecesarias llamando a

vec.reserve(num_elements_to_push);

Sin embargo, si de alguna manera inserta desde una matriz, la forma más idónea es usar insert .





insert