2

I'm testing performance difference between pushing back Objects vs pushing back object Pointers to Vector in C++.

I've read in Stackoverflow and other articles that you should avoid pushing back pointers unless you must do so...

However, I realized that there is a HUGE performance gain for pushing back Pointers,,,

This is a simple test I ran:

tstart = chrono::system_clock::now();

vector<MyObject> VectorOfObjects;
for (int i=0; i<10000; i++) {
    MyObject x("test");
    VectorOfObjects.push_back(x);
}

tend = chrono::system_clock::now();
tt = tend-tstart;
cout << "Pushback Object: " << tt.count()*1000 << " Milliseconds\n" << endl;




tstart = chrono::system_clock::now();

vector<MyObject *> VectorOfPointers;
for (int i=0; i<10000; i++) {
    VectorOfPointers.push_back(new MyObject("test"));
}

tend = chrono::system_clock::now();
tt = tend-tstart;
cout << "Pushback Pointers: " << tt.count()*1000 << " Milliseconds\n" << endl;


The result is actually pretty surprising:

Pushback Objects:  989 Milliseconds
Pushback Pointers: 280 Milliseconds


As you can see, pushing back pointers is 3~4 times faster than pushing back objects! which is a huge performance difference, especially when dealing with large volume of data.

So my question is: WHY NOT USE Vector of Pointers??

Answers to almost every post on Stackoverflow regarding similar question says Avoid Vector of Pointers..

I know memory leakage might be a problem,, but we can always use Smart Pointers,, and even manually deleting the pointers at destruction is not that difficult..

I'm also curious about the cause of this performance difference..

Thanks


UPDATE:

Actually I tested on ideone .... and here, pushback objects is Faster!!!

In Visual Studio,, pushing back Objects was Wayyy slower..

Why is this...??

