Improving MFC memory performance
If you are using MFC arrays it is possible in quite a few cases to improve the speed and memory performance of these arrays. This applies to the standard CStringArray, CUIntArray and similar classes and also to template classes based upon the CArray template.
If you are not using MFC but using another framework or set of classes that provide similar functionality you can often find similar functions in those classes that will allow you to get a similar benefit to what I will describe in this article.
The Problem
The problem is the typical use of populating the array is by calling the Add() method to add something to the array. No actual problem with that, its simple and straightforward enough. However, under the hood, each time you call Add() the array class has to reallocate more memory of the data stored in the class.
This reallocation has a cost. The cost is increased CPU usage as suitable memory space is searched for by the memory allocator and memory is copied and reassigned inside the class. For small arrays this is not really a problem.
However for larger arrays this becomes quite a noticeable issue. In addition you also run into potential memory fragmentation issues, where memory “holes” of an unusuable size are left in the memory managed by the memory allocator. Should enough of these holes occur you can suffer out of memory conditions even when Task Manager tells you you have available memory. Frustrating! I’ll cover Memory Fragmentation in a different article.
Here is a (simplified) example of the type of problem:
// read data from the serialization archive and store in an array DWORD i, n; ar >> n; for(i = 0; i < n; i++) { someClass *sc; sc = new someClass(); if (sc != NULL) { sc->load(ar); array.Add(sc); } }
The Solution
In the case shown above we know how many objects we require storage for beforehand. This means we can tell the array how many objects to store and only perform one memory allocation to set aside storage for the array. This has CPU benefits and also because there are no repeated calls to reallocate the memory the likelihood of fragmentation occurring diminishes dramatically. In many cases, completely removed from the scenario.
To set the size beforehand we need to call SetSize(size); and to place data in the array we no longer use Add();, but use SetAt(index, data); instead.
Here is the reworked example:
// read data from the serialization archive and store in an array DWORD i, n; ar >> n; array.SetSize(n); for(i = 0; i < n; i++) { someClass *sc; sc = new someClass(); if (sc != NULL) { sc->load(ar); array.SetAt(i, sc); } }
For large volumes of data the above implementation can be noticeably faster.
Caveats
When you preallocate memory like this you must be aware that if you don’t fill all locations in the array using SetAt() you may get errors when you call GetSize() to get the array size and GetAt(i) to retrieve data.
// read data from the serialization archive and store in an array // we won't store all data, leaving some unused memory at the end of // the array DWORD i, n, c; ar >> n; c = 0; array.SetSize(n); for(i = 0; i < n; i++) { someClass *sc; sc = new someClass(); if (sc != NULL) { sc->load(ar); if (sc->IsEmpty()) { // discard delete sc; } else { array.SetAt(c, sc); c++; } } }
GetSize() will return the size of the array you set when you called SetSize(), this is not necessarily the number of items in the array – this will come as a surprise to people used to adding data by calling Add().
To fix this, use FreeExtra() to remove any unused items from the end of the array. You can also use GetUpperBound() to find the largest index that is used by the array. The example below shows this.
// read data from the serialization archive and store in an array // we won't store all data, leaving some unused memory at the end of // the array DWORD i, n, c; ar >> n; c = 0; array.SetSize(n); for(i = 0; i < n; i++) { someClass *sc; sc = new someClass(); if (sc != NULL) { sc->load(ar); if (sc->IsEmpty()) { // discard delete sc; } else { array.SetAt(c, sc); c++; } } } // make sure array.GetSize() returns the max number of items used array.FreeExtra();