 |
|
 |
An object pool is a pretty common thing, in STL its known as an allocator.
But looking at your code im not convinced your original problem was caused by memory fragmentation.
You appear to increase the size of your object pool every time you run out of objects in getNewObj() with a call to increaseObjMem().
I can see two major flaws with the increaseObjMem() function. The first is that every time you allocate a new larger array you delete the old one, and therfore all of the objects that you previously allocated !
if (arrOldObjs != NULL)
delete[] arrOldObjs;
Allowing size in the new array for the old objects to be returned is not a solution to this problem, since youve still deleted them. And you can't fix this by copying the old objects either, since they are outside your control and will always reference the memory that used to be part of arrOldObjs[]. What you need is a list of arrays, or buckets and you have to track them all. Only when a bucket is completely empty is it safe to delete.
The second problem is that you claim that this is a solution to some memory fragmentation situation but in increaseObjMem() you are calling operator new for every object in your s_arrAllObjs[] pool. Aside from being completely pointless, since all your doing is then asigning its value to a location in the array, your also just slowing your program down and causing lots of memory allocations (which is what you wanted to avoid in the first place).
If what you want to do is initialise the array with your objects, then considder using placement new. You can call placement new as required in getNewObj() saving you some performance and a lot of memory allocations, which is after all the entire point of having a pool.
But basically, your code at the momement does not work, and causes far greater problems than the imaginary one you were trying to fix.
|
|
|
|
 |
|
 |
Agree with you for two points, I have some solutions for those:
- Every time allocate a new larger array, the code delete the old one.
--> try to use an vector to save the pointer of each time allocate.
- Big error once delete an item, since the outsites' reference cannot control the change.
--> just put as NULL for those deleted item (to re-use later).
With these changes, I think the code is ok to use!
Vu Thuy.
|
|
|
|
 |
|
 |
Since your library never frees memory (just increase but never decrease) then using MFC we have the same functionality with just few line of code. See it: http://www.codeproject.com/cpp/CFixAlloc.asp
Anyway, thanks for your contribution to community.
|
|
|
|
 |
|
 |
I just looked at your template and haven't had a chance to try it out yet so I may be off base here.
Inside getNewObj(), if you run out of objects in the memory pool, you call increaseObjMem(). This routine in turn allocates a brand new array and then deletes the old one. Where do you preserve the addresses of the objects that have already been created? It seems you just added just the new ones to the array.
|
|
|
|
 |
|
 |
I've been asked this question a couple of times from my friends. When you call getNewObj() to get an object from the pool you own its memory, when you call deleteObj() the pool receives back the memory and own it.
After calling increaseObjMem(), new allocated objects will be placed at the bottom of the pool. If you called getNewObject() you have to call deleteObject() after finishing using an object (like the way we call new and delete operators). The object which is returned to the pool is placed on the top.
If you look at the method increaseObjMem(), the below three code lines have already preserved memory to receive back the objects from outsite:
//Resize the pool
s_iNumOfObj += iGrowNum;
s_arrAllObjs = new long[s_iNumOfObj];
s_iLastObjIdx = iGrowNum-1;
|
|
|
|
 |
|
 |
Your are allocating Block 1, 2, 3, 4, 5...
And you are freeing them in the same sequence: 1, 2, 3, 4, 5...
What about the fragmantation if you free the memory in the order you allocated it? So starting to free the memory with the last you allocated.
|
|
|
|
 |
|
 |
by the way,what the Performance monitor do you use? Woule you like give me the name of this software,and where download it?
|
|
|
|
 |
|
|
 |
|
 |
http://www.boost.org/libs/pool/doc/
It's a very mature cross-platform header-only C++ library which does the same thing and more.
|
|
|
|
 |
|
 |
CPtrArray::RemoveAt returns void, I dont understand how this code compiles it should be coded like this CPtrArray g_oBuffer; //... //Any time new data arrived, I allocated //a new memory and added it to the buffer // return the index of the pointer int AllocateObject() { MyData* oData = new MyData(); g_oBuffer.Add(oData); return g_oBuffer.GetSize()-1; } //... //When old data wasn't needed anymore, I removed it void DeleteObject(int index) { MyData* oData = g_oBuffer.GetAt(index); g_oBuffer.RemoveAt(index); delete oData; } //... crash1013
|
|
|
|
 |
|
 |
Yes, CPtrArray::RemoveAt returns void. But this is only my mistake when creating a pseudo code for describing the problem.
I'm going to update this article soon.
|
|
|
|
 |
|
 |
Interesting article, I wasnt aware that this could be a problem.
Can you tell ma what program you used for displaying the memory usage in your screenshots?
|
|
|
|
 |
