LimaKilo
LimaKilo

Reputation: 218

Improving MATLAB logical indexing performance

I'm working on a function that selects all points within a bounding box around a line from a large coordinate set (to speed up a RANSAC fit). I ran the script containing the function last night, and of the total 55k seconds 30k (in 55M calls) were spent in this line (X are 3×N cartesian coordinates, xmin, xmax etc. are vectors of same length as X and corresponding boundaries):

inlierIdx = ( X(1,:) >= xmin & X(1,:) <= xmax & X(2,:) >= ymin & X(2,:) <= ymax );

Could you help me on how to speed this up? Short-circuiting, which would be a good start to improve performance, doesn't seem to work with indexing.

Or if there would be a completely different better approach, here's the rest of the function's code (p1, p2 are the points defining the line, a is the offset of the bounding box; the additional steps took another 12k secs for 55M calls):

function [inlierX, xmin, xmax, ymin, ymax] = selectbox(X, L, a) % xmin ... ymax only for plotting purposes
% X: 3xN coords to check; L: 3x2 vector defining line; a: offset of bounding box
p1 = L(:,1);
p2 = L(:,2);

constfactor = (X(3,:)-p1(3))/(p2(3)-p1(3)); % precompute for following steps
xmin = p1(1) + (p2(1)-p1(1))*constfactor - a; % line parallel to x-component of defined line, with offset
xmax = xmin + 2*a;
ymin = p1(2) + (p2(2)-p1(2))*constfactor - a;
ymax = ymin + 2*a; 

inlierIdx = ( X(1,:) >= xmin & X(1,:) <= xmax & X(2,:) >= ymin & X(2,:) <= ymax );

if p1(3) == p2(3) % singularity if line is horizontal, discard then
    inlierIdx = [];
end

inlierX = X(:,inlierIdx); % save & return inlier coordinates

It just occurred me that I should move that singularity if-clause so the computation is skipped if true, but that's a minor performance hit.

The function is called for each RANSAC sample, to select only points in a reasonable distance from the sampled line to compute their distance for thresholding.

MATLAB version is R2016a.

Parallel Computing Toolbox is available, I tried moving the steps into arrayfuns and calling the whole function with gpuArrays, but it was way slower.

The whole context is as follows:

prepare coordinates

while trialcount < expectedNumTrialsNeeded

draw random sample (generated array of randomly sampled coordinates and index into it)

check if sample is not degenerate and has allowed angle

compute number of inliers:

    this calls my selectbox function, to select a smaller set of points, which are near enough to check - takes 30k sec of 55k

    take those points and compute their distance to the line, all points within threshold are inliers - takes 12k secs of 55k

if number of inliers > best number of inliers yet, this is new best set

update expected number of trials needed to find sample of only inliers
increment trialcount

end

return best set

I work with arrays of 10k-100k points, and for each line I want to fit I run 1E+5...1E+6 trials to find the best set. I have around 50 lines to fit, I work through them by deleting inliers of found lines and doing the algorithm on the remaining.

Upvotes: 3

Views: 534

Answers (2)

Alex Chudinov
Alex Chudinov

Reputation: 724

What about to use columnwise indexing in your code? I mean to use X as Nx3 cartesian coordinates not as 3xN. As far, as I know columnwise indexing can be faster due to MATLAB arrays are stored in a FORTRAN manner.

Upvotes: 1

Rody Oldenhuis
Rody Oldenhuis

Reputation: 38042

I doubt that that line is the real problem. You say you do 55 million calls to this function -- may we see the code that does that? Because I strongly suspect that this code is where the true problem lies.

I suspect that you call this function in a loop, which would make it hard/impossible for MATLAB to accelerate the call effectively with its JIT compiler. If that is the case, then indeed, the heaviest burden shown will be this one line, but it's only so slow because your code structure forces MATLAB to keep calling the interpreter, rather than execute machine code. If this is indeed the case, then copy-pasting this small function in-place in the loop body will tremendously speed everything up (that is, if the remainder of the loop body can also be accelerated...)

Well anyway, this is what I could make of it. It reduces the number of comparisons at the cost of additional indexing; I also doubt that this is faster...oh well, just do 1000 calls and compare.

function [inlierX, xmin, xmax, ymin, ymax] = selectbox(X, L, a)

    % X: 3xN coords to check; L: 3x2 vector defining line; a: offset of bounding box
    p1 = L(:,1);
    p2 = L(:,2);

    constfactor = (X(3,:)-p1(3)) / (p2(3)-p1(3));

    % line parallel to x-component of defined line, with offset
    xmin = p1(1) + (p2(1)-p1(1))*constfactor - a; 
    xmax = xmin + 2*a;
    ymin = p1(2) + (p2(2)-p1(2))*constfactor - a;
    ymax = ymin + 2*a;

    % singularity if line is horizontal, discard then
    if p1(3) ~= p2(3)        
        inlierIdx            = X(1,:)         >= xmin;        
        inlierIdx(inlierIdx) = X(1,inlierIdx) <= xmax(inlierIdx);
        inlierIdx(inlierIdx) = X(2,inlierIdx) >= ymin(inlierIdx);
        inlierIdx(inlierIdx) = X(2,inlierIdx) <= ymax(inlierIdx);                
    else
        inlierIdx = [];
    end

    inlierX = X(:,inlierIdx); % save & return inlier coordinates

end

Upvotes: 1

Related Questions