std::vector range constructor vs initializer list vs reserve() & push_back performance benchmark
There are several ways to populate a std::vector in C++ if you know the number of elements in advance, such as in
data.cpp
const double data[10] = {1,2,3,4,5,6,7,8,9,10};Using range constructor
vector_range_constructor.cpp
std::vector<double> vec1(data, data + 10);Using 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]};Using reserve() and 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]);
}Benchmark results
On Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz, compiled using
compile.sh
g++ --std=c++17 -o vector_benchmark vector_benchmark.cpp -march=native -O3the benchmark results are as follows:
| 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 |
Observations:
- The range constructor is consistently the fastest method across all tested sizes.
- The initializer list is competitive for small sizes
push_back()withreserve()is significantly slower, especially as the size increases.
Hence, we can conclude that for performance-critical applications where the size is known in advance, using the range constructor is the preferred method to populate a std::vector. This is particularly true for larger datasets, and also helps to keep the code clean and concise.
Yet, I encourage you to download the benchmark source code below and run it on your own hardware to verify these results.
Benchmark source code
vector_benchmark.cpp
#include <vector>
#include <array>
#include <chrono>
#include <iostream>
#include <random>
#include <iomanip>
#include <functional>
#include <cassert>
// Fixed test data
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;
}();
// Variant 1: Range constructor
std::vector<double> variant1(size_t N) {
return std::vector<double>(data.begin(), data.begin() + N);
}
// Variant 2: Push back with 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;
}
// Actual initializer list variants for different sizes
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])
};
}
// Benchmark function
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) {
// Warm-up
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);
// Print results
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";
// Verify all variants produce the same result
auto r1 = variant1(N);
auto r2 = variant2(N);
if (r1 != r2) {
std::cerr << "Error: Variants produce different results for size " << N << "!\n";
return 1;
}
// Verify initializer list for supported sizes
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;
}Check out similar posts by category:
C/C++
If this post helped you, please consider buying me a coffee or donating via PayPal to support research & publishing of new posts on TechOverflow