Search Contact information
University of Cambridge Home Department of Engineering
University of Cambridge >  Engineering Department >  computing help >  programs >  matlab

Matlab vectorisation tricks

Some basic tips on speeding up matlab code and on exploiting vectorisation are mentioned in the Optimisation section of our matlab page. Some of the tricks below come from newsreader.mathworks.com and the Mathworks site. They are low-level and capable of delivering order-of-magnitude improvements.

I've added the author names (where known). If you have contributions, mail them to tpl@eng.cam.ac.uk.

Indexing using vectors

Many of these tricks use the fact that there are two ways of accessing matrix elements using a vector as an 'index'.

There can be a problem if MATLAB decides to use the masking scheme when you want index addressing but such situations are rare.

You can combine what you already know to solve some problems. For example, how can you determine the number of items in a 2D array that are greater than 8? Have a go before reading on ...

m=magic(5)

creates a 2D matrix that you can practice with. Doing

m>8

is part of the way towards an answer. How can you sum these elements? Try

sum(m>8)

That's closer - sum has summed the columns. If you sum again

sum(sum(m>8))

you'll get the answer. An alternative is

sum(m(:)>8)

m(:) has all the elements of m in a single column

Creating and manipulating Matrixes

To use the indexing ideas effectively you need to be able to create 'mask' matrices efficiently, and manipulate arrays. This requires the use of functions that you may not have used before. Some are listed here -

Reshaping matrices can help help when vectorising. Fortunately reshaping is a cheap operation - the underlying data isn't moved. It often helps to be able to locate the original location of an element even if the matrix has changed shape. sub2ind and ind2sub are useful in this regard. The following example creates a 3D matrix where one element is 7, makes that matrix into a vector and finds where the 7 is (it's the 3rd element), then works out where that element is in the original matrix.

m=zeros(2,3,4); m(1,2,1)=7; a=find(m(:)==7) [x y z ]=ind2sub(size(m),a)

Extra features of common routines

Some fairly commonly used routines have extra features that are especially useful when vectorising

Vectorising Routines

Examples

These examples are short, so by reading about the functions used and testing with small matrices, you should be able to discover why they work. More than one way is shown to solve some of these questions so that you can compare methods. When big matrices are used, you might find that some methods are hundreds of times faster than others. Remember that the most elegant-looking way may not be the fastest, and faster methods may use a lot more memory. Use flops or tic and toc to assess performance.

A worked example

