Memory Management

Overview

This page will cover the practices used by VisIt for doing memory management with VTK and AVT.

It will be broken into three parts:

  • How to manage memory for a VTK object
  • What the policy is for memory management of VTK objects in AVT filters
  • What the policy is for memory management of VTK objects in AVT database plugins

It should be noted that there is no notion of memory management for AVT objects in this document. This is because AVT uses the ref_ptr templates. Ref_ptr automatically manages issues related to managing reference counts and freeing unreferenced objects. So AVT objects can be allocated with no care regarding their de-allocation (which will happen automatically).

How to manage memory for a VTK object

VTK objects contain their own mechanism for managing memory. This mechanism makes use of reference counts. All VTK objects derived from the class vtkObject, which contains methods for explicitly maintaining the reference count. Here are some important points:

  1. When a VTK object is instantiated, its reference count is 1.
  2. You can increase the reference count of a VTK object, obj, by calling obj->Register(NULL). The Register method takes an argument of a VTK object. This is to aid in debugging memory leaks. The Register method can be instrumented to return statements like “VTK object obj was just referenced by a vtkAppendFilter”. It would get the class type (vtkAppendFilter) through the argument to Register. Of course, since we are typically increasing the reference count while living in a non-VTK world, we have no VTK object to offer as an argument. Hence NULL is used most often.
  3. You can decrease the reference count of a VTK object by calling Delete().
  4. VTK filters instantiate a new data object to be their output. These data objects have reference count of 1. But if the filter is then deleted, it decrements the reference count of its output data object.

Number 4 is a common situation. The question becomes: how to separate a VTK data object from the filter that generated it? It can be done with the following code:

vtkDataSet *obj = filt->GetOutput();
obj->Register(NULL);
obj->SetSource(NULL);
filt->Delete();

By increasing the reference count, obj can live on after filt is destructed. And by setting its source to NULL, obj will not try to reference filt when it is used in subsequent pipelines. Of course, reference count of obj now must be decremented when obj is no longer wanted (and it is your burden to do so).

How to manage memory for an avtFilter

There are essentially two use cases.

In the first use case, the avtFilter is a derived type of avtStreamer. This is important because avtStreamer has a virtual method called ExecuteData. So here is the common scenario: to satisfy the ExecuteData call, the avtFilter sets up a VTK pipeline. The input to that pipeline is the VTK data set that is given to the ExecuteData method as an argument, and the output of the pipeline is also a VTK data set.

So how do we do memory management? The avtFilter would like for the output to be managed by someone else, but that filter still needs to clean up the VTK filters it instantiated. So we essentially have situation #4 from the previous section (that is, we want to delete the filters, but retain the output). We follow a similar procedure to that outlined above. At this point, the avtFilter is ready to return the data set, but it has a reference count of one. It should be noted that it could be the convention that the avtFilter returns this data set with one extra reference and the caller decrements the reference when it has put the data set into a larger data structure. But this is not our convention. This mostly dates back to legacy reasons, but there are still merits to it (memory management is self-contained, also easier to debug memory leaks). So what is the mechanism to manage the memory? There is a convenience function called “ManageMemory” provided by avtStreamer. This will temporarily set up an extra reference to the data set. After calling ManageMemory on a data set, you can decrement the reference count (i.e. call Delete()). In essence, ManageMemory allows the ExecuteData method to return the data set and pretend that it has reference count 0 without causing it to be deleted.

In pseudo-code, this looks like:

vtkFilter *filt = vtkFilter::New();
filt->SetInput(input);
filt->Update();
vtkDataSet *obj = filt->GetOutput();
ManageMemory(obj);
obj->SetSource(NULL);
filt->Delete();
return obj;

The other use case is that we are not using a derived type of avtStreamer. In this case, the output will be made up of avtDataTrees. Because avtDataTrees live in the world of ref_ptr’s, no special attention is needed for their memory management. And, as soon as a VTK data set is added to an avtDataTree, it’s reference count is increased. So the VTK data set can be deleted as normal to prevent leaks.

How to manage memory for a database plugin

The basic convention for each GetVar() or GetMesh() call is that the object returned by that method is owned by the caller. That is, the reference count of each object should be one. And the avtGenericDatabase (which calls GetVar or GetMesh) will delete the object when it is done with it.

In pseudo-code:

avtYourFileFormat::GetVar()
{
    vtkDataArray *arr = vtkDataArray::New();
    // set up array
    return arr;
}

In addition, sometimes file formats will return the same object repeatedly. A good example of this would be a file format where the mesh stays constant across all time states. Then the mesh for the first time state applies to all. In this case, the file format must be incrementing the reference count for each call, to ensure that the caller doesn’t remove the object it is caching.

In pseudo-code:

avtYourFileFormat::GetMesh()
{
    if (theMesh == NULL)
    {
        theMesh = vtkDataSet::New()
        // read the mesh
    }
    theMesh->Register(NULL); // Add a ref so caller can “own” it.
    return theMesh;
}

Debugging memory leaks

A separate page is devoted to debugging memory leaks.