Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Implementation of back() in std::vector

Tags:

c++

stl

Why is the Vector back() method implemented in terms of iterators

reference back()
{   // return last element of mutable sequence
    return (*(end() - 1));
}

rather than something like...

return (*(_Myfirst + size()));

Background to this question:

I have been working recently on optimising some legacy code (an allocator implementation) and noticed that a significant amount of time was being spent in std::vector::back(). So I did some experiments using different collections (Vector vs List vs Deque) and since back() is basically retrieving the last element in the collection so I also compared vector::back() against vector[size()-1]

This is the code I used to test:

#include <vector>
#include <list>
#include <deque>
#include <algorithm>
#include <boost/timer/timer.hpp>

int RandomNumber () { return (rand()%100); }



void doVector( std::vector<int>& test_vec )
{
    std::cout << "vect back() = " << test_vec.back() << std::endl;
    {
        boost::timer::auto_cpu_timer t;
        for (int i = 0; i < 100000000; i++ )
            test_vec.back();
    }
}

void doVector2( std::vector<int>& test_vec )
{
    std::cout << "vect [size()-1] = " << test_vec[test_vec.size()-1] << std::endl;
    {
        boost::timer::auto_cpu_timer t;
        for (int i = 0; i < 100000000; i++ )
            test_vec[test_vec.size()-1];
    }

}


void doList( std::vector<int>& test_vec )
{
    std::list<int> test_list(test_vec.begin(),test_vec.end());
    std::cout << "list back() = " << test_list.back() << std::endl;

    {
        boost::timer::auto_cpu_timer t;
        for (int i = 0; i < 100000000; i++ )
            test_list.back();
    }

}

void doDeque( std::vector<int>& test_vec )
{
    std::deque<int> test_deq(test_vec.begin(),test_vec.end());
    std::cout << "Deque back() = " << test_deq.back() << std::endl;

    {
        boost::timer::auto_cpu_timer t;
        for (int i = 0; i < 100000000; i++ )
            test_deq.back();
    }

}


int _tmain(int argc, _TCHAR* argv[])
{
    std::vector<int> test_vec(100);
    std::generate(test_vec.begin(), test_vec.end(), RandomNumber );

    doVector(test_vec);
    doVector2(test_vec);
    doList(test_vec);
    doDeque(test_vec);
}

The results were :

Removed Results because I had turned on Debug / turned off Optimisations in release times in the order of seconds - I should have seen that

Clearly I will gain significantly from using vect[ size() - 1] and further to that I will also gain by using vect[0] over front(). I realise that I am tying myself to vector but in the short term that's my chosen direction (Quick win). But, it made me think - why is it that the vector implementations of front() and back() use a less efficient implementation under the hood?


vect back() = 41 0.183862s wall, 0.171601s user + 0.000000s system = 0.171601s CPU (93.3%)

vect [size()-1] = 41 0.416969s wall, 0.421203s user + 0.000000s system = 0.421203s CPU (101.0%)

list back() = 41 0.079119s wall, 0.078001s user + 0.000000s system = 0.078001s CPU (98.6%)

Deque back() = 41 0.186574s wall, 0.187201s user + 0.000000s system = 0.187201s CPU (100.3%)

Clearly I was looking at the wrong results when I did my analysis - as this totally backs up the choice of the implementation. Apologies to people who looked at this before this edit.....

like image 764
Caribou Avatar asked Jul 15 '26 05:07

Caribou


2 Answers

The C++ specification defines semantics and not implementation. If it matters on an implementation that std::vector<T, A>::back() is implemented using a different implementation than the specification suggests, an implementation should just do the Right Thing! Obviously, it needs to still deliver the correct semantics.

The basic reason it is specified as is probably comes from the original implementation: The iterator type of std::vector<T, A> was just a T* (which is still a valid implementation although one which has become unfashionable for various reason). Contemporary std::vector<T, A> implementations tend to use a simple wrapper for pointers.

All that said, the overhead you see should actually be optimized because for decent compilers: What optimization flags did you use?

like image 77
Dietmar Kühl Avatar answered Jul 17 '26 17:07

Dietmar Kühl


From your question _Myfirst + size() I guess you are using MSVC that has iterator debugging, are you sure that you are using release build for your test, since in debug build iterator debugging take some extra time, but in release mode they both take equal time(at least in my machine using MSVC 2010)

like image 39
BigBoss Avatar answered Jul 17 '26 16:07

BigBoss



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!