I am trying to accelerate an algorithm where I am computing a 2D variance of many complex matrices stored as pages of a 3D array in Matlab. My implementation uses a Mex function with the C++ Matlab Data API. I perform Toeplitz convolution along both dimensions of the matrix with a simple kernel of ones to compute a new matrix of equal size. I then multiply the original matrix by the computed 2D variance matrix and store the sum of the result in my output. This "2D variance" is normalized by the sum of the magnitudes of the corresponding values. Here is a figure illustrating the concept of how the variance is calculated:
There are a few things I think could be improved to make this algorithm and/or code more efficient that I haven't been able to figure out:
- Instead of Toeplitz convolution with matrices Tband and Rband for each column and row of each page respectively, I could try some sort of fourier convolution. I haven't been able to accurately implement this where the outputs from both codes match...
- Is the use of temporaries with const auto and limiting evaluation until the end the most efficient way to implement the inside of the parallelized for loop? I tried preallocating some matrices that stored enough data for the number of threads of the given machine (measured at runtime), but I wasn't able to get better performance that way...
- Is there a way to perform these evaluations in Eigen without a for loop?
- Are there any other obvious optimizations I'm missing here?
Here's the code:
#include <iostream>
#include "mex.hpp"
#include "mexAdapter.hpp"
#include <Eigen/Dense>
#include <Eigen/Sparse>
#include <MatlabDataArray.hpp>
#include <cmath>
#include <math.h>
#include <omp.h>
class MexFunction : public matlab::mex::Function {
public:
// Pointer to MATLAB engine to call fprintf
std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();
// Factory to create MATLAB data arrays
matlab::data::ArrayFactory factory;
// Guarantee Alignment (not sure if this helps)
EIGEN_MAKE_ALIGNED_OPERATOR_NEW
void operator()(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
checkArguments(outputs, inputs);
// Initialize input parameters
long int numEl = inputs[0][0];
long int na = inputs[0][1];
long int nPoints = inputs[0][2];
long int szZ = inputs[0][3];
long int szX = inputs[0][4];
// Initialize input idxtMTX data
auto ptr = getDataPtr<std::complex<double>>(inputs[1]);
Eigen::Map< const Eigen::MatrixXcd > idxt( ptr, numEl*na, nPoints );
// Initialize Toeplitz band matrix inputs
auto ptr2 = getDataPtr<double>(inputs[2]);
Eigen::Map< const Eigen::MatrixXd > Rband( ptr2, numEl, numEl );
auto ptr3 = getDataPtr<double>(inputs[3]);
Eigen::Map< const Eigen::MatrixXd > Tband( ptr3, na, na);
outputs[0] = factory.createArray<std::complex<double>>({static_cast<size_t>(szZ),static_cast<size_t>(szX)});
auto ptrRecon = getOutDataPtr<std::complex<double>>(outputs[0]);
Eigen::Map<Eigen::MatrixXcd> Recon(ptrRecon,szZ,szX);
#pragma omp parallel for
for (int i = 0; i < nPoints; i++) {
// Define temporary pages of complex and absolute data
const auto idxtCol = idxt.col(i).reshaped(numEl,na);
const auto idxt2 = idxt.col(i).reshaped(numEl,na).cwiseAbs();
// Compute Numerator and Denominator respectively
const auto s1 = (Rband*idxt2).array()*(idxt2*Tband).array();
const auto s2 = ((Rband*idxtCol).array()*(idxtCol*Tband).conjugate().array()).abs();
// Compute result for only values where the denominator is nonzero. Otherwise set to zero. Then multiply by weights
const auto weight = (s1 != 0).select(s2/s1,0.0);
const auto result = idxtCol.array()*weight;
// Sum page and assign to output
Recon(i%szZ,i/szZ) = result.sum();
}
}
void checkArguments(matlab::mex::ArgumentList outputs, matlab::mex::ArgumentList inputs) {
std::shared_ptr<matlab::engine::MATLABEngine> matlabPtr = getEngine();
matlab::data::ArrayFactory factory;
if (inputs.size() != 4) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Five inputs required") }));
}
if (inputs[0].getNumberOfElements() != 5) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Need 3 input parameters") }));
}
if (inputs[0].getType() != matlab::data::ArrayType::INT32) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Input parameter must be integer") }));
}
if (inputs[1].getType() == matlab::data::ArrayType::DOUBLE ||
inputs[1].getType() != matlab::data::ArrayType::COMPLEX_DOUBLE) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Input idxtMTX must be type complex double") }));
}
if (inputs[1].getDimensions().size() != 2) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Input must be m-by-n dimension") }));
}
if (inputs[2].getType() != matlab::data::ArrayType::DOUBLE ||
inputs[2].getType() == matlab::data::ArrayType::COMPLEX_DOUBLE) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Input Rband must be type double") }));
}
if (inputs[2].getDimensions().size() != 2) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Input must be m-by-n dimension") }));
}
if (inputs[3].getType() != matlab::data::ArrayType::DOUBLE ||
inputs[3].getType() == matlab::data::ArrayType::COMPLEX_DOUBLE) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Input Tband must be type double") }));
}
if (inputs[3].getDimensions().size() != 2) {
matlabPtr->feval(u"error",
0, std::vector<matlab::data::Array>({ factory.createScalar("Input must be m-by-n dimension") }));
}
}
template <typename T>
const T* getDataPtr(matlab::data::Array arr) {
const matlab::data::TypedArray<T> arr_t = arr;
matlab::data::TypedIterator<const T> it(arr_t.begin());
return it.operator->();
}
template <typename T>
T* getOutDataPtr(matlab::data::Array& arr) {
auto range = matlab::data::getWritableElements<T>(arr);
return range.begin().operator->();
}
};
<math.h>
in C++.<cmath>
includes that one and puts the identifiers in thestd::
namespace. \$\endgroup\$feval
to error out? What were they thinking? \$\endgroup\$