Once std::vector is filled (size() equals to capacity()), a subsequent push_back(…) results in an exponential expansion of the vector capacity. The following table shows that the expansion happens when the index reaches a power of two:
index: 0, capacity: 1, address: 0x1fa6c20 index: 1, capacity: 2, address: 0x1fa6c40 index: 2, capacity: 4, address: 0x1fa6c20 index: 4, capacity: 8, address: 0x1fa6c60 index: 8, capacity: 16, address: 0x1fa6c90 index: 16, capacity: 32, address: 0x1fa6ce0 index: 32, capacity: 64, address: 0x1fa6d70 index: 64, capacity: 128, address: 0x1fa6e80 index: 128, capacity: 256, address: 0x1fa7090 index: 256, capacity: 512, address: 0x1fa74a0 index: 512, capacity: 1024, address: 0x1fa7cb0 index: 1024, capacity: 2048, address: 0x1fa8cc0 index: 2048, capacity: 4096, address: 0x1faacd0 index: 4096, capacity: 8192, address: 0x1faece0 index: 8192, capacity: 16384, address: 0x1fb6cf0 index: 16384, capacity: 32768, address: 0x1fc6d00 index: 32768, capacity: 65536, address: 0x1fe6d10 index: 65536, capacity: 131072, address: 0x2026d20 index: 131072, capacity: 262144, address: 0x20a6d30 index: 262144, capacity: 524288, address: 0x21a6d40 index: 524288, capacity: 1048576, address: 0x23a6d50 index: 1048576, capacity: 2097152, address: 0x27a6d60 index: 2097152, capacity: 4194304, address: 0x2fa6d70 index: 4194304, capacity: 8388608, address: 0x7f8e9225f010 index: 8388608, capacity: 16777216, address: 0x7f8e8e25e010 index: 16777216, capacity: 33554432, address: 0x7f8e8625d010 index: 33554432, capacity: 67108864, address: 0x7f8e7625c010 index: 67108864, capacity: 134217728, address: 0x7f8e5625b010 index: 134217728, capacity: 268435456, address: 0x7f8e1625a010 index: 268435456, capacity: 536870912, address: 0x7f8d96259010 index: 536870912, capacity: 1073741824, address: 0x7f8c96258010
This is why std::vector::push_back complexity is amortized constant. Below I provided the source code that generates this table:
#include <iostream>
#include <vector>
#include <chrono>
typedef std::chrono::steady_clock Clock;
void test(bool reserve)
{
std::vector<int> v;
std::cerr << "Reserve: " << (reserve?"true":"false") << "\n";
size_t size = 1000 * 1000 * 1000;
if(reserve)
{
v.reserve(size);
//v.resize(size);
//v.clear();
}
size_t last_cap = v.capacity();
const auto start_time = Clock::now();
for (size_t i = 0; i < size; ++i)
{
v.push_back(i);
size_t cur_cap = v.capacity();
if (cur_cap != last_cap)
{
last_cap = cur_cap;
std::cout << "index: " << i << ", capacity: " << last_cap << ", address: " << static_cast<void *>(v.data()) << std::endl;
}
}
const auto end_time = Clock::now();
std::cout << "time elapsed: " << 1e-6*std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count() << " s\n";
}
int main()
{
test(false);
test(true);
test(false);
}
In opposite to std::vector, the insertion or removal of elements of std::deque at the end or beginning is constant O(1), but not amortized constant.