user2436815
  • 3,455
  • 5
  • 27
  • 40
  • 2
    If you need a vector of pointers, use a Boost ptr container. C++11's move semantics should cover copies of objects really well. And if you're giving us a performance sample, please actually do so with a complete example and compiler options. – chris Jul 22 '14 at 20:19
  • 1
    Usually, objects in a container (and objects in general) are accessed much more often than they are created. Have you tried measuring the cost of that? – Benjamin Lindley Jul 22 '14 at 20:21
  • How did you build this? The compiler should have optimized the first loop out. – juanchopanza Jul 22 '14 at 20:21
  • Instead of `push_back`, use `emplace_back` to construct the objects in-place. Will be even faster. – The Paramagnetic Croissant Jul 22 '14 at 20:23
  • @juanchopanza what do you mean by the compiler "optimizes the first loop out"?? – user2436815 Jul 22 '14 at 20:29
  • The compiler can figure out there are no effects from running the loop. If it can do this, it can remove the code entirely. So compile with optimizations turned on. – juanchopanza Jul 22 '14 at 20:31
  • 1
    @juanchopanza Sorry,, "no effects from running the loop"?? It's changing the state of the vector.. Also It shouldn't remove the code entirely.. No sure what you mean exactly – user2436815 Jul 22 '14 at 20:33
  • You can gain a lot of performance by reserving vector space before you start. – Thomas Matthews Jul 22 '14 at 20:35
  • 1
    I meant what I said. On top of that, 10000 calls to `new` are quite expensive. The vector of objects does a very small number of memory allocations, and you can reduce these to two by calling `VectorOfObjects.reserve(10000)` before filling it. And on top of that, the vector of object's data will be contiguous, and the pointer one scattered. Accessing the former will be faster. – juanchopanza Jul 22 '14 at 20:40
  • @user2436815, It doesn't matter if the state of the vector changes. The vector is never used after being filled. It makes no observable difference if it removed entirely. – chris Jul 22 '14 at 20:43
  • Anyway, compiling [this code](http://ideone.com/DtP39T) with clang 3.4 and -O3 optimization level the vector of objects is about 5 times faster than the vector of pointers. And that is without accessing the data. (Mac OSX 10.9.4). – juanchopanza Jul 22 '14 at 20:51
  • @juanchopanza Yeah I realized that too,, So why is my result like that only when ran in Visual Studio..? How do I check my compiler options in VS? – user2436815 Jul 22 '14 at 20:58
  • Ideone is not a good profiler. If you want to profile using other compilers you should download and use them. – Fozi Jul 22 '14 at 21:01
  • vector of objects allow for good Prefetching, which affect use performance a great deal. also, creation should be done with `emplace_back` which will be at least as fast as the pointer version – sp2danny Jul 22 '14 at 21:03
  • The reason of performance difference is that. In C++ you use "NEW" to allocate memory. Every time you call "NEW" you are allocating memory. However with the pointers, you just created a pointer x and assigned memory location once. Then you reuse the same pointer and same memory address. Whenever you create an object with "NEW", the Object`s default constructor get called first, then member variables initialization. If your Vector is pointing to an Object then it`s making a reference to memory address, where else if your Vector is pointing to the same pointer x then it`s pointing to a value. – Juniar Jul 23 '14 at 03:15

3 Answers3

4

To be fair, when measuring your code you should account for deallocation of all those pointers. A sample code would read as :

#include <chrono>
#include <string>
#include <iostream>
#include <functional>
#include <vector>

using namespace std;

// 1. A way to easily measure elapsed time -------------------
template<typename TimeT = std::chrono::milliseconds>
struct measure
{
    template<typename F>
    static typename TimeT::rep execution(F const &func)
    {
        auto start = std::chrono::system_clock::now();
        func();
        auto duration = std::chrono::duration_cast< TimeT>(
            std::chrono::system_clock::now() - start);
        return duration.count();
    }
};
// -----------------------------------------------------------


// 2. MyObject -----------------------------------------------
struct MyObject {
    string mem;
    MyObject(const char *text) : mem(text) {};
};
// -----------------------------------------------------------


int main() 
{
    vector<MyObject> VectorOfObjects;
    vector<MyObject *> VectorOfPointers;

    cout << "Pushback Object: " << measure<>::execution([&]()
    {
        for (int i = 0; i < 100000; i++) {
            MyObject x("test");
            VectorOfObjects.push_back(x);
        }
    }) << endl;


    cout << "Pushback Pointers: " << measure<>::execution([&]()
    {
        for (int i = 0; i < 100000; i++) 
            VectorOfPointers.push_back(new MyObject("test"));
        for (auto &item : VectorOfPointers) 
            delete item;
    }) << endl;

    return 0;
}

and when compiled with

g++ -std=c++11 -O3 -march=native -Wall -pedantic

the results are (I'm using +1 order of magnitude in the for loops) :

Pushback Object: 20

Pushback Pointers: 32


  • If you used

    VectorOfObjects.emplace_back("test");

    The duration of the VectorOfObjects modification would drop to 18

  • If you preallocated both vectors

    vector<MyObject> VectorOfObjects;
    VectorOfObjects.reserve(100000);
    
    vector<MyObject *> VectorOfPointers;
    VectorOfPointers.reserve(100000);
    

    the result would be 17-34 (for the vector of objects again)

  • If you use a vector of unique pointers then the results are similar

    vector<unique_ptr<MyObject>> VectorOfPointers;
    

    note that I'm limiting the scope of the vectors to explicitly account for the destruction of the smart pointers

  • Other choices would include boost's pointer containers in which case the related data structure would be a pointer vector

Community
  • 1
  • 1
Nikos Athanasiou
  • 29,616
  • 15
  • 87
  • 153
  • Thanks and Respect for rerunning my test :) – user2436815 Jul 22 '14 at 20:55
  • 1
    But I don't understand why I got that result when I ran the test in Visual Studio.... Do you know how i can check my compiler option in VS,, and why this effects the performance..? – user2436815 Jul 22 '14 at 20:56
  • This answer is not a very good one: either use emplace or use shared pointers. Using naked pointers in STL containers is not considered good style in C++11, using delete fixes the memory leaks but does not change the fact that this is not the way to go. – Fozi Jul 22 '14 at 20:59
  • @Fozi I've actually asked another question on Stackoverflow regarding that issue.. Why exactly is using naked pointers bad, if simply deleting fixes the memory issue?? I mean there are cases where use have no option but to use raw pointers.. For example, [covariant return types](http://stackoverflow.com/questions/196733/how-can-i-use-covariant-return-types-with-smart-pointers)... Is there an article that explains why storing raw pointers in STL containers is bad? – user2436815 Jul 22 '14 at 21:04
  • @user2436815: The behavior _could_ be caused by the fact that VS doesn't have a `noexcept` specifier, and thus probably assumes that moves throw, which means vector resizing will _deep copy_ instead of simply moving, for exception safety. If so, if one could convince VS that the move assignment/constructor wouldn't throw, that _might_ have a significant performance impact. – Mooing Duck Jul 22 '14 at 21:18
  • @user2436815 Just the fact that you have code that deletes objects is bad: It's unnecessary, it's boilerplate code, it can have bugs or be misused, it adds to your LOC with no added functionality or efficiency and it took you or someone else time to think about it, write it and potentially maintain it. In a professional environment all of the above is Really Bad (tm). – Fozi Jul 23 '14 at 00:28
2

I prefer to use shared pointers opposed to regular pointers and I always use them when I can.

I use shared pointers with vectors when dealing with the vector changing a lot.

You should avoid regular pointers when dealing with vectors, as they need to be manually destructed and will just cause memory leaks.

So to answer your question...

Look into the shared_ptr library and use those instead here is a link http://www.cplusplus.com/reference/memory/shared_ptr/

hope this answers your question

Darrell
  • 638
  • 4
  • 17
2

The way your sample code is written, there is definitly a memory leak problem. Agreed that you can fix that problem by doing a deletes.

It can be done but it is just cumbersome. If you take care of things like memory leaks, it is fine.

The root issue for performance here is that there are copies of objects being made. You create an object. When you add it to the vector. It creates a new object and copies yours using the copy constructor.

C++11 improves the situation a bit by introducing emplace_back(). So if you are using C++11, you may be able to get the same performance by using emplace.

Yasser Asmi
  • 1,150
  • 2
  • 12
  • 27