|
 |
I used Performance tool. You can find this tool from Administrative Tools group on Windows.
Be noted that the problem doesn't always occur. Two reasons that could cause the problem:
1. The frequency of allocating data is too high
2. The size of the object allocated. (Please refer to http://www.devx.com/tips/Tip/14060[^]
|
|
|
|
 |
|
 |
Hi,
I have found the tool, but how do I profile a particular exe?
Regards,
Mel
|
|
|
|
 |
|
 |
I meet the same problem too, the code look alright, but always running out of virtual memory. I will try your code and thanks for your contribution here.
|
|
|
|
 |
|
 |
Microsoft has already addressed this problem in both Windows XP and Windows 2000 using a low fragmentation heap (LFH), which you can use in your application by invoking the usual: HeapCreate(...) WIN32 function(s).
Use SetHeapInformation(...) to set the heap as a LFH heap.
"The LFH avoids fragmentation by managing all allocated blocks in 128 predetermined different block-size ranges. Each of the 128 size ranges is called a bucket. When an application needs to allocate memory from the heap, the LFH chooses the bucket that can allocate the smallest block large enough to contain the requested size. The smallest block that can be allocated is 8 bytes".
Your solution, though a good start, is not all that comprehensive.
Review: doug lea's allocator and perhaps the internal workings of smartheap(commercial product) which provide much more potent solutions.
Also, consider that there are at least two forms of damaging fragmentation:
physical fragmentation, and logical fragmentation. You want a solution that mitigates both.
Cheers.
-yafan.
|
|
|
|
 |
|
 |
I have been working on a memory 'display' tool that shows how the memory is being used by a process. The information is collected by processing the data returned by VirtualQueryEx. What I observe is MANY blocks in lower memory for a typicall process that can NEVER be allocated, because they are less than 64K in size. The the VirtualAlloc can allocate only on 64K boundaries. So it is also possible that your program might have a lot of memory 'free' but also inaccessible.
|
|
|
|
 |
|
 |
I guess you ment HeapSetInformation. GetProcessHeap can be used as well to obtain a handle to the heap of the current process.
|
|
|
|
 |
|
 |
The HeapSetInformation seems to not be available for 2000 (BUMMER! Only for Longhorn, 2003, XP at this point according to msdn.microsoft.com), which is where I am investigating a problem which may be related (fragmentation issues surrounding high data rates) or similar. There's no leak in my case, but the HeapAlloc/HeapCoelese calls die after many hours of runtime in 2000 (same code did NOT fail EVER in NT 4.0 SP5, which is interesting). Thanks for the info - I'll try profiling it more for VM usage... and maybe pool objects where I can for "free" (low risk).
|
|
|
|
 |
|
 |
I wonder if this really works when you have a pool of CString objects. As far as I know a CString allocates it's own memory from the heap, so this memory is not in the pool and still risks getting fragmented. All you have in the pool is the fixed size object a CString represents, which is not that likely to become fragmented. Besides, a CString already has inbuild protection for memory fragmentation by allocating in chunks of 64 bytes (or kilobytes?).
|
|
|
|
 |
|
|
 |
|
 |
You're right -- MFC does not recognize the importance of Allocators, unlike STL. Every STL constructor takes an allocator template of some kind, so that you cause any object you like to use a specific function to alloc raw memory.
Vectors, Strings, Maps, Hash-Tables, Sets, etc. all need allocators support to allow you to do things like this. Every object that embeds objects that accept allocators should also accept allocators in its constructor, and pass them down to the member objects. You must also support allocators in the constructors of derived objects, if the parent class can take an allocator.
You can really do some nice things once you have allocator support everywhere - for example, allocators that used memory-mapped files as their store, allowing fast & easy objects that exist in memory and on disk at the same time. (it's crazy, but having a database around isn't always garanteed)
|
|
|
|
 |
|
 |
You are right. In the example, actually I just used CString by random to show step by step of how to use the object pool.
The object pool CObjectPoolImpl only aims to manage the memories that contain/hold T instances. It will not manage the memories allocated dynamically by T's methods/operators at rumtime.
|
|
|
|
 |
|
 |
I like your templatized implementation of a memory pool manager. Good job!
However, I suggest you check for possible NULL values of the allocated pointers returned by the new operator. As you may know, it is not guaranted that the operator always returns a valid pointer, especially in this specific context where you allocate a very large amount of memory.
|
|
|
|
 |
|
 |
operator new MUST throw an exception if failed to allocate memory (of course, if you have not turned exception handling off), so it never returns null.
gnk
|
|
|
|
 |