Monday, 2 July 2012

STL Iterator versus Auto (C++)


I've had an interesting time with a piece of C++ overnight, I've been using the std vector class to hold a set of values, let say integers, and I've been interating through the contents of the vector to give me the list when I need to go through it.. I've had no issues with this, here's my code:

#include <vector>
#include <iostream>

using namespace std;

void foo ()
{
vector<int> nums;
nums.push_back(1);
nums.push_back(2);
nums.push_back(3);
for (vector<int>::const_iterator itr = nums.begin(); 
itr != nums.end(); 
++itr)
{
cout << (*itr) << endl;
}
}

This has worked fine, been fast, and easy to comprehend... But I let someone else review my code, just a snippet, and they came up with this rather bolshy "you must use an auto" comment and changed the code, thus:

for (auto pos = nums.begin(), end = nums.end();
pos != end; 
++pos)
{
cout << (*pos) << endl;
}
I took this on the chin, never complained, and had a look at it, the code certainly worked, just as it always had... So I immediately thought, is this another case of change for changes sake... something which has been rearing its ugly head in my direction time and again for weeks; months in places...

I started to do some timings, here's the complete timing code:

void autoUseFoo()
{
    vector<int> l_vector = DefaultVector();
    int l_timeA, l_timeB;
    timespec l_StartTime, l_EndTime;

    int i = 0;
    while ( i < 100 )
    {
        l_timeA = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &l_StartTime);

        for (auto pos = l_vector.begin(), end = l_vector.end();
                  pos != end; 
                  ++pos)
        {
            cout << (*pos) << endl;
        }

        l_timeB = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &l_EndTime);

        OutputTime (c_Auto, l_StartTime, l_EndTime);
        i++;
    }
}

void autoIteratorFoo()
{
    vector<int> l_vector = DefaultVector();
    int l_timeA, l_timeB;
    timespec l_StartTime, l_EndTime;

    int i = 0;
    while ( i < 100 )
    {
        l_timeA = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &l_StartTime);

        for (vector<int>::const_iterator pos = l_vector.begin();
                  pos != l_vector.end(); 
                  ++pos)
        {
            cout << (*pos) << endl;
        }

        l_timeB = clock_gettime(CLOCK_THREAD_CPUTIME_ID, &l_EndTime);

        OutputTime (c_Itr, l_StartTime, l_EndTime);
        i++;
    }
}

As you can see, I clock the time before and the time after the loop, one using a const_iterator, the other using an auto-range... My argument being that because in this isntance I don't need to change the items being output, to use the auto results in needing to go through a construction (of the auto object) unneccesarily, slowing my code.

I produced this code, and I came up with exactly what I expected, auto results show a large spike of over 12000ns to set up, and then approximately 4300ns to go through the vector... Whilst the const iterator shows no spike and performs the loop through in an approximate average of 4200ns... That, to me, tells me my code is faster, its also in my opinion easier to read, and more easily changed to support better casting when we use complex objects, say to call functions... Especially is one typedefs the ugly "vector<int>::const_iterator" type to somethig more legible.

I showed my retort, I infact have written this up in my project notes, and given reasoning and code examples as well as timings taken over 10, 100 and 100,000 iterations of the while loops shown above, each and everytime the iterator is faster than the auto... Yet this person refused to listen, refused to even look at my reply... Their input "auto is right"...

This is code, there is no right or wrong, they are just different ways to get to the same conclusion, just that my solution results in quicker code and hence a slightly better user experience... and I accept that its only 100 nanoseconds on my timings, plus the spike, but if I were willing to accept that today, what would I accept tomorrow?... How much worse could the user experience be?

4 comments:

  1. I think the above test code is not the same condition.
    const_iterator is faster than iterator as my experiences.
    'pos != end' and 'pos != l_vector.end()' are very different.
    I recommend that you examine assembly codes for the for_loop.

    ReplyDelete
    Replies
    1. Aye, I know const_iterator is quicker, and that was how my code worked... Someone else decided to change it, insisting I use "auto"... which I hate... Jump back to the top of my post and have a read, you'll see "I let someone else review my code, just a snippet, and they came up with this rather bolshy 'you must use an auto" comment and changed the code'". Which really annoyed me. I like const_iterator over auto every single day of the week, I hate auto's.

      Delete
  2. The question about using auto with no specific reason is a good question to ask. auto is begin bandied about as the new silver bullet for all the problems you didn't know you had.
    Nor is improved performance always the right answer. If you're writing a general purpose library or one with many common classes then auto is probably the right answer.
    if your code isn't run in an inner loop and executed millions of times per second, saving 12 microseconds isn't important.

    ReplyDelete