Difference between revisions of "Slicer3:VTK Leak Debugging"

From Slicer Wiki
Jump to: navigation, search
(Added further tips and tricks for memory leak prevention and analysis)
Line 1: Line 1:
 
==In your code==
 
==In your code==
  
 +
===Finding memory leaks===
 +
 +
====Enabling debug message output for leaked classes====
 
If you are getting leaks for a vtkObject but can't figure out where it's coming from, you can try making a change to the constructor for that class and checking the debug output (warning, it can be verbose).  What you will see from the following type of change is instances that are created but never destroyed.  Then you can add other debug statements in your code and narrow down which instances aren't being deleted.
 
If you are getting leaks for a vtkObject but can't figure out where it's coming from, you can try making a change to the constructor for that class and checking the debug output (warning, it can be verbose).  What you will see from the following type of change is instances that are created but never destroyed.  Then you can add other debug statements in your code and narrow down which instances aren't being deleted.
  
Line 35: Line 38:
 
}
 
}
 
</pre>
 
</pre>
 +
 +
====Saving pointers to leaked objects====
 +
 +
With a small VTK patch you can get the pointers to the actual leaked object instances. Then, using the pointers, you can print the contents, analyze the object with the debugger, etc.  The idea is to simply add the object pointer to a std::set when in the vtkObject constructor, remove the pointer from the std::set in the destructor, and print the pointers in vtkDebugLeaks. The implementation requires minor modification of 3 files, for VTK-5.8.0 a patch/modified files are available at the NA-MIC sandbox: http://svn.na-mic.org/NAMICSandBox/trunk/SlicerDebug/DebugLeakAnalysis/VTK-5.8.0/
 +
 +
====Print additional object info upon object creation using breakpoints (Windows-only)====
 +
One approach that has worked for me is to modify the behavior of visual studio breakpoints - right clicking on a breakpoint and picking the 'When Hit..." option allow you to do things like print an expression or stack trace and continue running.  This does slow down execution but can provide useful log data without modifying the code.
 +
 +
===Preventing memory leaks===
 +
 +
There are a couple of useful tools:
 +
  vtkNew - See http://www.vtk.org/doc/nightly/html/classvtkNew.html#details
 +
  vtkSmartPointer - See http://www.vtk.org/doc/nightly/html/classvtkSmartPointer.html#details and http://www.vtk.org/Wiki/VTK/Tutorials/SmartPointers
 +
  Their cousin the "vtkWeakPointer" could also be useful. See http://www.vtk.org/doc/nightly/html/classvtkWeakPointer.html#details
 +
 +
Whenever possible, make use them. There are plenty of resources on-line talking about the reason motivating the use of SmartPointer. For example: http://ootips.org/yonat/4dev/smart-pointers.html#Why
 +
 +
For example, you could use them to hold the pointer returned by vtkMRMLScene::GetNodesByClass. Indeed, this function returns a NEW reference and it's the caller role to manage the lifecycle of the object. See https://github.com/Slicer/Slicer/blob/master/Libs/MRML/vtkMRMLScene.cxx#L1636
 +
 +
