I have a project that makes extensive use (high frequency) of a limited set of key linear algebra operations such as matrix multiplication, matrix inverse, addition, etc. These operations are implemented by a handful of linear algebra libraries that I would like to benchmark without having to recompile the business logic code to accommodate the different mannerisms of these various libraries.
I'm interested in figuring out what is the smartest way of accommodating a wrapper class as an abstraction across all of these libraries in order to standardize these operations against the rest of my code. My current approach relies on the Curiously Recurring Template Pattern and the fact that C++11 gcc is smart enough to inline virtual functions under the right circumstances.
This is the wrapper interface that will be available to the business logic:
template <class T>
class ITensor {
virtual void initZeros(uint32_t dim1, uint32_t dim2) = 0;
virtual void initOnes(uint32_t dim1, uint32_t dim2) = 0;
virtual void initRand(uint32_t dim1, uint32_t dim2) = 0;
virtual T mult(T& t) = 0;
virtual T add(T& t) = 0;
};
And here is an implementation of that interface using e.g. Armadillo
template <typename precision>
class Tensor : public ITensor<Tensor<precision> >
{
public:
Tensor(){}
Tensor(arma::Mat<precision> mat) : M(mat) { }
~Tensor(){}
inline void initOnes(uint32_t dim1, uint32_t dim2) override final
{ M = arma::ones<arma::Mat<precision> >(dim1,dim2); }
inline void initZeros(uint32_t dim1, uint32_t dim2) override final
{ M = arma::zeros<arma::Mat<precision> >(dim1,dim2);}
inline void initRand(uint32_t dim1, uint32_t dim2) override final
{ M = arma::randu<arma::Mat<precision> >(dim1,dim2);}
inline Tensor<precision> mult(Tensor<precision>& t1) override final
{
Tensor<precision> t(M * t1.M);
return t;
}
inline Tensor<precision> add(Tensor<precision>& t1) override final
{
Tensor<precision> t( M + t1.M);
return t;
}
arma::Mat<precision> M;
};
Questions:
- Does it make sense to use CRTP and inlining in this scenario?
- Can this be improved with respect to optimizing performance?
As pointed out in an answer, the use of polymorphism here is a bit odd due to the templating of the base class. Here is why I think this still makes sense:
You will notice the base class is named "Tensor" rather than something more specific like "ArmadilloTensor" (after all, the base class implements ITensor methods using Armadillo methods). I kept the name as is because according to my current design, the use of polymorphism is more due to a sense of formalism than anything else. The plan is for the project code to be aware of a class called Tensor that offers the functionality specified in ITensor. For each new library that I want to benchmark, I would just write a new "Tensor" class in a new compilation unit, package the compilation results into an .a archive, and when doing a benchmarking test, link the business logic code against that library. Switching between different implementations then becomes a matter of choosing which Tensor implementation to link against. To the base code it is all the same whether the Tensor methods are implemented by Armadillo or something else. Advantages: avoids having code that knows about every library (they are all independent), and no compile time changes are required in the base code in order to use a new implementation. So, why the polymorphism? In my mind I just wanted to somehow formalize the functions that need to be implemented by any new library that is added to the benchmark. In reality, the base code would then work with ITensors in the function parameters, but then potentially static_cast them down to Tensors in the method bodies themselves.