3

I have a program which creates a big amount of objects and inserts them in a vector. My idea was to create about 10.000 objects, but I found out that the program crashes after a few thousands. The amount of objects created before crashing is random an depends on if I modify any line in the code, so I suppose is a memory allocation problem related.

The object I am creating is this one:

class Object {
public:

    //Needed by map
    Object() {

    }

    Object(int newID, std::string newText) {
        id = newID;
        text = newText;
    }

    int getID() {
        return id;
    }

    std::string getText() {
        return text;
    }

    ~Object() {

    }

private:

    int id;
    std::string text;
};

Nothing special, as you can see. The program which creates the objects is as follows:

int main(int argc, char** argv) {

int numberOfElements;
long start;
long end;
long time1, time2, time3, time4, time5, time6;


numberOfElements = 7000;  //7000<X<7050  Maximum reliable

{
    //Measuring time for creation of 1000 elements in vector of objects,
    cout << "VECTOR OF OBJECTS:" << endl;
    start = getTimeInMicroseconds();
    vector<Object> vectorOfObjects;
    vectorOfObjects.reserve(10000);
    for (int i = 0; i < numberOfElements; i++) {

        cout << "Creating object " << i << endl;

        Object object = *(new Object(i, "This is object "+i));
        cout << "Created object " << i << endl;

        vectorOfObjects.push_back(object);             
        cout << "Object inserted" << endl;
    }
    end = getTimeInMicroseconds();
    time1 = end - start;

    cout << "- Time to create " << numberOfElements << " objects = "
            << time1 << " microseconds" << endl;
}

return 0;
}

Again, something very simple. The amount of objects created before crashing depends on what I add after this code. Sometimes it crashes after 2000, sometimes, after 4000, sometimes after 7000... I suppose it is a memory allocation problem, but I don't know how to solve it.

I tried creating the objects as:

Object object(i, "text");
vectorOfObjects.push_back(object);

vectorOfObjects.push_back(Object(i, "text");
vectorOfObjects.push_back(*(new Object(i, "text")));  

but none of them worked. Of course, I would prefer a method which creates dynamically those objects, like the two last examples I show here; I tried also with different containers, such as map or deque, but since the problem happens because of the creation of the object and not because of the container itself, it doesn't matter which container I use.

This is the core dump:

-bash-3.2$ pstack core
core 'core' of 5884:    ./datastructuresperformance
 d147646c strlen   (8046eb8, 8055000, 8046ebc, d17c34ed) + c
 08051fdf main     (1, 8047264, 804726c, 8051ddf) + f7
 08051e27 _start   (1, 80473b8, 0, 80473d4, 80473ef, 8047441) + 67


-bash-3.2$ pmap core
core 'core' of 5884:    ./datastructuresperformance
08044000      16K rwx--    [ stack ]
08050000      20K r-x--  /export/home/dcs/SolStudioProjects/DataStructuresPerformance/dist/Release/OracleSolarisStudio-Solaris-x86/datastructuresperformance
08064000       8K rwx--  /export/home/dcs/SolStudioProjects/DataStructuresPerformance/dist/Release/OracleSolarisStudio-Solaris-x86/datastructuresperformance
08066000     280K rwx--    [ heap ]
D1450000    1088K r-x--  /lib/libc.so.1
D1560000      32K rwx--  /lib/libc.so.1
D1568000       8K rwx--  /lib/libc.so.1
D1570000     292K r-x--  /lib/libm.so.2
D15C8000      16K rwx--  /lib/libm.so.2
D15D0000      48K r-x--  /usr/lib/libCrun.so.1
D15EB000       8K rwx--  /usr/lib/libCrun.so.1
D15ED000      20K rwx--  /usr/lib/libCrun.so.1
D1600000      24K rwx-- 
D1610000    1244K r-x--  /usr/lib/libCstd.so.1
D1750000       4K rwx-- 
D1756000     216K rwx--  /usr/lib/libCstd.so.1
D1790000       4K rwx-- 
D17A0000       4K rw--- 
D17B0000       4K rw--- 
D17BF000     176K r-x--  /lib/ld.so.1
D17F0000       4K rwx-- 
D17FB000       8K rwx--  /lib/ld.so.1
D17FD000       8K rwx--  /lib/ld.so.1
 total      3532K

It should not be a problem related to the amount of memory, since the maximum amount of memory used by this program so far is much lower than the 1GB of this machine.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
user2470437
  • 57
  • 1
  • 6
  • 1
    Why are you using `new` when you aren't using `std::vector`? – crashmstr Nov 01 '13 at 17:10
  • 1
    `Object object = *(new Object(i, "This is object "+i));` You just leaked memory literally in a single line of code. If this is all the code there is, I would hedge a bet on `getTimeInMicroseconds` somehow invoking undefined behavior, and trashing your heap in the process. Run it under Valgrind. – WhozCraig Nov 01 '13 at 17:11
  • @WhozCraig you didn't notice the `+i` thing :) – rabensky Nov 01 '13 at 17:15
  • 1
    @cluracan not until now. I lost it in the fog of that insane memory leak. +1 to your answer, btw. =P – WhozCraig Nov 01 '13 at 17:18
  • Hi there, guys. First of all, thank you for your answers. As I am sure you have noticed, after more than 10 years programming in Java, I still use this java-style which is a bit hard to get rid of. @crashmstr I was just trying different things of doing the same. I use Object* in a different test and I was just keeping the simmetry. – user2470437 Nov 04 '13 at 09:10
  • @WhozCraig The error was indeed in that +i. The "+" operator in Java parses automatically everything you add to a string, and there was my error, I assumed C++ would do the same, when obviously doesn't. I didn't get clear which part of the code got you so pissed off. I assume it was this +i thing, but in case I am wrong, I would be pleased if you coud explain to me what else you would change and why. Of course, let's assume that I am avoiding that "new" thing and I am using directly Object object(i, blahblah) as I show in the alternatives I've tried. Thanks a lot, guys. – user2470437 Nov 04 '13 at 09:17

3 Answers3

12
  1. *new T() has been dubbed the memory leak operator. You don't want to dynamically allocate an Object, instead, you just want to construct one (that will be destructed with the vector)
  2. You can't use "string" + i because it will do pointer arithmetic on the char(&)[7] (that is "string"). Logically, "string" + i says &("string"[i]), which causes undefined behaviour (in your case, crash) when i is > than the length of the string literal.

Don't use new in C++ unless you know what you're doing:

Object object = Object(i, "This is object " + to_string(i));

Better yet, consider using emplace_back if your compiler isn't very old:

    vector<Object> vectorOfObjects;
    vectorOfObjects.reserve(100000000ul);
    for(int i = 0; i < numberOfElements; i++) {
        vectorOfObjects.emplace_back(i, "This is object " + to_string(i));
    }
sehe
  • 374,641
  • 47
  • 450
  • 633
  • +1 , and I wish I could again for the alternate (and correct) way to tack that index on the end of the string. – WhozCraig Nov 01 '13 at 17:20
  • @sehe Thank you for your answer. This was exactly the error, I was thinking about Java, my bad. I kept the "new" operator just because I was trying different things to fix the error and in this way I kept the simmetry with other parts of the code in which I use Object*. Anyway, as I assume, you all think it is much better to use Object object(i, blahblah). Again, my bad because of Java, I first thought in this case I would overwrite the original object and create a copy in every single position of vector. – user2470437 Nov 04 '13 at 09:22
5

(Sorry, was a bit startled by that line :/)

Object object = *(new Object(i, "This is object "+i));

Don't do that. Ever. Try this instead:

Object object(i,"This is object");

There are 2 things wrong with that line:

a) What you actually did was create an object, copy it and then forget about it.

