Upgrading an Algorithm

Learning Objectives

  • "Learn about upgrading algorithms to the new Gaudi framework."

Steps to convert

The following are the five steps to upgrade an algorithm.

Identify TES interactions

First, find and identify the input data and output data. You are looking for get, put, or getIfExists. For example:

Example: Locating TES interactionsClick to expand

Deduce functional algorithm to use

Out/in 0 1-n vector
0 Consumer
1 Producer Transformer MergingTransformer
n Producer MultiTransformer
vector SplittingTransform
boolean FilterPredicate
boolean+1-n MultiTransformerFilter

Possible inputs

  • 0 : no input, pure producer
  • 1-n : one or several independent inputs. This number and the type of each input must be known at compile time
  • vector : any number of inputs (not know at compile time), all of the same type, aka a vector of inputs of the same type

Possible outputs

  • 0 : no output, pure consumer
  • 1-n : one or several independent outputs. This number and the type of each output must be known at compile time
  • vector : any number of outputs (not know at compile time), all of the same type, aka a vector of outputs of the same type
  • boolean : an additional boolean output, saying whether we should carry on or stop the processing, aka a filter

Modify Algorithm declaration

  • Change inheritance from Algorithm to your functional algorithm base class.
  • Template the functional algorithm with the “signature” of the algorithm, that is its data flow.

Example: DeclarationClick to expand

  • All inputs are now using const references.
  • All declarations of members storing locations of input/output can be dropped (automatic retrieval from TES).
  • Equivalent properties are automatically defined by the functional framework.

Example: Member declarationClick to expand

Example: PrMatchNNClick to expand

Modify constructor/destructor

Changes

  • Initialize functional algorithm rather than Algorithm
  • New arguments are needed
    • Input location / list of input locations in TES
    • Output / list of output locations in TES
  • Locations are given using KeyValue objects, such as KeyValue{"VeloInput", LHCb::TrackLocation::Velo}
  • This creates the adequate property in the back
    • So old declareProperty lines can be dropped
  • For each location, a default might be given

Example: PrMatchNNClick to expand

Convert execute into operator(...)

The declaration of operator() will have a signature matching the data flow, and will be made using const references. The definition is of the form:

Output operator() (const Input&, ...) const override;

The input is a const reference, the method itself is both const and will override another method. The output is returned by value. It is a good idea to add std::move(...) around the return value to make sure a copy is not made; compilers, especially newer ones, may do this for you.

Compared to execute

  • Drop all interactions with TES (get/put)
  • Replace memory allocation of the output by simple creation on the stack
  • Thus change accordingly -> into ., drop some *
  • Return output rather than a StatusCode
  • Throw an exception when you were returning a bad StatusCode

Example: PrMatchNNClick to expand

Improving

These changes were made to give faster, more readable code, easy transition to parallel Gaudi, and more checks at compile time. To truly get improvements to the code, a few further steps should be taken to update the algorithms while it is already being modified and reviewed.

Property

Gaudi::Property provides a simple, clean way of declaring properties, and almost obsoletes the old declareProperty. It is not completely backward compatible, but fixes are trivial and usually result in better code. The property setup will exist in one place (header) instead of two (header and constructor).

Example: Property conversionClick to expand

In most cases, this change is transparent and you can use the property the same way. In cases where it does not work, such as when using -> for a pointer property, you can use .value()-> instead. Also, printing a property to an iostream is customized, giving name and value. If the old print is needed, use .value() to output just the value.

Modern C++

This is a great time to update to modern C++ syntax. Although all C++11 and much of C++14 is readily available, even in the base-line GCC 4.9 compiler, the following changes are especially useful:

  • Member initialization: By moving member initialization to the declaration, you can simplify the constructor and discover missing initializations.
  • Use nullptr instead of 0 or NULL: Type safety is always a good thing, and can catch pointer bugs.
  • Use using constructor if possible: if everything can be moved out of the constructor, you can use a using directive instead to further clean up code.
  • Use auto and range-based loops: Often it is nicer to your reader to be explicit, but in many cases, the type does not matter or is something complex, like an iterator, that really does not need to be known. Those can be moved to using auto to clean up code. Also, closely related, moving to range-based for loops can make code much, much easier to read.
  • Lambdas: Using the built in lambdas instead of boost::lambda and its quirks. Also can be used to move some tasks to the STL that used to be loops.

Thread safety

Moving to the new framework should take care of thread safety by using const, but there are a few ways around it that, unfortunately, are common in our code base. You can remove const with const_cast, and a member variable can be changed in a const method if it is declared mutable. As a general rule, you should only see mutable in C++11 when the variable itself is thread-safe (for example, a mutex). It should not be used to simply make adapting bad code easier. To remove them, you can usually simply remove the const_cast or mutable, then trace down the compiler errors to see what is happening.

Often these are introduced because code is not using the TES for data transmission; the new AnyDataHandle makes that trivial to do now, so the best solution is to put the data in the TES using an AnyDataHandle.

You'll want to remove Incidents, as well. You'll see something like this:

void handle(const Incident&) override final;

And, in the constructor:

incSvc()->addListener(this, IncidentType::EndEvent);

In most cases, this is only needed to reset a cache at the start of the event. The cache needs to be removed to be thread safe anyway, so that also solves the Incident issue. In the other cases, the TES or function arguments should replace the communication that the Incident was providing.

Further reading

results matching ""

    No results matching ""