Home
Fractals
Tutorials
Books
My blog
My LinkedIn Profile

BOOKS i'm reading

Napoleon Hill Keys to Success: The 17 Principles of Personal Achievement, Napoleon Hill, ISBN: 978-0452272811
The 4-Hour Workweek: Escape 9-5, Live Anywhere, and Join the New Rich (Expanded and Updated), Timothy Ferriss, ISBN: 978-0307465351
The Fountainhead, Ayn Rand, ISBN: 0452273331

Real case example of using the undocumented MFC class CFixedAlloc

Contents





Introduction

This tutorial is a follow up of my tutorial 'Enhance your dynamic memory allocation in C++ with an undocumented MFC class (CFixedAlloc)'. I am proposing to apply CFixedAlloc class to the grid control from Chris Maunder. The changes have been made on version 2.26 of the control so you should check if a new version has been released hopefully with the optimization discussed in this tutorial or at least a variant of it.

This tutorial will first present a brief presentation of the aspects of Chris Maunder grid control classes that are important for this tutorial. Then I will present the methods that I used to measure the improvement resulting of applying CFixedAlloc. This will then be followed by a presentation of the code change required to apply CFixedAlloc and finally, I will present the optimization result from these changes.

Presentation of the CGrid control

This is a prime candidate for applying a fixed allocator optimization. Please consult my previous tutorial on the topic for a detailed coverage of the positive properties of such an allocator or the grid control page for an extended coverage on its architecture and its usage. What is important to know for this tutorial is that the grid control consist of an array of GRID_ROW. GRID_ROW is also an array that contains cell objects. The cell class can be specialized with derived classes (useful cell specializations are provided with the control) and you can indicate to the control which type of cells that you want it to populate the grid by providing it a meta-class of the desired cell class with the help of the MFC RUNTIME_CLASSes.

Rows and cells are all dynamically allocated from the heap. It means that for a 64000x8 grid, the control is going to perform 64000 new for GRID_ROW and 64000x8 new for creating the cells.

Methods used for performance measurements

For measuring the time it takes to resize the grid, I have modified the dialog box event handling functions that control the size of the grid in the file GridCtrlDemoDlg.cpp:

void CGridCtrlDemoDlg::OnUpdateEditCols()
{
...
DWORD start = GetTickCount();
TRY {
    m_Grid.SetColumnCount(m_nCols);
}
CATCH (CMemoryException, e)
{
    e->ReportError();
    return;
}
END_CATCH
DWORD end = GetTickCount();
Trace(_T("Time spent in SetColumnCount(): %d\n"),end-start);
...
}
void CGridCtrlDemoDlg::OnUpdateEditRows()
{
...
DWORD start = GetTickCount();
TRY {
    m_Grid.SetRowCount(m_nRows);
}
CATCH (CMemoryException, e)
{
    e->ReportError();
    return;
}
END_CATCH
DWORD end = GetTickCount();
Trace(_T("Time spent in SetRowCount(): %d\n"),end-start);
...
}

For measuring the control memory usage I have simply used the task manager 'Processes' tab. Spot the 'GridCtrlDemo.exe' entry in the process list prior resizing the grid and then take note of the new memory usage value after the resizing.

CFixedAlloc application

Before applying the changes presented in this section, I suggest that you take measurements before so that you can compare the difference.

CGridCell class and its derived children classes and the GRID_ROW class are prime candidates CFixedAlloc optimization because of their frequent and numerous allocation/deallocation. Here are the neccessary changes for CGridCell:

GridCell.h:

#include "GridCellBase.h"
#include "fixalloc.h"
#define POOL_SIZE 1024

class CGridCell : public CGridCellBase
{
    friend class CGridCtrl;
    DECLARE_DYNCREATE(CGridCell)
    DECLARE_FIXED_ALLOC_NOSYNC(CGridCell)
    ...
};

class CGridDefaultCell : public CGridCell
{
    DECLARE_DYNCREATE(CGridDefaultCell)
    DECLARE_FIXED_ALLOC_NOSYNC(CGridDefaultCell)
    ...
};
GridCell.cpp:

IMPLEMENT_FIXED_ALLOC_NOSYNC(CGridCell,POOL_SIZE)
IMPLEMENT_FIXED_ALLOC_NOSYNC(CGridDefaultCell,POOL_SIZE)

First, note that you will have to update the include directories search path as described in my previous tutorial. Also, it is very important that you use the macros DECLARE_FIXED_ALLOC_NOSYNC() and IMPLEMENT_FIXED_ALLOC_NOSYNC() for all the classes deriving from CGridCell. Those are CGridCellCheck, CGridCellCombo, CGridCellDateTime, CGridCellNumeric and CGridURLCell. The reason for this is that the macro allocates POOL_SIZE blocks of sizeof(classname used in the macros). If you do not overload the new and delete operators for a derived class, it will use the operators of the base class and it will corrupt the memory because the derived classes size is usually bigger than their base class.

For using the CFixedAlloc in GRID_ROW, you must modify the GridCtrl module:

GridCtrl.h:

class CFixedAllocGridRow : public CTypedPtrArray<CObArray, CGridCellBase*>
{
    DECLARE_FIXED_ALLOC_NOSYNC(CFixedAllocGridRow);
};

typedef CFixedAllocGridRow GRID_ROW;
GridCtrl.cpp:

IMPLEMENT_FIXED_ALLOC_NOSYNC(GRID_ROW,POOL_SIZE)

I have chosen the value 1024 for POOL_SIZE but feel free to experiment with that value.

Results

Here is a table comparing the results obtained with and without the CFixedAlloc modification for the SetRowCount() and SetColumnCount() operations. As you can see, the difference is very convincing.

  SetRowCount(64000) SetColumnCount(6400)
  t size t size
Without changes 359 35,428 KB 860 75,516 KB
With changes 157 30,168 KB 344 63,480 KB

Conclusion

Everything looks too good to be true. There is one disadvantage of incorporating the proposed optimization. While it is true that the grid resizing will be blazingly fast and use memory more efficiently, once the fixed allocator has allocated a pool of memory, it will never be released until the end of the program execution. If you are allocating memory for a 64000x16 grid once then you will use that memory even if you shrink back the grid to a 32x32 grid for the rest of the execution of the program. For some programs, it might be unacceptable. That being said, it is certainly possible to come up with a home-brew fixed size allocator with a little bit more of intelligence. Not too much to lose the benefits of CFixedAlloc but just enough to address the memory usage shrinking. The Loki framework from Andrei Alexandrescu and described in his book Modern C++ Design contains such allocator.

Before finishing, there are few points that are discussed in my previous tutorial that are worth mentionning again:

  • In the DEBUG build, the CFixedAlloc is not used by the macros (Probably to ease heap corruption tracking).
  • I have used VC++2003.NET for my testing. CFixedAlloc has been deprecated in VC++2005. Of course, since the class was not documented in the first place, there is nowhere in the product documentation something explaining why. Someone has reported to me that he did not witnesses a significant improvement from the optimization when compiled with VC++2005. One can suppose then that the reason for the deprecation is that Microsoft has significantly improved their Heap management functions (malloc,free) in MSCRT that made the usage of CFixedAlloc unnecessary.

Also, my blog is probably the best medium if you would like to provide feedback to this tutorial, you can do so here.

Bibliography

History

  • 09-29-2007:
    • Original article.


Back to the tutorials list

Home :: Fractals :: Tutorials :: Books :: My blog :: My LinkedIn Profile :: Contact