tags:

views:

120

answers:

3

I have a std::vector of a class called OGLSHAPE.

each shape has a vector of SHAPECONTOUR struct which has a vector of float and a vector of vector of double. it also has a vector of an outline struct which has a vector of float in it.

Initially, my program starts up using 8.7 MB of ram. I noticed that when I started filling these these up, ex adding doubles and floats, the memory got fairly high quickly, then leveled off. When I clear the OGLSHAPE vector, still about 19MB is used. Then if I push about 150 more shapes, then clear those, I'm now using around 19.3MB of ram. I would have thought that logically, if the first time it went from 8.7 to 19, that the next time it would go up to around 30. I'm not sure what it is. I thought it was a memory leak but now I'm not sure. All I do is push numbers into std::vectors, nothing else. So I'd expect to get all my memory back. What could cause this?

Thanks

*edit, okay its memory fragmentation from allocating lots of small things, how can that be solved?

+4  A: 

Calling std::vector<>::clear() does not necessarily free all allocated memory (it depends on the implementation of the std::vector<>). This is often done for the purpose of optimization to avoid unnessecary memory allocations.

In order to really free the memory held by an instance just do:

template <typename T>
inline void really_free_all_memory(std::vector<T>& to_clear)
{
    std::vector<T> v;
    v.swap(to_clear);
}

// ...
std::vector<foo> objs;

// ...
// really free instance 'objs'
really_free_all_memory(objs);

which creates a new (empty) instance and swaps it with your vector instance you would like to clear.

hkaiser
I do as you just mentioned for the OGLSHAPE vector, and I assume all the other vectors call their destructor
Milo
@hkaiser, good tip. I wonder why don't add this as a standard functionality in the STL (something like: std::vector::free_all_memory).
Patrick
@Patrick: I think because it can easily be expressed as a one-liner: `std::vector<T>().swap(v);` Not really worth adding to the standard library when it is such a common idiom at this point.
Evan Teran
@Evan: I think `v.shrink_to_fit()` is so much more understandable than `std::vector<T>().swap(v)`. The latter is an _idiom_, which you have to learn and know in order to understand what that code is doing, and it's not even guaranteed to work. An aptly named function is certainly much better - which is why the next version of the standard will actually have `std::vector<>::shrink_to_fit()`.
sbi
@sbi: I generally agree, but unfortunately a function like `v.shrink_to_fit()` may to a certain degree hide the costs of the operation. Seeing a copy constructor with a swap makes it very obvious what is happening cost wise.
Evan Teran
@Evan: If that was a valid argument, `std::vector<>` shouldn't have member functions like `insert()`, because they are so expensive. But it has such a member functions, and it does so because it is not a valid argument. (In fact, _if that was a valid argument, we shouldn't write any functions except for absolute trivial ones_. And that's certainly the opposite of what's good advice.)
sbi
@sbi: Well, you have taken the argument to the far extreme (obviously to demonstrate a point, which is fine). But there are examples in the standard of it being true. For example, `std::list` does not have an `operator[]` **specifically** because it would appear to be less costly than it is (leading to some particularly bad code). I am not saying that only trivial functions should be implemented. In fact, I said that I generally agree with you! But I do think that sometimes (like in this case) a nice little function that wraps a one liner can hide the associated costs, for better or worse.
Evan Teran
@Evan: The designer of the std lib did not put an `operator[]()` into `std::list<>`, but (now) did put a `shrink_to_fit()` into `std::vector<>`, because there is a line between aptly named algorithms which can be arbitrarily complex, and innocent looking operators, which already have a (non-complex) associated meaning in other contexts. When STL containers where given an `insert()` function, the identifier `insert` didn't have a well established meaning associated with a certain complexity in other context. `operator[]`, OTOH, did have a meaning, associated with O(1).
sbi
BTW, following this reasoning, you will find that `operator+()` for strings is questionable (also because it's not commutative), `std::array::swap()` (with _O(n)_!) is a terrible misnomer and a few other "errors" in the standard library. And, indeed, I would agree with you if you were criticizing those.
sbi
@sbi: yea, i don't really have any problem with `shrink_to_fit`, it is probably a good thing when the standard starts including idoms that have proven to be useful, simple and easily seen to be correct. So I would put it on the "good side of the line" :-). As for your second comment, yea, those are all examples of the types of things I would avoid but have innocent looking names.
Evan Teran
A: 

Use the correct tools to observe your memory usage, e.g. (on Windows) use Process Explorer and observe Private Bytes. Don't look at Virtual Address Space since that shows the highest memory address in use. Fragmentation is the cause of a big difference between both values.

Also realize that there are a lot of layers in between your application and the operating system:

  • the std::vector does not necessarily free all memory immediately (see tip of hkaiser)
  • the C Run Time does not always return all memory to the operating system
  • the Operating System's Heap routines may not be able to free all memory because it can only free full pages (of 4 KB). If 1 byte of a 4KB page is stil used, the page cannot be freed.
Patrick
A: 

There are a few possible things at play here.

First, the way memory works in most common C and C++ runtime libraries is that once it is allocated to the application from the operating system it is rarely ever given back to the OS. When you free it in your program, the new memory manager keeps it around in case you ask for more memory again. If you do, it gives it back for you for re-use.

The other reason is that vectors themselves typically don't reduce their size, even if you clear() them. They keep the "capacity" that they had at their highest so that it is faster to re-fill them. But if the vector is ever destroyed, that memory will then go back to the runtime library to be allocated again.

So, if you are not destroying your vectors, they may be keeping the memory internally for you. If you are using something in the operating system to view memory usage, it is probably not aware of how much "free" memory is waiting around in the runtime libraries to be used, rather than being given back to the operating system.

The reason your memory usage increases slightly (instead of not at all) is probably because of fragmentation. This is a sort of complicated tangent, but suffice it to say that allocating a lot of small objects can make it harder for the runtime library to find a big chunk when it needs it. In that case, it can't reuse some of the memory it has laying around that you already freed, because it is in lots of small pieces. So it has to go to the OS and request a big piece.

SoapBox
How could I solve memory fragmentation?
Milo
Is it the same as a memory leak?
Milo
You generally do not need to worry about memory fragmentation. It is natural and rarely becomes a serious issue. None of the stuff I outlined is a memory leak, just barriers to you detecting whether or not a memory leak exists without doing something more complicated. Use a debugger which tracks memory, and it will tell you if you have a leak.
SoapBox