Efficient memory use, and freeing memory after use

Started by
21 comments, last by Kylotan 17 years, 5 months ago
Ok I've been a programmer for about 7 years now, and I'm embarrased to ask, but even after some programming classes at the university I'm still not totally clear of how to make efficient use of memory, and how (and esp. WHEN) to free memory after it's used. In my programs, I use a lot of OOP (btw I use C++). This means a lot of classes, and also a lot of pointers to classes. Often a class has members that are pointers to other classes, etc. In the middle of all this is me, trying to make sure I use the least amount of memory necessary and make sure I free any memory that shouldn't be used anymore. However, I feel like I'm not doing this like I should. For example, check this class: class CHeightmap { private: protected: CVertex3** vertex; // Two-dimension array of vertices CVertex3** pointer_array; // One-dimensional array of pointers to the vertices above CVertex3* vertex_array; // Dereferenced pointer array from above public: CHeightmap () { } ~CHeightmap () { } void create (); void render (); void destroy (); }; There's a lot more in this class, which I left out for convenience. The most important thing is the 3 arrays of CVertex3 (which is a custom class without any pointers in it). I need all these 3 arrays, please don't ask why :) Suppose I would make a pointer to a heightmap class: CHeightmap* heightmap = NULL; heightmap = new CHeightmap(); I know a pointer is a bit useless here, but I want it to be a pointer so I could create more than one heightmap. After I've stopped using this heightmap, I want to free all the data. As far as I'm concerned, I do this: if (heightmap) { delete heightmap; heightmap = NULL; } But well, there's also pointers in the heightmap class itself. So I'd use the destructor to call the destroy(); function in the heightmap, and clean up the inside of the heightmap when the heightmap itself is deleted. The only problem is, I'm not exactly sure what I should clean up, and how. delete doesn't work on the CVertex3** members. Well basically my question is, what kind of data (normal instances, pointers, double pointers, etc) should I clean up and how? Also, sometimes I want to pass the vertex_array data member of this class to an octree, which distributes these vertices to its nodes, and the visible nodes are drawn instead of the heightmap itself. Making an exact copy of the vertex array would result in storing it twice, and since nodes grow exponentially (and the vertex array data is passed on) memory will be filled with a lot of copies of the same data. So I guessed my octree should store pointers to the vertex array, so I would end up with CVertex3** members again. However, I just can't get this to work well. I think I have a pretty good understanding of pointers, except for the part of what actually happens in memory. I suspect my code has a lot of memory leaks, and inefficient usage of memory, but I don't know a good way to check this. So my second question is, is there some way of checking how much memory is used in certain places in my program? Thanks a lot for the help, I really want to get this right because I've been putting it off for too long now.
Advertisement
also, I'm not sure of if/why/when I should use malloc/memset/free. I see this a lot, but mostly in C code, C++ seems to work with NULL/new/delete all the time. Is there any reason why I should use malloc/memset/free in certain contexts?
Are you sure you need the pointer everywhere?
CHeightmap* heightmap = NULL;heightmap = new CHeightmap();

I see no reason why you should not just use:
CHeightmap heightmap;

This will get destroyed as soon as it gets out of scope.

Quote:
delete doesn't work on the CVertex3** members.

If they are arrays (allocated with new type[size]) then you should delete them with delete[] not with delete.

In C++ the only reason to use malloc/free is when working with a library written in C that allocates or deallocates memory itself (not likely).
Ok thanks a lot, that is already very helpful.

well of course the pointer to a heightmap was just for illustrational purposes, and not necessary here. But like I said, suppose that heightmap pointer was a data member of another class (say a complete level class, which features multiple heightmaps) it would be useful to have a pointer, so I can dynamically choose the amount of heightmaps for that level.

Edit: deleting of CVertex3** already gave me several mb of memory back :) :)

so well, the most important question left is, is there a way to check where memory leaks occur, if all memory is freed after closing the program, and see where (and how much) memory is allocated for use? This would make it possible for me to check where I still don't free memory the right way.
As for storing pointers to your data in octree nodes, I would rather suggest to modify your design. There are my 2 suggestions:

a) centralized storage of the heightmap, use some sort of resource index to identify the batch of the heightmap you want to draw
+ you store only references
+ if you freed your heightmap data you don t run into trouble with illegal pointers ...

I apply the concept of centralized storage primarily to resources like images, sounds, model meshes, model animations

b) local storage of static or maybe even deformable data
class node{public:void add_storage(storage*);private:std::vector< storage* >  m_StorageContainers;}


storage is a virtual base class that serves as a base for customized storage containers.
e.g.: static_mesh_storage, deformable_mesh_storage, light_info_storage.....


Thats the way I did implement the local resource handling.
You don t need to store information that is only used in local environments in a huge global central database, do you? No.


