Skip to Content
Course content

63: Vectors: Growth and Performance

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I once worked with a junior dev on a real-time physics engine for a small indie project. He had a system that spawned debris particles whenever an object shattered. Everything looked buttery smooth for the first few seconds, but then he noticed these rhythmic, microscopic stutters—"micro-hitches," we call them. He checked his algorithms; they were $O(n)$. He checked his memory leaks; there were none. It took me about ten minutes of profiling to show him that the hitches happened exactly when his std::vector<Particle> hit its capacity limit and had to reallocate.

If you've been using vectors, you know they're great because they feel like dynamic arrays. You just keep calling push_back() and the vector "grows." But in C++, nothing grows for free. A vector is just a thin wrapper around a raw heap-allocated array. When you add an element and the underlying array is full, the vector can't just "expand" the existing memory—it might be blocked by other data on the heap. Instead, it has to find a completely new, larger chunk of memory, copy every single existing element over to the new location, and then delete the old array.

The Mechanics of Exponential Growth

To keep push_back() efficient, C++ vectors don't just grow by one slot every time they fill up; that would be a performance nightmare. Instead, they use a growth factor—usually doubling the capacity (though some implementations use 1.5x). This is why we say push_back() has an amortized constant time complexity. Most of the time, it's just a pointer increment and an assignment. Every once in a while, it's a massive move operation.

You can see this in action by tracking the difference between size() and capacity(). The size is how many elements are actually in the vector; the capacity is how many it could hold before it needs to reallocate. Watch what happens here:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> vec;
    for(int i = 0; i < 10; ++i) {
        vec.push_back(i);
        std::cout < "Size: " < vec.size() < " Capacity: " < vec.capacity() < "\n";
    }
    return 0;
}

If you run this, you'll see the capacity jump in powers of two (or similar). It’s a clever trade-off: we waste a little bit of memory to avoid spending too much time copying data.

Eliminating Reallocations with Reserve

The "hitch" my colleague experienced happened because he was pushing thousands of particles into a vector without telling the vector how many to expect. The vector was reallocating and copying its entire contents dozens of times per frame. The fix is simple: reserve().

When you call vec.reserve(1000), you're telling the vector, "I don't have the data yet, but I know I'm going to need room for at least 1,000 elements." The vector allocates that memory immediately. Now, the first 1,000 calls to push_back() are guaranteed to be fast, with zero reallocations. I always tell people: if you have a rough idea of how much data you're about to load, reserve() is the single easiest performance win in the C++ standard library.

Just be careful not to confuse reserve() with resize(). resize() actually changes the size of the vector and default-constructs new elements. reserve() only changes the capacity; the vector still thinks it's empty, it just has a bigger "parking lot" ready for the data you're about to bring in.




📋 Practical Task

Performance Benchmarking: The Particle Burst Simulator

In this exercise, you will build a small simulation to quantify the performance difference between a "naive" vector growth strategy and an optimized "reserved" strategy.

Your Task:

  • Create a program that simulates loading 100,000 "Particle" objects (a simple struct with three doubles for x, y, z coordinates) into a std::vector.
  • Implement two separate functions: loadNaive() which simply calls push_back() in a loop, and loadOptimized() which calls reserve(100000) before the loop.
  • Use the <chrono> library to measure the execution time of both functions.
  • Print the time difference to the console.

Success Criteria: Your program should clearly demonstrate that loadOptimized() is faster than loadNaive(), and you should see a significant reduction in the time spent populating the vector.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.