Loren on the Art of MATLAB

January 18th, 2006

Inverting Logic in an Algorithm

A while ago, a colleague needed to use the sinc function to interpolate some channel data posted by the IEEE 802.15.3a working group. The files they supplied were large and the interpolation took a while. Seems like a job worthy inspecting the code and using the profiler (see references below). Here's what you'll find.

The sinc function used to contain this code, avoiding directly evaluating sinc(0):

y=ones(size(x));
i = find(x);
y(i)=sin(pi*x(i))./(pi*x(i));

The output is preloaded with ones and the function is only evaluated for nonzero input values. The problem with this is that the indexing in the third line is very expensive in terms of time.

Thinking about using the sinc function, it often gets used for signal processing waveform creation. So ask yourself, in a signal, how many of the time elements are 0 – and the answer is typically a very small number, usually either 0 or 1.

The following approach seems to run about 30 to 40% faster, based on a very unscientific study, and is what ships today in the Signal Processing Toolbox.

i=find(x==0);
x(i)= 1;  % From LS: don't need this if /0 warning is off
y = sin(pi*x)./(pi*x);
y(i) = 1;

In this case, we look for the zero values (most likely a small number of them), replace them with ones, and then evaluate the expression on the whole input array. After that's complete, we replace the output values corresponding to input values of zero with ones.

Note the comment on the second line. If warnings for zero-division are off, then removing that line gives even more speed improvement. Why? Because a copy of the x array needs to be made if the second line is there since some values in x are being modified; the local copy no longer matches the array passed into the function. MATLAB will only use extra memory for an input array if it gets modified inside the function (see the documentation for more information).

Finally, an even shorter version, where we don't worry about the 0 division warning might look like this:

y = sin(pi*x)./(pi*x);
y(x==0) = 1;

In this version, we are performing the computation on the original array x, and then operating on the output, y using logical indexing to replace values where x = 0 with the value 1, taking advantage of MATLAB's scalar expansion.

Profiler References

Here are several pointers into the MATLAB documentation with information about the MATLAB Profiler.

4 Responses to “Inverting Logic in an Algorithm”

  1. Peter Boettcher replied on :

    Skipping the 0 check could result in much worse penalty than copying the data array. On the Pentium IV architecture, FPU operations that result in NaN or Inf are very expensive. Divide-by-zero warnings being on or off doesn’t matter. For example:

    warning(’off’, ‘MATLAB:divideByZero’);

    x = round(rand(1,1e7)); % Roughly equal number of zeros and ones
    y = x + 1; % No zeros

    tic; s = 1./x; toc
    tic; s = 1./y; toc

    The timing results on my machine come out to 2.2s for the first case, with 1/2 zeros, while the second case only takes 0.34 seconds.

    Of course, your point in this was that the number of input elements that are exactly zero is very small. When I change the proportion to 10000 zeros (1%), then it’s only 0.38 vs the 0.34.

    Just something to consider for other optimizations when there are many zeros. The same thing will apply to processing with NaNs.

  2. Thomas Kite replied on :

    To get around the sinc(0) = 1 problem, I add eps to the arguments in the numerator and denominator: sin(x + eps) ./ (x + eps). This gives negligible error and avoids the division by zero problem (unless x is -eps, which seems very unlikely). The following code:

    % Compare sinc on a random vector
    x = randn(1000000, 1);
    y1 = sin(pi * x) ./ (pi * x); y1(x==0) = 1;
    y2 = sin(pi .* x + eps) ./ (pi .* x + eps);

    % Compare sinc on a zero vector
    x = zeros(size(x));
    y1 = sin(pi * x) ./ (pi * x); y1(x==0) = 1;
    y2 = sin(pi .* x + eps) ./ (pi .* x + eps);

    gives results within eps in the first case and identical results in the second. Using the profiler, we see execution times of 0.20/0.19 seconds for the random vector, and 0.42/0.05 seconds for the zero vector. Thus in both instances the ‘add eps’ version is faster. The results would also seem to bear out Peter’s comments above, that processing with NaNs is slow.

  3. Vijay replied on :

    Hi Loren,
    Is there anyway to globally cast the class of all variables to ’single’ instead of ‘double’? For instance if I type a=10, I would like ‘a’ to be of class ’single’ instead of ‘double’. Of course, I could do a=single(10), but I can avoid the additional casting step if I can set ’single’ to be the default class.
    Thanks,
    Vijay

  4. Loren replied on :

    Vijay-

    There is not a way to change the default. But you can do things like:

    x(1,400) = single(0);
    

    and write code that is agnostic with respect to what kind of floating point, e.g.,

    y = zeros(1,n,class(input1));
    

    –Loren

Leave a Reply

Wrap code fragments inside <pre> tags, like this:

<pre class="code">
a = magic(3);
sum(a)
</pre>

If you have a "<" character in your code, either follow it with a space or replace it with "&lt;" (including the semicolon).


Loren Shure works on design of the MATLAB language at The MathWorks. She writes here about once a week on MATLAB programming and related topics.

  • Jun: I totally can not believe it, Loren. You are really helpful. Thank you so much, MATLAB master!
  • Loren: Wow folks- Always lots of interest when there’s a quickie to try out! I will only make 2 general...
  • Loren: Jun- ismember is your friend here: >> [aa,ind] = ismember(Array2,Arra y1) aa = 1 1 1 1 1 1 1 ind = 1 2 1 4 4 3...
  • Dan: I like the first way better than the second way. Combining the arrays into one and running any is nice, although...
  • James Myatt: How about I = (a == 0 | b == 0); a(I) = []; b(I) = [];
  • Tunc: Hello Loren, love your blog because of such inspiring and challenging comments to such ’small’...
  • Pekka Kumpulainen: Here is my tradeoff. I usually want to keep the original variables as they are most probably...
  • Iain: Followup: Of course, to allow NaNs (counting them as non-zero): mask = (a~=0) & (b~=0); The mask says “a...
  • Matt Fig: I would usually go with something like this: y = a&b; x = a(y); y = b(y); But I was surprised to find...
  • kk: c=all([a;b]) a(c) a(b)

These postings are the author's and don't necessarily represent the opinions of The MathWorks.