b) (and here's the thing that crashed your code) - give it a string starting at the place i bytes forward than the string "This is Object ". As you can imagine, that memory isn't free for you to read like that.

rabensky
  • 2,864
  • 13
  • 18
  • @cluracan Noted and solved. I have already explained why I used the "new" operator in previous comments. Java inheritance because of too many Java programming years, I think. Thank you. – user2470437 Nov 04 '13 at 09:24
-1

In addition to the answers that have already been given, I would suggest that you keep it simple. In most cases you do not need to reserve the size of your vector, as the allocation strategy will generally give a great performance. Work incrementally, profile your code, and then decide to add further statements.

The reason why I comment on that in particular is because it is a symptom of a greater disease: premature optimization.

I used the following code for testing:

#include<string>
#include<vector>
#include<iostream>

#include<boost/timer/timer.hpp>

class Object {
 public:
  Object() { }
  Object(int id, const std::string& text)
      : m_id(id),
        m_text(text) { }
  int id() const {
    return m_id;
  }
  std::string text() const {
    return m_text;
  }
 private:
  int m_id;
  std::string m_text;
};

int main(int argc, char* argv[]) {
  size_t N = std::stoi(argv[1]); // add argc check
  std::vector<Object> objects;
  //objects.reserve(N);
  {
    boost::timer::auto_cpu_timer t;
    for(size_t k=0; k<N; k++) {
      //objects.emplace_back(Object(k, "random text"));
      objects.push_back(Object(k, "random text"));
    }
    std::cout<<"Objects in container: "<<objects.size()<<std::endl;
  }
}

And for optimized code in my machine (g++ example.cpp -std=c++11 -O2 on OS X 10.7.4 using GCC 4.8.1) I get the following results (Wall time in seconds for N=1,000,000):

           Push Emplace
   Reserve 0.078 0.078
No Reserve 0.087 0.087

You could go ahead and start defining move-constructors, and optimizing all you want but, in general, the simpler code is normally well-performant.

Escualo
  • 40,844
  • 23
  • 87
  • 135
  • -1 counter-productive; he's clearly testing/benchmarking different strategies (`long time1, time2, time3, time4, time5, time6;`) and just happens to have the reserve case first - as you did in your test matrix. – kfsone Nov 01 '13 at 18:45
  • 2
    The question is for assistance debugging a crash in one of several benchmarks he is trying. He only posted one strategy but the evidence is there for 5 more if you look at what he did post. Your response, then, doesn't help with his crash but it does say "you're not testing the most efficient strategy" which you then back up by posting a comparison of two different strategies. I might ask you: Why did you test the reserve case if you already knew it wasn't more efficient? Daft question? It's essentially the same question your answer asks of the OP. – kfsone Nov 01 '13 at 19:17
  • 1
    Which would be excellent advice that I would upvote if you weren't posting it in response to a "my benchmark comparison attempts are crashing" question. – kfsone Nov 01 '13 at 19:43
  • 1
    Thank you for your answers, guys. As @kfsone has pointed, this is just a very simple performance test for a dozen alternatives for a very specific case solving sparse systems whith much more code than this simple toy. I use many more alternatives with different containers, kinds of objects and pointers instead of objects. I am not interested in changing the algorithm because the algorithm is not the point of this post and it is completely irrelevant. – user2470437 Nov 04 '13 at 09:27