Speedup by reducing number of for loops

Viewed 44

I am currently coding using Matlab. As everyone knows, for loops are slow, while Matlab does matrix multiplication very efficiently. Unfortunately, my code is full of for loops.

My code resembles this:

function FInt = ComputeF(A, B, C, D, E, F, G, H)
    %A is a real column vector of size [Na, 1].
    %B is a real column vector of size [Nb, 1].
    %C is a real column vector of size [Nc, 1].
    %D is a real column vector of size [Nd, 1].
    %E, F, G are real column vectors of the same size as A.
    %H is a real column vector of the same size as A.
    %This function evaluates FInt, a tensor of size [Na, Nb, Nc, Nd]. 

    %Recording the correct dimensions and initializing FInt
    Na = size(A, 1);
    Nb = size(B, 1);
    Nc = size(C, 1);
    Nd = size(D, 1);
    FInt = zeros(Na, Nb, Nc, Nd);

    %Computing the tensor FInt
    for na = 1:Na
        for nc=1:Nc
            for nd=1:Nd
                %Calculating intermediate values
                S1 = -((B(:) - C(nc) + E(na)) ./ (2 * sin(D(nd) ./ 2))).^2;
                S2 = (B(:) + C(nc) + F(na)) ./ (2 .* cos(D(nd) ./ 2));
                S3 = (B(:) + C(nc) + G(na)) ./ (2 .* cos(D(nd) ./ 2));
                S4 = H(na) ./ cos(D(nd) ./ 2);
                %Calculating the integrand FInt
                FInt(na, nc, :, nd) = exp(S1) .* (sinh(S2 + 1i * S4) + conj(sinh(S3 + 1i * S4)));
            end
        end
    end
end

As you can see I have already tried to vectorize the process by using : for the vector B, improving at least a bit the speed of computations. (Why B? Usually it is the longest vector).

My problem is that the quantities depend on so many indexes that I have no idea how to vectorize it properly.

1 Answers

In numpy, there is an idea that is formally called broadcasting. MATLAB introduced the concept in R2016b. It is called "vectorization", "expansion", and sometimes "broadcasting" in the MATLAB community. The idea is that if you line up the dimensions of a bunch of arrays, you can expand unit dimensions to match the full ones. Here is a great resource on the subject: https://blogs.mathworks.com/loren/2016/10/24/matlab-arithmetic-expands-in-r2016b/.

If you want your result to have size [Na, Nb, Nc, Nd], you can make all your arrays of the appropriate size, with ones filling in the missing dimensions:

A = reshape(A, Na, 1, 1, 1);
B = reshape(B, 1, Nb, 1, 1);
C = reshape(C, 1, 1, Nc, 1);
D = reshape(D, 1, 1, 1, Nd);
E = reshape(E, Na, 1, 1, 1);
F = reshape(F, Na, 1, 1, 1);
G = reshape(G, Na, 1, 1, 1);
H = reshape(H, Na, 1, 1, 1);

Now you can perform vectorized operations on these arrays directly with no ambiguity:

S1 = -((B - C + E) ./ (2 * sin(D ./ 2))).^2;
S2 = (B + C + F) ./ (2 .* cos(D ./ 2));
S3 = (B + C + G) ./ (2 .* cos(D ./ 2));
S4 = H ./ cos(D ./ 2);
%Calculating the integrand F
FInt = exp(S1) .* (sinh(S2 + 1i * S4) + conj(sinh(S3 + 1i * S4)));

Notice that all the explicit loops were removed here. The sizes of the intermediate arrays depend on the sizes of their inputs:

size(S1) == [Na, Nb, Nc, Nd]
size(S2) == [Na, Nb, Nc, Nd]
size(S3) == [Na, Nb, Nc, Nd]
size(S4) == [Na, 1, 1, Nd]

You don't need to preallocate the output because it automatically results from the sizes of the inputs.

Related