Russell Butler
Russell Butler

Reputation: 346

why is my rudimentary implementation of Vector faster than the stl version for push_back?

I have implemented a rudimentary vector using the code from the Weiss C++ textbook on data structures (see below). when i time it with 100,000 push_backs it takes 0.001 seconds.

when i do the exact same experiment using the stl::vector, it takes 0.008 seconds (roughly 8x slower). does anyone know why this is? thanks

#include <iostream>
#include <algorithm>
#include <ctime>
#include <vector>

template<typename Object>
class Vector {

public:

    // normal constructor
    explicit Vector(int initialSize = 0) :
        theSize{ initialSize }, theCapacity{ initialSize + SPARE_CAPACITY },
        objects{ new Object[theCapacity] }
    {}

    // copy constructor
    Vector(const Vector& rhs) :
        theSize{ rhs.theSize }, theCapacity{ rhs.theCapacity }, objects{ nullptr }
    {
        objects = new Object[theCapacity];
        for (int k = 0; k < theSize; ++k)
            objects[k] = rhs.objects[k];
    }

    // copy assignment operator
    Vector& operator=(const Vector& rhs)
    {
        Vector copy = rhs;
        std::swap(*this, copy);
        return *this;
    }

    // destructor
    ~Vector()
    {
        delete[] objects;
    }

    // move constructor
    Vector(Vector&& rhs) :
        theSize{ rhs.theSize }, theCapacity{ rhs.theCapacity }, objects{ rhs.objects }
    {
        rhs.objects = nullptr;
        rhs.theSize = 0;
        rhs.theCapacity = 0;
    }

    // move assignment operator
    Vector& operator=(Vector&& rhs)
    {
        std::swap(theSize, rhs.theSize);
        std::swap(theCapacity, rhs.theCapacity);
        std::swap(objects, rhs.objects);

        return *this;
    }

    void resize(int newSize)
    {
        if (newSize > theCapacity)
            reserve(newSize * 2); // talk about amortized time (python book)
        theSize = newSize;
    }

    void reserve(int newCapacity)
    {
        if (newCapacity < theSize)
            return;

        Object* newArray = new Object[newCapacity];
        for (int k = 0; k < theSize; ++k)
            newArray[k] = std::move(objects[k]);

        theCapacity = newCapacity;
        std::swap(objects, newArray);
        delete[] newArray;
    }

    Object& operator[](int index)
    {
        return objects[index];
    }

    const Object& operator[](int index)const
    {
        return objects[index];
    }

    bool empty() const
    {
        return size() == 0;
    }

    int size() const
    {
        return theSize;
    }

    int capacity() const
    {
        return theCapacity; 
    }

    void push_back(const Object& x)
    {
        if (theSize == theCapacity)
            reserve(2 * theCapacity + 1);

        objects[theSize++] = x; 
    }

    void push_back(Object&& x)
    {
        if (theSize == theCapacity)
            reserve(2 * theCapacity + 1);

        objects[theSize++] = std::move(x); 
    }

    void pop_back()
    {
        --theSize;
    }

    const Object& back() const
    {
        return objects[theSize - 1];
    }

    // iterator
    typedef Object* iterator;
    typedef const Object* const_iterator;

    iterator begin()
    {
        return &objects[0];
    }
    const_iterator begin() const
    {
        return &objects[0];
    }
    iterator end()
    {
        return &objects[size()];
    }
    const_iterator end() const
    {
        return &objects[size()];
    }

    static const int SPARE_CAPACITY = 16; 

private:
    int theSize;
    int theCapacity;
    Object* objects; 
};


int main()
{
    std::clock_t start;
    start = std::clock(); 
    std::vector<int> vec2{ 0 };
    for (int i = 0; i < 100000; i++)
        vec2.push_back(i);
    double duration = (std::clock() - start) / (double)CLOCKS_PER_SEC;

    std::cout << "printf: " << duration << '\n';


    start = std::clock();       
    Vector<int> vec{ 0 };
    for (int i = 0; i < 100000; i++)
        vec.push_back(i);

    duration = (std::clock() - start) / (double)CLOCKS_PER_SEC;

    std::cout << "printf: " << duration << '\n';


    
    
}

Upvotes: 0

Views: 133

Answers (2)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

Reputation: 275585

You compiled it in debug.

In debug, visual studio instruments std::vector with a bunch of debugging aids. It will detect in some cases if you try to use invalidated iterators, for example.

In general, doing performance testing in debug is useless. The only reason you should do it is if you are having problems where your debug build it too slow for development purposes.

Real use of your application should be done in release mode, which does optimization, and also removes extra "double check" work to help find bugs.

Your vector is indeed different, but that difference isn't that important at all.

Upvotes: 1

Sam Varshavchik
Sam Varshavchik

Reputation: 118352

Your vector implementation and std::vector are fundamentally different.

Your vector default-constructs all values in the vector, up to reserve capacity, and push_back() merely replaces the next reserved value with the new value, using the assignment operator.

std::vector is fundamentally different,, and does not default-construct "non-existent" values in the vector, but constructs them "for real-sies". std::vector::push_back constructs the new value in the vector, your push_back assigns it.

Depending on the object in the container, its assignment operator and its constructor may have completely different logic, and comparative benchmarks are meaningless.

Upvotes: 0

Related Questions