[vtkusers] Poor VTK pipeline performance with filters with high fan-out and also many actors/mappers

James Johnston JamesJ at motionview3d.com
Wed Nov 20 15:50:59 EST 2013


Hi,

I am having some performance problems that are having a noticeable impact on
frame rate.  I've traced these issues to the
vtkDemandDrivenPipeline::ComputePipelineMTime function which recursively
evaluates the pipeline modified time.  It seems that in my application, the
modified time on various algorithms and data objects is being tested tens of
thousands of times, even though I only have a few hundred instances of
algorithms/data objects defined in my code total.

Summary of below:  My program spends all its time checking modified time;
what can I do about it?

1.  Some of my filter outputs fan out to multiple other filter inputs.
Those additional filter outputs are then merged back together by a final
filter.  In this case, the initial filter that fans out has its modified
time checked repeatedly - more times than necessary.  Example below to
explain this better.  Note that the example is very contrived; the real
application has a more complicated pipeline:

    const int baseCount = 40, userCount = 20;
    vtkSmartPointer<vtkPolyData> baseData[baseCount];
    vtkSmartPointer<vtkLoggingTrivialProducer> baseProducers[baseCount];
    vtkSmartPointer<vtkAppendPolyData> mergedBase =
vtkSmartPointer<vtkAppendPolyData>::New();
    for (int i = 0; i < baseCount; ++i) {
        baseData[i] = vtkSmartPointer<vtkPolyData>::New();
        baseProducers[i] =
vtkSmartPointer<vtkLoggingTrivialProducer>::New();
        baseProducers[i]->SetOutput(baseData[i]);
        mergedBase->AddInputConnection(baseProducers[i]->GetOutputPort());
    }

    vtkSmartPointer<vtkAppendPolyData> mergedUsers[userCount];
    vtkSmartPointer<vtkAppendPolyData> allUsers =
vtkSmartPointer<vtkAppendPolyData>::New();
    for (int i = 0; i < userCount; ++i) {
        mergedUsers[i] = vtkSmartPointer<vtkAppendPolyData>::New();
        mergedUsers[i]->AddInputConnection(mergedBase->GetOutputPort());
        allUsers->AddInputConnection(mergedUsers[i]->GetOutputPort());
    }
    vtkLoggingTrivialProducer::PrintMTimeCount();
    allUsers->Update(); // first update will have to execute some stuff in
the pipeline
    vtkLoggingTrivialProducer::PrintMTimeCount();
    allUsers->Update(); // pipeline already updated; just check modified
times 
    vtkLoggingTrivialProducer::PrintMTimeCount();

vtkLoggingTrivialProducer inherits from vtkTrivialProducer; it just logs how
many times GetMTime is called on it:

    static int GetMTimeCount;
    unsigned long GetMTime() {
        GetMTimeCount++;
        return this->Superclass::GetMTime();
    }
    static void PrintMTimeCount() {
        std::cout << "vtkLoggingTrivialProducer GetMTime called " <<
GetMTimeCount << " times." << std::endl;
    }

The pipeline can be summarized as follows:

baseProducers[baseCount] --> mergedBase (vtkAppendPolyData) -->
mergedUsers[userCount] --> allUsers (vtkAppendPolyData)

Note that the *same* mergedBase filter is fanning out to many mergedUsers
filters.  If we run the program, we get:

vtkLoggingTrivialProducer GetMTime called 0 times.
vtkLoggingTrivialProducer GetMTime called 800 times.
vtkLoggingTrivialProducer GetMTime called 1600 times.

Note that 800 = baseCount * userCount.  Certainly, there was no need to
check modified time on the second Update() call so many times - it shows
that pipeline modified time on the same mergedBase was completely recomputed
userCount times.

What I'd hopefully like to see is a way of doing something like this:

allUsers->Update() called; for each input to allUsers (the mergedUsers
array):
    a.  For mergedUsers[0], to compute the modified time, the mergedBase
time is recursively recomputed, as usual.
    b.  For mergedUsers[> 0], the mergedBase time was already computed and
hasn't changed, so reuse it without recursively recomputing mergedBase time.

2.  The second problem is somewhat related to the first.  In the previous
example, suppose we delete the allUsers object and have each mergedUser[]
array element feed into a mapper and actor instead.  In this case, the
renderer containing the actors is eventually going to Update() each mapper.
For the first mapper, there is a need to recursively recomputed the modified
time of mergedBase, but for further mappers in this case it isn't necessary
to recursively check modified time beyond mergedBase's modified time in the
pipeline - because mergedBase is being reused over & over.  I note that
vtkMapper does have a Static member that can be used to prevent updating the
pipeline and in my case it does improve performance significantly to desired
levels, proving that my problems have to do with modified time checking.
But I don't see how I can use it because then every piece of code that
modifies something in the pipeline needs to search out and manually update
any mappers.  Kind of defeats the purpose of a pipeline, and brings back the
maintainability nightmare I'm cleaning up.  The real application has
countless locations that change stuff in the pipeline, involving both GUI
buttons and also VTK widgets.  It also does nothing to solve the first
scenario I introduce.  (Both scenarios #1 and #2 exist in my program.)  I
suspect if #1 is solved, then a solution to this problem will be more
forthcoming.

Obviously, different pipeline topologies for this application may help
somewhat, but I can't think of any that won't create a maintenance headache
elsewhere.  So that's my very, very last-resort option that may not work out
anyway.  I'm trying to tie several general-purpose custom VTK filters
together as building blocks, and this is the topology that has made sense so
far.  It works great, except it's slow.  I'd rather come up with a general
fix to the problem to avoid these concerns, as I foresee creating more
potentially problematic pipelines in the future and not having these
problems would make that job a lot easier.

Note I'm on VTK 5.4.2, but checking source code of VTK 6.0's
vtkDemandDrivenPipeline's ComputePipelineMTime doesn't lead me to believe
that the situation would be any different there as the code looks identical.
(Unfortunately, the application is intricately tied to a C++ compiler that
VTK is no longer compatible with... at least for now.)

Best regards,

James Johnston




More information about the vtkusers mailing list