2011-11-24

Standard allocator interface.

As you know, Open CASCADE has its own memory allocation mechanism, which entry points are Standard::Allocate() and Standard::Free(). They forward (de-)allocation requests to a current memory manager, which can either be a). default system allocator (if environment variable MMGT_OPT=0), b). own Open CASCADE's (MMGT_OPT=1), or c). Intel TBB (MMGT_OPT=2).

Most Open CASCADE classes (e.g. all which are defined in .cdl files) redefine operators new and delete to use Standard::Allocate() and Standard::Free() respectively – look at any .hxx file.

Using common memory allocation mechanism allows to decrease memory footprint and/or increase performance, especially when using TBB allocator (in multi-threaded apps). However, you may easily miss this advantage in your application in the following typical cases:
1. You dynamically allocate objects of your classes and their new/delete operators do not use Standard::Allocate()/::Free().
2. You use standard containers (std::vector, std::map, std::list,...).
#1 is easily addressed by redefining new/delete operators in your base class(es) in a way similar to OCC.
#2 is more tough, as standard collections by default use standard allocator (std::allocator<T>). So all auxiliary elements (e.g. list nodes) are allocated outside of OCC-governed memory allocator.

This was until now. Hereby I would like to share a code that implements the standard allocator interface as defined by the ISO C++ Standard 2003 (and also conforming to a recently published C++11). Download Standard_StdAllocator.hxx.

This is a pure header implementation, so no .cxx files and linking with libraries is needed; just copy to your project and start using. Implementation is derived from Intel tbb::tbb_allocator, which itself just follows the C++ standard requirements.

The file is intentionally made copyright-free and put into a Public domain, the most permissive way. I also hope that the OCC team will be willing to pick up this file and integrate into OCC.

The simplest example can be as follows:

typedef Standard_StdAllocator<void> allocator_type;

std::vector<TopoDS_Shape, allocator_type> aSVec;
TopoDS_Solid aSolid = BRepPrimAPI_MakeBox (10., 20., 30.);
aSVec.push_back (aSolid);

std::list<int_Shape, allocator_type> aList;
aList.push_back (1);


There is also a unit test (download Standard_StdAllocatorTest.cxx). The code is based on Qt test framework and should be self-explaining to port to any other test framework.

Those who are curious enough may offer similar standard allocator implementation for NCollection_BaseAllocator which is used in NCollection containers. It is as straightforward as this one.

2011-06-19

Is my memory leaking? Part3.

(continued)

OK, now when you have some clues of possible root-causes, what's next ?

#1.If you believe you found a memory leak, first of all, do not panic. Be patient to identify if this is a true leak or a false positive. For that:

#2. Use efficient memory checking tools (Intel Parallel Inspector XE, Valgrind, etc).

#3. If you want to exclude custom memory allocator, be sure to set environment variable MMGT_OPT=0 and experiment further.

#4. If the issue has gone and you suspect it to be an allocator problem, then the chance is that it's a false positive, not an allocator issue. Though you may continue investigating, of course ;-).

#5. When designing architecture of your application, make sure you understand how your memory is managed. Do consider consistent use of smart pointers – e.g. boost's or Open CASCADE handles. That will save you multiple hours of tedious debugging.

#6. If you are paranoic about memory consumption then you might want to periodically call Standard::Purge() when using the OCC allocator (MMGT_OPT=1). It is supposed to free unused small memory blocks. You could do this when closing the MDI doc, for instance. (I never used myself though.)

#7. Advanced developers may want to step further and use fine-tune optimization techniques like use of memory pools (or regions). See NCollection_IncAllocator as an example of such. NCollection containers can accept it as an extra argument. TBB is going provide support for thread-safe pools in future versions.

#8. Black belts may also want to experiment with memory tracing routines that have been enabled in OCC allocator. See Standard_MMgrOpt::SetCallBackFunction() which is called after each alloc/free. This can be any user callback function that traces sizes of requested/freed chunks, addresses, etc.

So, here is what I was able to recall on this subject, and hope it will be helpful.
As I said in the beginning, any extensions or other best practices are welcome.

Roman

2011-06-09

Is my memory leaking? Part2.

(continued)

Having discussed possible symptoms, now let's try to understand their possible root-causes.

1. True leaks.
When developing in native (C/C++) code you may just forget to free allocated memory. This can be for example:

a. Something as simple as such:
{
char* p = (char*)malloc (1 * 1024);

//do work...

//free (p); //will never forget to uncomment this later
}