It is preferred to pass already created objects to methods for update rather than creating the object instance in a method and returning a pointer to that (this way it's easier to keep the responsibility of creating and destructing the object at the same place). For example: https://github.com/Slicer/Slicer/blob/master/Libs/MRML/vtkMRMLScene.cxx#L1608  (May be a method taking a vtkCollection as parameter could also be added in addition/instead of the one dealing std::vector. In this case, python wrapping, efficiency .. are also things to consider)
 +
 +
You could also use tools/widgets like "DebugLeaksView" [2]. See http://www.vtk.org/Wiki/DebugLeaksView
 +
 +
Tracking the leaks when they appear is the <b>highly</b> recommended approach. It's when you are implementing a feature that you have the best understanding of the code, things are "wired" in your brain. The more you wait, the more painful it could be ...
 +
  
 
== In VTK ==
 
== In VTK ==

Revision as of 21:30, 14 November 2011

Home < Slicer3:VTK Leak Debugging

In your code

Finding memory leaks

Enabling debug message output for leaked classes

If you are getting leaks for a vtkObject but can't figure out where it's coming from, you can try making a change to the constructor for that class and checking the debug output (warning, it can be verbose). What you will see from the following type of change is instances that are created but never destroyed. Then you can add other debug statements in your code and narrow down which instances aren't being deleted.

Add this at the beginning of the constructor:

this->DebugOn();
vtkDebugMacro("constructing");

and this in the destructor:

vtkDebugMacro("destructing");

For example, these changes helped me find an extra instance of vtkPoints

// Construct object with an initial data array of type float.
vtkPoints::vtkPoints(int dataType)
{
this->DebugOn();
vtkDebugMacro("constructing");
  this->Data = vtkFloatArray::New();
  this->Data->Register(this);
  this->Data->Delete();
  this->SetDataType(dataType);

  this->Data->SetNumberOfComponents(3);

  this->Bounds[0] = this->Bounds[2] = this->Bounds[4] = 0.0;
  this->Bounds[1] = this->Bounds[3] = this->Bounds[5] = 1.0;
}

vtkPoints::~vtkPoints()
{
vtkDebugMacro("destructing");
  this->Data->UnRegister(this);
}

Saving pointers to leaked objects

With a small VTK patch you can get the pointers to the actual leaked object instances. Then, using the pointers, you can print the contents, analyze the object with the debugger, etc. The idea is to simply add the object pointer to a std::set when in the vtkObject constructor, remove the pointer from the std::set in the destructor, and print the pointers in vtkDebugLeaks. The implementation requires minor modification of 3 files, for VTK-5.8.0 a patch/modified files are available at the NA-MIC sandbox: http://svn.na-mic.org/NAMICSandBox/trunk/SlicerDebug/DebugLeakAnalysis/VTK-5.8.0/

Print additional object info upon object creation using breakpoints (Windows-only)

One approach that has worked for me is to modify the behavior of visual studio breakpoints - right clicking on a breakpoint and picking the 'When Hit..." option allow you to do things like print an expression or stack trace and continue running. This does slow down execution but can provide useful log data without modifying the code.

Preventing memory leaks

There are a couple of useful tools:

 vtkNew - See http://www.vtk.org/doc/nightly/html/classvtkNew.html#details
 vtkSmartPointer - See http://www.vtk.org/doc/nightly/html/classvtkSmartPointer.html#details and http://www.vtk.org/Wiki/VTK/Tutorials/SmartPointers
 Their cousin the "vtkWeakPointer" could also be useful. See http://www.vtk.org/doc/nightly/html/classvtkWeakPointer.html#details

Whenever possible, make use them. There are plenty of resources on-line talking about the reason motivating the use of SmartPointer. For example: http://ootips.org/yonat/4dev/smart-pointers.html#Why

For example, you could use them to hold the pointer returned by vtkMRMLScene::GetNodesByClass. Indeed, this function returns a NEW reference and it's the caller role to manage the lifecycle of the object. See https://github.com/Slicer/Slicer/blob/master/Libs/MRML/vtkMRMLScene.cxx#L1636

It is preferred to pass already created objects to methods for update rather than creating the object instance in a method and returning a pointer to that (this way it's easier to keep the responsibility of creating and destructing the object at the same place). For example: https://github.com/Slicer/Slicer/blob/master/Libs/MRML/vtkMRMLScene.cxx#L1608 (May be a method taking a vtkCollection as parameter could also be added in addition/instead of the one dealing std::vector. In this case, python wrapping, efficiency .. are also things to consider)

You could also use tools/widgets like "DebugLeaksView" [2]. See http://www.vtk.org/Wiki/DebugLeaksView

Tracking the leaks when they appear is the highly recommended approach. It's when you are implementing a feature that you have the best understanding of the code, things are "wired" in your brain. The more you wait, the more painful it could be ...


In VTK

In VTK/Common/vtkObjectBase.cxx, modify the RegisterInternal and UnRegisterInternal methods as follows. The prints to cerr will provide a running list of all memory allocation activity. To make this even more useful, you may want to change places in your code (and VTK code) from o->Delete() to o->UnRegister(this).

//----------------------------------------------------------------------------
void vtkObjectBase::RegisterInternal(vtkObjectBase* o, int check)
{
  std::cerr << this << " " << this->GetClassName() << " " << this->ReferenceCount << " Register by ";
  if ( o )
    {
    std::cerr << o->GetClassName();
    }
  else
    {
    std::cerr << "NULL";
    }
  std::cerr << "\n";

  // If a reference is available from the garbage collector, use it.
  // Otherwise create a new reference by incrementing the reference
  // count.
  if(!(check &&
       vtkObjectBaseToGarbageCollectorFriendship::TakeReference(this)))
    {
    ++this->ReferenceCount;
    }
}

//----------------------------------------------------------------------------
void vtkObjectBase::UnRegisterInternal(vtkObjectBase* o, int check)
{
  std::cerr << this << " " << this->GetClassName() << " " << this->ReferenceCount << " Unregister by "
  if ( o )
    {
    std::cerr << o->GetClassName();
    }
  else
    {
    std::cerr << "NULL";
    }
  std::cerr << "\n";

  // If the garbage collector accepts a reference, do not decrement
  // the count.
  if(check && this->ReferenceCount > 1 &&
     vtkObjectBaseToGarbageCollectorFriendship::GiveReference(this))
    {
    return;
    }

  // Decrement the reference count.
  if(--this->ReferenceCount <= 0)
    {
    // Count has gone to zero.  Delete the object.
#ifdef VTK_DEBUG_LEAKS
    vtkDebugLeaks::DestructClass(this->GetClassName());
#endif
    delete this;
    }
  else if(check)
    {
    // The garbage collector did not accept the reference, but the
    // object still exists and is participating in garbage collection.
    // This means either that delayed garbage collection is disabled
    // or the collector has decided it is time to do a check.
    vtkGarbageCollector::Collect(this);
    }
}