Now as for the time and locality of deallocation, try to deallocate your resource where you allocated them. Why? its easier to implement custom allocators later if you need any and you don t pollute your code with a lot of memory handling and pointer checking.
e.g.:
you have a resource_loader that allocates an object and returns the pointer to the resource manager, implement a function so the resource manager can pass the pointer back to the resource loader to do deallocation and such.
Its also handy to pass a resource_context to the loader so if you load an image for example you can directly send the image data to the rendering context:

makecurrent();
glTexture2D.....();
release context();

Here s a abtract base class I use in my projects.
     17         class resource_loader     18         {     19         public:     20                 //! load a resource from a file     21                 virtual resource* load(const std::string& name, resource_context *pcontext) = 0;     22     23                 //! reload a resource from a file     24                 virtual bool reload(resource *pres, const std::string& strFilename, resource_context *pcontext) = 0;     25     26                 //! unload a resource     27                 virtual void unload(resource *pres, resource_context *pcontext) = 0;     28         };
http://www.8ung.at/basiror/theironcross.html
Quote:Original post by Subotron
Ok thanks a lot, that is already very helpful.

well of course the pointer to a heightmap was just for illustrational purposes, and not necessary here. But like I said, suppose that heightmap pointer was a data member of another class (say a complete level class, which features multiple heightmaps) it would be useful to have a pointer, so I can dynamically choose the amount of heightmaps for that level.

Edit: deleting of CVertex3** already gave me several mb of memory back :) :)

so well, the most important question left is, is there a way to check where memory leaks occur, if all memory is freed after closing the program, and see where (and how much) memory is allocated for use? This would make it possible for me to check where I still don't free memory the right way.



Most operation systems should completely free the process's memory except if you are allocating objects in dlls/shared libraries

http://www.8ung.at/basiror/theironcross.html
@Basiror: Sometimes I use DLLs, but even if I don't I get the feeling that after executing my projects a lot of times, the computer got slower, and rebooting is necessary.

Also, I like the idea of a 'resource manager' and such, I also try to find a way to store all geometry that should be drawn in a single list, and draw it using one single function, and stuff like that. Unfortunately this is too big of a problem for me, since different parts might use different amount of textures, other shaders, or whatever.

I don't really get much of what you're trying to tell me though. Deallocating just after allocation sounds to me like you throw away what you just created. Your code examples dont help me much, since you use std:: a lot, which I don't want to use at all. (don't ask me why, I just don't like using external code that I do not fully understand. Maybe I should start thinking about std, since it seems to be used a lot. However, making a VECTOR for the purpose of a storage class seems really weird to me.)

Also, what I do with the heightmap->octree:

The heightmap has a vertex array, which is created out of the 2D vertex array (the "CVertex3** vertex") using the pointer array. This is useful for map editing, where I want a two-dimensional array of vertices, and each vertex is only represented once (in the vertex array, vertices are used more than once, so it is about 4 times as big since I draw quads). The pointer array is used to efficiently pass changed in the original 2-dimensional array to the vertex array that is drawn to the screen.

However, instead of drawing this vertex array all at once, I want to use octrees to divide it up into parts so draw visible parts only. Just storing indices won't work, since the vertices that belong to a certain octree node (leaf) are not stored right after eachother in the array. So what I do is when I create an octree, is pass the vertex array data. First, I tried to use pointers to the vertex array in the octree, so it would be efficient on memory and by editing the octree data, the heightmap would also be edited. However, this didn't work out, so I just copied the vertex array to the octree. This is probably not a good solution though, since I still probably want to edit the heightmap, and it means the vertices in the octree will remain unedited.

Anyway, after the quadtree is constructed (this quadtree is the first node), I will divide it. So I pass the copied vertices data to the nodes, using an algorithm to find out which vertices belong to which nodes. After this is done, I now use delete[] vertices; vertices = NULL; in the current node, because all the vertices are passed on to new nodes, so they aren't used in the current one anymore.

Once I get to the leafs, I dont delete the data anymore, since the leafs will be drawn. I guess these leaves should store pointers to the vertex data in the original array, but I'd have to use a dereference trick like in the heightmap class to make sure the data is tightly packed, so I can draw it using vertex array routines.

Problems like these always bug me, which is why I made this topic. I hope you can try to explain your method to me, because I think the way I currently reason about sharing geometry (and other things as well) is pretty bad. I'd really like to store all my polygonal data in one place (pool), which can be edited by modules that don't need to store a copy to this data, and let it be drawn by one render function. This is probably too much to ask, but any step in the right direction would be great. (forget about the render function etc. though, this topic is in 'general programming' so I guess I should stick to how to do things memory-wise.)
Quote:Original post by Subotron
Your code examples dont help me much, since you use std:: a lot, which I don't want to use at all. (don't ask me why, I just don't like using external code that I do not fully understand. Maybe I should start thinking about std, since it seems to be used a lot.
There's no maybe about it. You should use the standard library of the language you are programming in. "I just don't like using external code that I do not fully understand" is an excuse, not a reason. I know, because I used to use it. I bet you're quite happy using a compiler you don't understand, not understanding how the code you write maps to machine code, using operating system functions you don't understand, etc. The standard libary is implemented by people smarter than you, tested more thoroughly than anything written by you, well documented, well understood and above all standard. Learn to use it. Then use it.

