Real case example of using the undocumented MFC class CFixedAlloc
Contents
- Introduction
- Presentation of the CGrid control
- Methods used for performance measurements
- CFixedAlloc application
- Results
- Conclusion
- Bibliography
- History
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_CLASS
es.
Rows and cells are all dynamically allocated from the heap. It means that for a 64000×8 grid, the control is going to perform 64000 new
for GRID_ROW
and 64000×8 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 ofsizeof
(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 64000×16 grid once then you will use that memory even if you shrink back the grid to a 32×32 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 ofCFixedAlloc
unnecessary.
Also, my blog is probably the best medium if you would like to provide feedback to this tutorial, you can do so here.
Source: Thanks to Olivier Langlois
http://www.olivierlanglois.net/Applying_CFixedAlloc.html
NOTE I now post my TRADING ALERTS into my personal FACEBOOK ACCOUNT and TWITTER. Don't worry as I don't post stupid cat videos or what I eat!