DevCaching

This page describes how to cache items you've constructed in a filter.

Overview

It is common with a data flow design to leave intermediate results around for re-use when attributes to a filter change. With this model, the filter that changes already has an up-to-date input, so only it and its downstream filters need to re-execute. VisIt does not follow this strategy. VisIt's basic philosophy is to be memory footprint conscious. As such, unneeded intermediate data objects are deleted and the entire pipeline needs to be re-executed when a filter's attributes change. That said, VisIt provides a facility for caching to accelerate the repeated execution case.

VisIt always performs I/O caching, since I/O is frequently the most expensive part of the pipeline. This caching is done automatically by the avtGenericDatabase class; caching is one of the services it provides. However, it is also possible to have filters in the pipeline cache objects they've constructed. We have made the design decision to store these objects in the database's cache. There are many reasons for this:

  • filters are transient in VisIt, while the database is permanent. Filters are frequently destroyed when their attributes change (instead, VisIt constructs a new pipeline with the same filters);
  • a database is often connected to many pipelines simultaneously. Through this design, derived objects can be shared across pipelines;
  • the caching strategy is centralized: you only have to tell the database to clear its cache and nowhere else;
  • thus, using databases fits gracefully within VisIt's existing implementation.

Caching dependencies

Some derived objects are specific to a given pipeline and these objects should not be cached. For example, a contouring operation can be accelerated by the presence of a scalar tree. If a Clip operator is in the pipeline before the contour, then the scalar tree generated for that contour filter is specific to the Clip, since this scalar tree can not be re-used in other pipelines. To handle this case, VisIt introduces the concept of dependencies and only allows derived objects to be cached if they don't violate any of the dependencies.

The dependencies are:

  • Data dependence: have the data values been modified in any way?
  • Spatial dependence: have the coordinates been transformed in any way?
  • Connectivity dependence: has the connectivity been modified in any way?

For example, a spatially-oriented derived type like a spatial locator used to accelerate streamlines, should not be cached if the spatial dependence is violated. However, it really doesn't matter to this data structure if the data dependence is violated.

Checking dependencies in VisIt

The three dependency types each have their own entry in an enumerated type, avtFilter::CacheItemDependence:

       DATA_DEPENDENCE = 1,          // Example: scalar tree for contouring
       SPATIAL_DEPENDENCE = 2,       // Example: lookup structure for streamlines
       CONNECTIVITY_DEPENDENCE = 4   // Example: facelist (point positions not important)

From your filter, you can call the method avtFilter::CheckDependencies:

   if (CheckDependencies(DATA_DEPENDENCE | CONNECTIVITY_DEPENDENCE))

Storing and fetching from the cache

There are two types of objects that can be stored to and fetched from the cache:

  1. VTK objects
  2. void_ref_ptrs

VTK objects have their own reference counting system and hence require a special case. void_ref_ptrs are VisIt's way of reference counting arbitrary objects. This class is used for everything but VTK objects.

This code fragment from avtContourFilter informs how to use caching from inside a filter:

   if (useScalarTree)
   {
        int ts = timeslice_index;
        tree = (vtkVisItScalarTree *) FetchArbitraryVTKObject(DATA_DEPENDENCE,
                                          contourVar, domain, ts, "SCALAR_TREE");
        if (tree == NULL)
        {
            tree = vtkVisItScalarTree::New();
            tree->SetDataSet(toBeContoured);
            int id0 = visitTimer->StartTimer();
            tree->BuildTree();
            visitTimer->StopTimer(id0, "Building scalar tree");
            StoreArbitraryVTKObject(DATA_DEPENDENCE, contourVar, domain, ts,
                                    "SCALAR_TREE", tree);
        }
        else
            tree->Register(NULL); // to account for later dereference
   }

Some notes about this routine:

  • Note that this code is storing a derived object for each domain. If your derived object is for all domains, we recommend the convention where you use "-1".
  • Note that this code is associating the derived object with a specific time slice. If your derived object applies to all time slices, we recommend the convention where you use "-1". Although the generic database regularly purges data not associated with the current time slice, it is believed that "-1" is exempt from such purges. That said, this has not been demonstrated with this code and you are encouraged to test this claim as you do your development.
  • The fetch and store commands take the dependence type as the first argument. These methods in turn call CheckDependencies automatically. If the dependencies are not satisfied, then that is determined automatically. Your store command will be a no-op and the next fetch command will return NULL.