Benchmark de rendimiento: constructor por rango de std::vector vs initializer list vs reserve() & push_back
Existen varias formas de llenar un std::vector en C++ si conoces el número de elementos de antemano, como por ejemplo en
data.cpp
const double data[10] = {1,2,3,4,5,6,7,8,9,10};Usando el constructor por rango
vector_range_constructor.cpp
std::vector<double> vec1(data, data + 10);Usando initializer list
vector_initializer_list.cpp
std::vector<double> vec2 = {data[0], data[1], data[2], data[3], data[4],
data[5], data[6], data[7], data[8], data[9]};Usando reserve() y push_back()
vector_reserve_push_back.cpp
std::vector<double> vec3;
vec3.reserve(10);
for (size_t i = 0; i < 10; ++i) {
vec3.push_back(data[i]);
}Resultados del benchmark
En un Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, compilado usando
compile.sh
g++ --std=c++17 -o vector_benchmark vector_benchmark.cpp -march=native -O3los resultados del benchmark son los siguientes:
| Size | Range Ctor | Push Back | Initializer |
|---|---|---|---|
| 1 | 0.0233655 s | 0.0255561 s | 0.0298313 s |
| 10 | 0.0207084 s | 0.0282417 s | 0.0210478 s |
| 100 | 0.0360425 s | 0.119358 s | 0.040467 s |
| 1000 | 0.18665 s | 0.980142 s | N/A |
| 10000 | 1.89109 s | 15.4342 s | N/A |
Observaciones:
- El constructor por rango es consistentemente el método más rápido en todos los tamaños probados.
- La initializer list es competitiva para tamaños pequeños
push_back()conreserve()es significativamente más lento, especialmente a medida que aumenta el tamaño.
Por lo tanto, podemos concluir que para aplicaciones críticas en rendimiento donde el tamaño se conoce de antemano, usar el constructor por rango es el método preferido para llenar un std::vector. Esto es particularmente cierto para conjuntos de datos más grandes, y también ayuda a mantener el código limpio y conciso.
Sin embargo, te animo a descargar el código fuente del benchmark a continuación y ejecutarlo en tu propio hardware para verificar estos resultados.
Código fuente del benchmark
vector_benchmark.cpp
#include <vector>
#include <array>
#include <chrono>
#include <iostream>
#include <random>
#include <iomanip>
#include <functional>
#include <cassert>
// Datos de prueba fijos
const std::array<int, 10000> data = []{
std::array<int, 10000> arr{};
std::mt19937 gen(42);
std::uniform_int_distribution<> dist(0, 1000);
for (auto& val : arr) {
val = dist(gen);
}
return arr;
}();
// Variante 1: Constructor por rango
std::vector<double> variant1(size_t N) {
return std::vector<double>(data.begin(), data.begin() + N);
}
// Variante 2: Push back con reserve
std::vector<double> variant2(size_t N) {
std::vector<double> out;
out.reserve(N);
for (size_t i = 0; i < N; ++i) {
out.push_back(static_cast<double>(data[i]));
}
return out;
}
// Variantes reales de initializer list para diferentes tamaños
std::vector<double> variant3_1() {
return {static_cast<double>(data[0])};
}
std::vector<double> variant3_10() {
return {
static_cast<double>(data[0]), static_cast<double>(data[1]),
static_cast<double>(data[2]), static_cast<double>(data[3]),
static_cast<double>(data[4]), static_cast<double>(data[5]),
static_cast<double>(data[6]), static_cast<double>(data[7]),
static_cast<double>(data[8]), static_cast<double>(data[9])
};
}
std::vector<double> variant3_100() {
return {
static_cast<double>(data[0]), static_cast<double>(data[1]), static_cast<double>(data[2]), static_cast<double>(data[3]),
static_cast<double>(data[4]), static_cast<double>(data[5]), static_cast<double>(data[6]), static_cast<double>(data[7]),
static_cast<double>(data[8]), static_cast<double>(data[9]), static_cast<double>(data[10]), static_cast<double>(data[11]),
static_cast<double>(data[12]), static_cast<double>(data[13]), static_cast<double>(data[14]), static_cast<double>(data[15]),
static_cast<double>(data[16]), static_cast<double>(data[17]), static_cast<double>(data[18]), static_cast<double>(data[19]),
static_cast<double>(data[20]), static_cast<double>(data[21]), static_cast<double>(data[22]), static_cast<double>(data[23]),
static_cast<double>(data[24]), static_cast<double>(data[25]), static_cast<double>(data[26]), static_cast<double>(data[27]),
static_cast<double>(data[28]), static_cast<double>(data[29]), static_cast<double>(data[30]), static_cast<double>(data[31]),
static_cast<double>(data[32]), static_cast<double>(data[33]), static_cast<double>(data[34]), static_cast<double>(data[35]),
static_cast<double>(data[36]), static_cast<double>(data[37]), static_cast<double>(data[38]), static_cast<double>(data[39]),
static_cast<double>(data[40]), static_cast<double>(data[41]), static_cast<double>(data[42]), static_cast<double>(data[43]),
static_cast<double>(data[44]), static_cast<double>(data[45]), static_cast<double>(data[46]), static_cast<double>(data[47]),
static_cast<double>(data[48]), static_cast<double>(data[49]), static_cast<double>(data[50]), static_cast<double>(data[51]),
static_cast<double>(data[52]), static_cast<double>(data[53]), static_cast<double>(data[54]), static_cast<double>(data[55]),
static_cast<double>(data[56]), static_cast<double>(data[57]), static_cast<double>(data[58]), static_cast<double>(data[59]),
static_cast<double>(data[60]), static_cast<double>(data[61]), static_cast<double>(data[62]), static_cast<double>(data[63]),
static_cast<double>(data[64]), static_cast<double>(data[65]), static_cast<double>(data[66]), static_cast<double>(data[67]),
static_cast<double>(data[68]), static_cast<double>(data[69]), static_cast<double>(data[70]), static_cast<double>(data[71]),
static_cast<double>(data[72]), static_cast<double>(data[73]), static_cast<double>(data[74]), static_cast<double>(data[75]),
static_cast<double>(data[76]), static_cast<double>(data[77]), static_cast<double>(data[78]), static_cast<double>(data[79]),
static_cast<double>(data[80]), static_cast<double>(data[81]), static_cast<double>(data[82]), static_cast<double>(data[83]),
static_cast<double>(data[84]), static_cast<double>(data[85]), static_cast<double>(data[86]), static_cast<double>(data[87]),
static_cast<double>(data[88]), static_cast<double>(data[89]), static_cast<double>(data[90]), static_cast<double>(data[91]),
static_cast<double>(data[92]), static_cast<double>(data[93]), static_cast<double>(data[94]), static_cast<double>(data[95]),
static_cast<double>(data[96]), static_cast<double>(data[97]), static_cast<double>(data[98]), static_cast<double>(data[99])
};
}
// Función de benchmark
template<typename Func>
double benchmark(Func func, size_t iterations) {
auto start = std::chrono::high_resolution_clock::now();
for (size_t i = 0; i < iterations; ++i) {
volatile auto result = func();
(void)result;
}
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> elapsed = end - start;
return elapsed.count();
}
int main() {
const std::vector<size_t> sizes = {1, 10, 100, 1000, 10000};
const size_t iterations = 1'000'000;
std::cout << "Benchmarking vector initialization methods\n";
std::cout << "=========================================\n";
std::cout << std::setw(8) << "Size" << " | "
<< std::setw(20) << "Range Ctor" << " | "
<< std::setw(20) << "Push Back" << " | "
<< std::setw(20) << "Initializer\n";
std::cout << "-----------------------------------------\n";
for (size_t N : sizes) {
// Calentamiento
for (int i = 0; i < 3; ++i) {
variant1(N);
variant2(N);
if (N == 1) variant3_1();
else if (N == 10) variant3_10();
else if (N == 100) variant3_100();
}
// Benchmark
double time1 = benchmark([N]{ return variant1(N); }, iterations);
double time2 = benchmark([N]{ return variant2(N); }, iterations);
double time3 = 0.0;
if (N == 1) time3 = benchmark(variant3_1, iterations);
else if (N == 10) time3 = benchmark(variant3_10, iterations);
else if (N == 100) time3 = benchmark(variant3_100, iterations);
// Imprimir resultados
std::cout << std::setw(8) << N << " | "
<< std::setw(18) << time1 << " s | "
<< std::setw(18) << time2 << " s | ";
if (N <= 100) {
std::cout << std::setw(18) << time3 << " s";
} else {
std::cout << std::setw(18) << "N/A";
}
std::cout << "\n";
// Verificar que todas las variantes producen el mismo resultado
auto r1 = variant1(N);
auto r2 = variant2(N);
if (r1 != r2) {
std::cerr << "Error: Variants produce different results for size " << N << "!\n";
return 1;
}
// Verificar initializer list para los tamaños soportados
if (N <= 100) {
std::vector<double> r3;
if (N == 1) r3 = variant3_1();
else if (N == 10) r3 = variant3_10();
else if (N == 100) r3 = variant3_100();
if (r1.size() != r3.size() || !std::equal(r1.begin(), r1.end(), r3.begin())) {
std::cerr << "Error: Initializer list produces different results for size " << N << "!\n";
return 1;
}
}
}
return 0;
}Echa un vistazo a artículos similares por categoría:
C/C++
Si este artículo te ha ayudado, considera invitarme a un café o hacer una donación vía PayPal para apoyar la investigación y publicación de nuevos artículos en TechOverflow