You have 2 sets of (x,y,z) coordinates. Find the distance from each of the points in the 1st set to each of the points in the 2nd set
The following worked example may not be the most elegant solution, but I hope it's educational. Let's start with 2 little datasets.
one=[1 2 3 ; 6 5 9; 8 1 3] two=[1 2 3 ; 4 5 6]
Clearly one could use nested loops to solve this, but we're going to sacrifice space in the hope that we'll gain speed. First note that if we had 2 points a = [1 2 3] and b = [1 4 7] we can find the distance between them using sqrt(sum((a-b).^2)). How can we use this formula to find more distances at once? Suppose we have
aa = [ 1 2 3 ; bb = [1 4 7 ; 1 2 3 ] 1 2 3 ]
- how much do we need to adjust the formula we used for a and b? sqrt(sum((aa-bb).^2)) gives 0 2 4 because sum has summed the columns, but sqrt(sum((aa-bb).^2,2)) sums the rows and gives [4.4721 ; 0 ], which is what we want in this case. So if we could take the initial matrices one and two and replicate their rows to form the matrices below, we could get all the distances we need by using the revised formula.
bigone = bigtwo = [ 1 2 3; [ 1 2 3; 6 5 9; 1 2 3; 8 1 3; 1 2 3; 1 2 3; 4 5 6; 6 5 9; 4 5 6; 8 1 3] 4 5 6]
Getting bigone is easy enough. First we need to know the array sizes
[r1 c1]=size(one); [r2 c2]=size(two);
Now bigone=repmat(one,r2,1). There's probably an easy way to get bigtwo as well, but at the moment all I can see is
foo=repmat(two,1,r1) g=reshape(foo',3,r1*r2) bigtwo=g'
Now
a= sqrt(sum((bigone-bigtwo).^2,2))
will give us 6 distances as a column vector. We can put them into a more convenient shape by using reshape(a,r1,r2). Compressing these lines of code to avoid too many temporary variables (they slow things down) gives us
one=[1 2 3 ; 6 5 9; 8 1 3] two=[1 2 3 ; 4 5 6] [r1 c1]=size(one); [r2 c2]=size(two); bigone=repmat(one,r2,1); bigtwo=reshape(repmat(two,1,r1)',3,r1*r2)' reshape(sqrt(sum((bigone-bigtwo).^2,2)),r1,r2)
and the answer
0 5.1962 8.3666 3.6056 7.0711 6.4031
The code above when using matrices with 5000 and 100 points (created using one=rand([5000,3]); two=rand([100,3]);) was 30 times faster than the following code
[r1 c1]=size(one); [r2 c2]=size(two); for i=1:r1 for j=1:r2 answer(i,j)=sqrt(sum((one(i,:)-two(j,:)).^2)); end end

A bubblesort using 1 loop

Each time the program goes round the loop it will look at the (1st 2nd), (3rd 4th) , .... pairs, swapping the numbers if necessary, then it will look at the (2nd 3rd), (4th 5th) ... pairs, swapping the numbers if necessary. The resulting code might not be very fast ...

a=[ 1 2 5 4 8 10 3 6 9 7 0]; len=length(a); evenmask= repmat([0 1], 1, fix(len/2)); oddmask = repmat([1 0], 1, fix(len/2)); if rem(len,2)==1, evenmask(len)=0; oddmask(len)=1; end changed=1; while changed==1, changed=0; %swap odd-end pairs if necessary odds_shifted_right=[0 a(1:len-1)].*evenmask; evens=a.*evenmask; change = evens<odds_shifted_right; if length(find(change)) ~= 0, changed=1; swopped=odds_shifted_right+[a(2:len) 0].*oddmask; mask=change+ [change(2:len) 0]; a= ~mask.*a + mask.*swopped end %swap even-odd pairs if necessary odds_shifted_left=[a(2:len) 0].*evenmask; evens=a.*evenmask; change = evens>odds_shifted_left; change(len)=0; if length(find(change)) ~= 0, changed=1; swopped=odds_shifted_left+[0 a(1:len-1)].*oddmask; mask=change+ [0 change(1:len-1)]; a= ~mask.*a + mask.*swopped end end

A Lesson

A user sent this code to the newgroup, asking how to optimise it

N = 10; delt = 5/10; A = rand(N,N,N); L = zeros(N^3,4); ind = -250*delt:delt:250*delt; for di=1:N for dj=1:N for dk=1:N L((N^2)*(di-1)+N*(dj-1)+dk,:) = [ind(di),ind(dj),ind(dk),A(di,dj,dk)]; end end end

Having a complicated line within 3 levels of loops is likely to be costly. Matt Fig suggested

... X = repmat({1:N},3,1); [X Y Z] = ndgrid(X{:}); L = [ind([Z(:) Y(:) X(:)]) reshape(permute(A,[3 2 1]),[],1)];

which gives the same answer without using loops. Ingenious, hard to understand, but if speed is important, who care? He then offered

... cnt = 0; for di=1:N for dj=1:N for dk=1:N cnt = cnt + 1; L(cnt,1) = ind(di); L(cnt,2) = ind(dj); L(cnt,3) = ind(dk); L(cnt,4) = A(di,dj,dk); end end end

which is nowhere near as impressive. When I tried these out, the non-looping solution was about 10% faster than the original code whereas the last solution was 10 times faster. The moral of the story is - measure. Matlab can be good at optimising code with loops as long as the code is simple. Vectorised code sometimes has hidden costs in terms of time or memory.

See Also

© Cambridge University Engineering Dept
Information provided by Tim Love (tpl)
Updated June 2010