Search code examples
performancematlabloopsnested-loops

How to improve speed of nested loop/ Looking for more efficient codes


I have the following code which is too slow right now and I am looking for better ways to write it.

This code is part of a much longer code including a for loop t=1:T, therefore the following code runs for every t.

I have F firms, which produce Yd. In every period t they buy a certain amount K of machine tools, whose productivity is given by A; therefore, the productive capacity of the machine tools bought in t equals AK=A.*K. Firms' total productive capacity equals sum(AK). Since sum(AK) might be greater than what they want to produce Yd, firms have to determine which fraction they will utilize (omega) of each machine "v" they own, considering that they start using the most productive machine tools first (those with the highest A). Thus, starting from the most productive machines, if the productive capacity from a certain machine is lower than Yd, omega of that machine will be set to 1, if the productive capacity has already reached Yd, omega of that machine will be set to 0, otherwise omega will be set to the fraction required. How can I code that?

This is what I did but it is definitely too slow:

T=100;                     
F=50;

A=rand(T,F);                  %initialized randomly (not as in my real code)
K=rand(T,F);                  %idem 
AK=A.*K;                      %productivity of machine tools
Yd=rand(1,F);                 %initialized randomly 
omega=zeros(T,F);             %needs to be computed                                     
OAK(:,:)=zeros(T,F);          %needs to be computed                                       

   for f=1:F
  [A_sorted_value,A_sorted_index]=sort(A(:,f));                            %sort machines according to their productivity level

        while sum(A_sorted_value)>0 && sum(OAK(1:t,f))<Yd(f)               %apply rule as long as there are machines available and desired production has not been achieved
            v=A_sorted_index(end);                                         %pick the best machine of capital, i.e. highest productivity A
            if AK(v,f)+sum(OAK(1:t,f))<=Yd(f)                              %assign omega=1 if existing capacity is below desired production
                omega(v,f)=1;
                OAK(v,f)=omega(v,f).*AK(v,f);                              %update existing capacity

            elseif AK(v,f)+sum(OAK(1:t,f))>Yd(f) && Yd(f)>sum(OAK(1:t,f))  %assign omega so that machine v can fill desired production
                omega(v,f)=(Yd(f)-sum(OAK(1:t,f)))/AK(v,f);
                OAK(v,f)=omega(v,f).*AK(v,f);

            end
            A_sorted_index(end)=[];                                        %remove machine v from the list and pick the next one if condition "while" is satisfied
            A_sorted_value(end)=[];                                        %otherwise go to the next firm
        end
    end

I hope it is clear and thanks in advance!


Solution

  • It appears that you are running some kind of simulation. This means (if results depend on previous iterations), your t and f loop can't be optimized away. So the only optimization target available would be the inner while loop. I'd recommend you consider whether or not you can pre-calculate or vectorize anything (check your math on paper).

    But since I can't really help you with that, here are some things which might help.


    One low hanging fruit I can offer immediately, is you can use the sort function to sort the entire matrix in some set direction, so you can move that outside the f loop.

    % Sort the entire A array, column-wise.
    [AVals, AIdxs] = sort(A,1);
    for f = 1:F
        A_sorted_value = AVal(:,f);
        A_sorted_index = AIdx(:,f);
        ...
    end
    

    Next, I recommend that you use a for loop instead of your while loop.

    Since you already know that you will be iterating through the entire sorted A list, or until another condition (sum(OAK(1:t,f))<Yd(f)) is met, this can be done with a for loop. Combined with the above recommendation, you might get something like this:

    % Sort A in descending order instead, so first element in the array is now highest in value
    [AVal, AIdx] = sort(A,1,'descend');
    for f = 1:F
        A_sorted_value = AVal(:,f); % Appears to be unused?
        A_sorted_index = AIdx(:,f);
    
        % Iterate through entire A_sorted_index array, eliminates rewriting of A_sorted_index
        for i = 1:length(A_sorted_index)
            v = A_sorted_index(i);
            
            % Loop Exit Conditions
            if (sum(OAK(1:t,f))<Yd(f)) || (sum(A_sorted_value(1:i)) <= 0)
                break
            end
    
            % Your code below
            %assign omega=1 if existing capacity is below desired production
            if AK(v,f)+sum(OAK(1:t,f)) <= Yd(f)
                omega(v,f)=1;
                %update existing capacity
                OAK(v,f)=omega(v,f).*AK(v,f);
    
            %assign omega so that machine v can fill desired production
            elseif AK(v,f)+sum(OAK(1:t,f))>Yd(f) && Yd(f)>sum(OAK(1:t,f))
                omega(v,f) = (Yd(f)-sum(OAK(1:t,f)))/AK(v,f);
                OAK(v,f) = omega(v,f).*AK(v,f);
            end
        end
    end
    

    Now, A_sorted_value appears to be unused. I can't assume that the value of elements in A are non-zero, positive numbers. So I had to include the check condition sum(A_sorted_value(1:i)) <= 0. But if you do have only non-zero, positive A elements, you can remove that snippet to speed the loop up even more.


    I profiled this code against the old one, at T=200, F=100, ran in a loop 50 times.

    new = 6.835 secs
    old = 65.634 secs
    

    Which is approximately a 90% time reduction.

    A large amount of the time reduced came from removing these lines of codes, which were quite wasteful.

    A_sorted_index(end)=[];
    A_sorted_value(end)=[];
    

    I'm sure there are still plenty more room for optimization, but this is good enough for me.

    Do verify that the code I provided is correct.