Really and truely calls to new([]) and especially delete([]) should be rare in a modern C++ program. Instead you should use RAII (Resource Acquisition Is Initialisation) wrappers such as the standard library container classes and smart pointers. The Boost libraries are worth looking into for their smart pointers (plus much more). Several libraries from Boost will be incorporated into the next C++ standard.

Σnigma
Enigma, you are right of course, I do not expect that I can code it better myself. However, using it because other people who are smarter than me made it seems to be a just reason not to do ANYTHING myself anymore, but just use someone else's code. I think doing as much as I can by myself is a better learning experience.

However, if you say std is something that is a standard, and for example commercial engines use it, I guess I should use it too. I know how to make linked lists myself, but I rarely ever do since it takes a lot of code. From what I understand, std::<vector> (or whatever) is actually a linked list, so it saves on code. I guess I will start looking into it.

So well, based on my last posts, about a way to keep all the data inside one resource manager, I went to search for some articles, and esp. this one seemed good:

http://www.gamedev.net/reference/articles/article1812.asp

However, it uses STL. STL seems pretty much the same as STD to me, but it isn't, right? Also, a lot of people say the implementation discussed in the article is quite bad. However, I think it points in the right direction of how to solve my goal. So I guess I'll start looking into STD and STL, and try to find what I need there.

Oh and well, I don't want to say you're wrong, but when you say using new and delete should be rare, I find it hard to believe you :) I see the usage of those functions a lot (I know, there's a difference between 'should be rare' and 'are rare'). Are you trying to tell me STD (and maybe boost/STL?) are replacements of these functions?

Anyway, I'm definetly going to look in to this, since it might offer some simple solutions to all my problems. Thanks for the help.
Quote:Original post by Subotron
Enigma, you are right of course, I do not expect that I can code it better myself. However, using it because other people who are smarter than me made it seems to be a just reason not to do ANYTHING myself anymore, but just use someone else's code. I think doing as much as I can by myself is a better learning experience.

There's a big difference between learning and using. Writing a linked-list implementation in order to learn is perfectly fine. Using a self-written linked list in a sample program to test that it works is perfectly fine. Using a self-written linked list when the object is solely to use a linked list is usually folly. Learning is best attempted in small chunks. You don't write a MMORPG in order to learn how to implement a linked list. Equally when you know how to write a linked list and want to use one in a complex program you want to know that the linked list is not going to cause you any problems (obscure unknown bugs, poor performance etc).

As for being a reason not to do anything yourself, if there already existed code to do what you want to do exactly as you want to do it you wouldn't be writing something yourself. The need to do something yourself is usually driven by your desire to do something new or do something better.
Quote:However, if you say std is something that is a standard, and for example commercial engines use it, I guess I should use it too. I know how to make linked lists myself, but I rarely ever do since it takes a lot of code. From what I understand, std::<vector> (or whatever) is actually a linked list, so it saves on code. I guess I will start looking into it.

I don't know what the uptake is with regards to standard library usage in game development. Professional game development is typically slow to adopt new technology. That said we've had the C++ Standard Library for eight years, so it's not exactly new anymore. I believe it's beginning to gain widespread adoption within the industry. Certainly the company I work for uses it.

As an aside std::list is a linked list, std::vector is a dynamic array.

Quote:So well, based on my last posts, about a way to keep all the data inside one resource manager, I went to search for some articles, and esp. this one seemed good:

http://www.gamedev.net/reference/articles/article1812.asp

However, it uses STL. STL seems pretty much the same as STD to me, but it isn't, right? Also, a lot of people say the implementation discussed in the article is quite bad. However, I think it points in the right direction of how to solve my goal. So I guess I'll start looking into STD and STL, and try to find what I need there.

The STL (Standard Template Library) refers to the containers and algorithms subset of the C++ Standard Library.

As to the article, I only skim-read it, but the fact that is defines its own RTTI (Run Time Type Information) and uses a lot of raw pointers (sometimes redundantly) is a bad sign.

Quote:Oh and well, I don't want to say you're wrong, but when you say using new and delete should be rare, I find it hard to believe you :) I see the usage of those functions a lot (I know, there's a difference between 'should be rare' and 'are rare'). Are you trying to tell me STD (and maybe boost/STL?) are replacements of these functions?

std::vector can (and should) replace most explicit calls to new[] and delete[]. Smart pointers eliminate the need for most dynamic object cloning and explicit calls to delete. And finally a lot of people use new and delete when they really don't need to. A good rule of thumb is to avoid pointers unless you can't do without them.

Σnigma

This topic is closed to new replies.

Advertisement