Using OCTAVE_LOCAL_BUFFER macro in place of Octave Matrix or Array classes

One of the items that came up at the last Online Developer Meeting was basing more of Octave internals on C++ libraries, rather than our own hand-rolled code. This should get us 1) more cross-platform compatibility, 2) less code of our own to maintain with a limited number of volunteers, and 3) better performance as library code has typically been optimized.

Although we haven’t done a root-cause analysis of why Octave has been slowing down over time, it is clear that there is a lot of memory allocations / memory frees going on. My suspicion is that this is because we have relatively heavyweight objects. Creating an octave_value which holds a Matrix requires creating and initializing dozens, maybe as many as 50, different member functions and going through multiple inheritance hierarchies.

While re-writing gsvd.cc, which interacts with LAPACK and Fortran code, I noticed that it was using Matrix as a way to get temporary, run-time scalable temporary memory. For example, if you need storage for N doubles (where N is known only at run-time) the code might be

Matrix tmp (N, 1);
double *ptr = tmp.fortran_vec ();

I can see the appeal. Because we have access to liboctave, this is a consistent cross-platform way to perform this task. However, it’s also a very expensive in terms of performance.

In the case of gsvd.cc, I just used the C++ STL and

std::vector<double> tmp (N);
double *ptr = tmp.data ();

which should be a better performing alternative.

As yet a third alternative, and possibly the highest performing, there is the OCTAVE_LOCAL_BUFFER macro which makes use of C++ <memory> library. To do the same thing as above, I can write

OCTAVE_LOCAL_BUFFER(double, ptr, N)

which will create the variable double *ptr with enough memory for N double objects.

I’m making a note of this here, because when interfacing with LAPACK there is often a need for scratchpad memory so the use of Matrix for temporary memory is likely to be a common pattern that could be replaced all over liboctave.

2 Likes

Yes, I agree that we should avoid using Matrix, NDArray, and other Array<T> objects when we only need work arrays or temporary scratch buffers.

If the array is only used as a temporary buffer that needs no indexing or other operations that are provided by std::vector or std::array, then it seems we could also use something like

std::unique_ptr<double> tmp (new double [N]);
double *ptr = tmp.get ();

which is what OCTAVE_LOCAL_BUFFER already does.