b. Architecture design deficiency. Unclear object ownership, management of their life-span leading to failure in proper destroying objects.
I found this shortage in Salome (the SMESH module in particular). There is proliferation of plain pointers (not smart pointers, like boost::shared_ptr) with complex dependencies between objects. I presume multiple developers maintaining the code just forgot some day which objects should destroy which. Here is the most recent work-around I had to make – to destroy sub-meshes in SMESH_Mesh you have to call SetShapeToMesh() with a null shape. This will destroy all objects stored in internal map which otherwise will be leaked (the SMESH_Mesh::~SMESH_Mesh() destructor does not destroy them):


/*! Frees resources allocated in SMESH_Mesh which otherwise leak
- bug in Salome.

*/
Mesh_MeshImpl::~Mesh_MeshImpl()
{
TopoDS_Shape aNull;
mySMesh->ShapeToMesh (aNull);
}

where mySMesh is defined as follows:

boost::shared_ptr mySMesh;


c. Cycles between smart pointers. If you have two smart pointers referring to each other, they won't get destroyed (as reference counter will never reach zero). I described this issue in the very first post.

True leaks are usually well caught by memory checkers.

2. Memory caching by memory allocators.

Many complex software comes with integrated memory allocators that are able to manage memory more efficiently (at least in terms of speed and/or footprint) than default allocators (part of OS or C run-time library). Open CASCADE comes with its own (activated by environment variable MMGT_OPT=1), with Intel TBB (MMGT_OPT=2), or default system allocator (MMGT_OPT=0).

Though OSes provide better and better allocators, custom ones are likely to stay for foreseeable future due to efficient solving of particular problems (e.g. thread-safety and scalability as TBB). If you are curious, you might want to check some comparisons I conducted with default, OCC and TBB allocators here.

The central idea of allocators is caching and reuse of previously allocated memory chunks for further allocations. Thus, when your application object is destroyed, its memory is effectively retained by the allocator and is not returned to the system. That is why, in particular, you won't see in Task Manager the memory level returning to the previous level even if all your document objects got destroyed after closing the MDI document. Allocators may apply different policies to retain/return these memory blocks. For instance, both OCC and TBB have different approaches for small and large blocks; the latter are returned faster (as the chances of their reuse are smaller), while the former may never be returned until application terminates.


3. Static objects residing in memory.

It is a wide spread practice to create static objects which live throughout the application life-time and get destroyed only upon program termination. Consider this:

myfile.cpp:


static boost::shared theSingleton = new MyClass();

MyClass* MyClass::Instance()
{
return theSingleton.get();
}

theSingleton will be created during loading the library containing it, and will be destroyed when it is unloaded (effectively when the application terminates unless it is explicitly unloaded before that).

There are multiple examples of such constructs in OCC code.

Below is the Inspector screenshot of the (false positive) leak reported on the screenshot in Part1:


Static objects in TKTopAlgo


4. Unused data residing in memory.

Similar to above, there are cases when some data are stored with the help of static objects and used to pass between the algorithm calls. I gave some examples in an earlier post. I believe this is a bad design and should be avoided but it may happen in third-party code. It's not really a leak but essentially wasting memory, which again only gets freed upon program termination.

(to be continued)

2011-06-02

Is my memory leaking? Part1.

There often appear posts on the Open CASCADE forum, either as questions or as blames that there are persistent memory leaks. Truth to be told, this happens on many forums of other software products I visited, so this is not something OCC-specific.
So I'd like to shed some light, which would hopefully help someone to understand the issue in the future. As always, extensions and any comments are welcome.

So how one detects there is a memory leak? I would suggest the following possibilities (presumably in the order of decreasing frequency of use by developers):

Using Visual Studio built-in features
(I never use these though).
Put the following lines in the source file or your executable:

#define _CRTDBG_MAP_ALLOC
#include <stdlib.h>
#include <crtdbg.h>

And into the main() function:
_CrtSetDbgFlag ( _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF );

For more information you can read this MSDN page (select appropriate VS version).

Under the debugger, in the Output window you will see something like:

Detected memory leaks!
Dumping objects ->
{35171} normal block at 0x04CD0068, 260 bytes long.
Data: < > 02 00 00 00 00 00 00 00 CD CD CD CD CD CD CD CD
{349} normal block at 0x0330E350, 100 bytes long.
Data: < > CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD CD
{246} normal block at 0x03309390, 108 bytes long.
Data: < E ( > 01 00 00 00 1A 00 00 00 45 01 00 00 28 0A 00 00


Once you see this you might find yourself yelling – "how may this !@#$% product exist with such fundamental bugs ?!!". But as soon as you see this attributes to your code, you may go "hmm, is this really an error?" As you start digging deeper, things may go different ways. You may discover a bug in your product or a tool limitation (the latter is more likely though).

Windows Task Manager
You notice the level of consumed memory before you start some piece of your code (e.g. opening a document in an MDI application).


MDI app memory level before opening a document

Then do some actions and see the new level. For instance, after closing the document in the MDI app, you would expect the level returns to a previous one. If not (and as a rule, not!) you start asking why.


Same app memory level after closing an opened document

Specialized memory checking tools
This includes Valgrind, Bounds Checker (never used those two), Rational Purify, Intel Parallel Inspector XE, etc. I used Purify in late 1990es and as of 2008 sticked to Intel Inspector. Here is a sample report generated by Inspector:


Intel Parallel Inspector XE reporting memory leaks

You can try an evaluation version on the Intel site here.

Debug print
Adding simple outputs in constructor and destructor is simple yet effective practice to quickly check if your object is destroyed whenever you expect.

Custom memory checkers/profilers
You might want to write some ad-hoc memory profiler – some hooks that trace allocation and deallocation routines (malloc/free, new/delete) – counting allocated and deallocated bytes. I myself created one when tracing memory in CAD Exchanger. If there is sufficient interest, I could publish it. The idea is to detect pieces of code where you expect that all allocated memory during that region will be deallocated upon its end.


(to be continued...)

2011-05-16

6.5.0 Visualization Highlights. Part2

Integrating new gradient support I noticed that the Visual3d_Layer class has been extended to contain multiple items called Visual3d_LayerItem. Exploring this further I realized that this was a way to offer an extensible way to support multiple user-defined objects. One of these is a color scale in a 3D view defined as V3d_ColorScaleLayerItem subclass.

Experimenting with this new concept I went on to redefine the way how the CAD Exchanger logo was implemented. It is a part of the over-layer, one which draws 'on top' of objects in a main scene. See the screenshot below:


Logo displayed in front of a 3D model

So I subclassed Visual3d_LayerItem and redefined its RedrawLayerPrs() method to do the actual work. Check this archive to see the full code.

Here is how you can add this object into the layer:
//create an overlayer
Handle(V3d_View) aView = ...;
Handle(Visual3d_Layer) anOverLayer = new Visual3d_Layer (aView->Viewer()->Viewer(), Aspect_TOL_OVERLAY, Standard_True /*aSizeDependant*/);

...
//create a texture layer item
const Handle(Visual3d_Layer)& aLayer = aView->Viewer()->Viewer()->OverLayer();
Handle(QOOcc_TextureLayerItem) aTexture = new QOOcc_TextureLayerItem (QImage (":/viewlogo.png"), aView, aLayer.operator->());
aTexture->SetPosition (Aspect_TOC_BOTTOM_RIGHT);
anOverLayer->AddLayerItem (aTexture);

This way you may add as many items as you want. The only downside (or assumption if you will), which is likely in place is that all items have to be drawn with the same coordinate system that needs to be supported for the layer (using Visual3d_Layer::SetOrtho()). So RedrawLayerPrs() needs to be written with a single convention in mind, and you seem unable to work with individual settings. It would be great to hear whether this assumption is correct or not – from both the OCC folks and anyone having practical experience.

6.5.0 Visualization Highlights. Part1

Open CASCADE 6.5.0 has introduced a few nice (undocumented, as often) features in its Visualization module and I'm going to highlight a couple of them today. Even if the version 6.5.0 made some mixed impressions (with regressions in BRepMesh being most unpleasant ones), visualization was really nice. So kudos to the involved folks!

When migrating CAD Exchanger to 6.5.0 I wanted to rework the way a gradient background is displayed. The previous way was described in the old blog post. 6.5.0 has added a 'built-in' support for gradients offering various options (horizontal, vertical, diagonal, corners, etc). Below is a sample screenshot made in DRAW:


Built-in gradient background support in 6.5.0 (using diagonal1 option).

You might want to experiment more using the vsetgradientbg command. The new API can be used as simply as follows:

Handle(V3d_View) myView = ...;
if (theEnable) {
myView->SetBgGradientColors (myTopBackgroundColor, myBottomBackgroundColor, Aspect_GFM_HOR, Standard_False);
} else {
myView->SetBgGradientColors (myTopBackgroundColor, myBottomBackgroundColor, Aspect_GFM_NONE, Standard_False);
}

2011-03-22

Thoughts on Git repository and community

Thomas Paviot announced a git repository that is aimed to accumulate community produced patches in an effort to systematize and ease their production and use. That effort addresses very limited community support from the OCC company side which often begs a question of the company's commitment to Open Source model. That often provokes emotional debates on the forum and I suggest that we put this offline (we can continue in a separate topic though).

I view Thomas' effort as much more constructive than typical claims, so it does deserve recognition, at least in my eyes. I appreciate it like other efforts, including a Wiki site initiated by Fabian Hachenberg, multiple fixes from Denis Barbier and Peter Dolby, numerous projects showcasing and leveraging OCC (pythonOCC by Thomas Paviot and Jelle Feringa, Salome ports by Fotios Sioutis, qtocc by Pete Dolby, etc) and even simple bug reports. As the old saying goes, "it's better to light a candle than to curse the darkness". So every constructive input is more valuable than an emotional claim.

This post is to share some initial thoughts. I suggest that we continue discussing details of Thomas' proposal on some other place, not the org forum. This is just to avoid unnecessary potential sensitive issues. The blog format is likely not too convenient either and another forum (e.g. on wiki) could be preferred.

Repository structure
Let's start with something simple and clear. Arthur Magill has suggested a 3 level structure which I would simplified down to 2:
- One master (mainline / trunk) branch per each OCCT version. The branch would start with pristine version of OCCT (say 6.5.0) and incrementally accumulate fixes. There must be reasonably strict gatekeeping process to maintain it stable. Fixes must be code-reviewed and tested to make this branch.
- Set of experimental branches maintained by individuals, projects, etc. These are sandboxes where volunteers can maintain their own version and which can contain fixes they selectively pick up or produce. No gatekeeping, everything is up to an owner.

Master branch commit process
To make the master branch as stable as possible, some efforts must be applied. I would start with 3 most important:
  • Code completeness. For instance, if you modify the header file then modify both original .cdl file (if applies) and .hxx file. If you modify a .vcproj file for Visual Studio 2008 32 bit then modify files for other flavors of Visual Studio (e.g. 2005, 2010, 32 and 64 bits) and automake files.
  • Testing. Apply reasonable effort to test your modifications.
  • Code review. OCCT is complex and caution must be taken to analyze potential implications (side effects, performance, memory footprint, platform specificities, etc). Ultimately, the best would be to have single decision maker(s) to approve or veto the modification. Participation of OCC team lead engineers would be really helpful. Ideas are welcome.
In the end of the day, the patch should make the official version of OCCT. To make it happen, the community should apply its efforts and due diligence. I could help in code reviewing testing as far as CAD Exchanger allows.

Consolidation of community resources
To avoid unnecessary proliferation of resources and thus confusion on what to use when, I suggest we define the tools and start sticking to them. Ideally, I would love to see all this hosted on opencascade.org but given continuous unwillingness to support that let's host them outside. If we find another single platform we might want to settle down there.
  • Git repository – to ease fixes sharing.
  • Forum. To discuss issues which OCC company does not appreciate on its forum (projects announcement, company policies, etc). More feature-rich forum (e.g. typical phpBB) would help overcome limitations of the ancient org forum.
  • Wiki – to document knowledge, maintain useful links, etc.
  • (?) Bug tracking. Org forum should probably suffice unless there are volunteers to track the bugs along the life cycle.
  • What else ?
Sourceforge could be a good single place but my experience with it was far from smooth, so I gradually gave up. On the contrary, http://opencascade.wikidot.com is very nice to work with.

Next steps
Thus, the next steps that I see are:
1. Define a must have list of tools for efficient community functioning. Start with forum, and use this blog until that. Once the forum is defined, continue discussions there.
2. Agree on the git structure and basic policies.
3. Start using it.

I would be thrilled to see OCC folks participating in the discussions and activities as much as they can. I agree with Thomas' statement that we all share same interests.

Thanks !

2011-02-09

3D view navigation: mouse wheel support and more

With recently released CAD Exchanger 2.1 Beta, which adds some GUI improvements, I thought to share some experience about that.
Default OCC viewer suggests some conventions based on using the Ctrl button and mouse buttons:
- Ctrl + MB1 (left button) – zoom;
- Ctrl + MB2 (middle button) – pan;
- Ctrl + MB3 (right button) – rotate.
This seems to be not only inconsistent with typical conventions in CAD systems but also challenging for user experience. For instance, MB3 is normally expected to open a context menu.
So following CAD Exchanger users’ feedback, I had to implement different navigation based on Solidworks and other conventions. They are based on using MB2:
- MB2 – rotate
- Shift + MB2 – zoom
- Ctrl + MB2 – pan.


In addition, support for mouse wheel (to zoom in/out), which is a commonly used convention, has been added. For Qt-based viewer it appeared to be quite easy:

void QOOcc_View3d::wheelEvent (QWheelEvent* theEvent)
{
if (theEvent->orientation() == Qt::Vertical) {
int numDegrees = theEvent->delta() / 8; //number of degrees the wheel rotated by
//let 100 degrees be approximately 2x zoom (see V3d_View::Zoom())
int numSteps = numDegrees;

myView->Zoom (0, 0, numSteps, 0);
theEvent->accept();
}
}

Apparently, QtOCC project by Peter Dolby already implemented that (though I did not notice). But Peter used V3d_View::SetScale(). Either should work anyway.

Perhaps, wheel support could be added into default OCC viewers and default OCC conventions could be revisited to better align with industrial ones.