0

I am writing a method to measure the frequency of a sampled sine wave. It takes a somewhat large 1D array (10^3 to 10^4 samples order of magnitude) and returns a double. A helper methods is also called within the body of the method that checks whether the wave is crossing zero. Here is an example of what I have written:

public static double Hertz(float[] v, int n) {
    int nZCros = 0
    for (int i = 1; i < n; i++) {
        if (IsZeroCrossing(v.Skip(i - 1).ToArray())) {
            ++nZCros;
        }
    }
    return (double)nZCros / 2.0;
}
private static bool IsZeroCrossing(float[] v) {
    bool cross;
    //checks if the first two elements of the array are opposite sign or not
    return cross;
}

My problem is that method takes 200-300 ms to run. So I decided to try using unsafe and pointers, like this,

public unsafe static double Hertz(float* v, int n) {
    int nZCros = 0
    for (int i = 1; i < n; i++) {
        if (IsZeroCrossing(&v[i - 1])) {
            ++nZCros;
        }
    }
    return (double)nZCros / 2.0;
}
private unsafe static bool IsZeroCrossing(float* v) {
    bool cross;
    //checks if the first two elements of the array are opposite sign or not
    return cross;
}

which runs in 2-4 ms.

However, I am not really comfortable with venturing outside the recommended bounds. Is there a way to achieve the same speed in a safe context? And if there isn't, does it defeat the purpose of using C#? Should I really be using C# for these kind of signal processing applications and scientific implementations?

This is just one of many DSP methods I'm writing which take a large array of samples as an input. But this one got me to realize there was a problem, because I accidentally put in 48000 samples instead of 4800 when I was testing this method and it took 20 seconds to return a value.

Thank you.

UPDATE: I tried adding Take(2) after Skip(i - 1) in the former snippet. This brought it down to 90-100 ms, but the question still stands.

skwear
  • 563
  • 1
  • 5
  • 24

1 Answers1

3

You don't need to pass a copy of the array elements to IsZeroCrossing().

Instead, just pass the two elements you are interested in:

private static bool IsZeroCrossing(float elem1, float elem2)
{
    return elem1*elem2 < 0.0f; // Quick way to check if signs differ.
}

And call it like so:

if (IsZeroCrossing(v[i-1], v[i]) {

It's possible that such a simple method will be inlined for a release build, making it as fast as possible.

Matthew Watson
  • 104,400
  • 10
  • 158
  • 276
  • +1 This is a good answer, but it doesn't reach the heart of the bigger problem I was alluding too. I have other methods which do take the rest of the array from the offset and not just the first two elements. – skwear Oct 28 '16 at 14:09
  • 3
    @skwear, then in those cases, pass the array and the offset as suggested in the comments. – adv12 Oct 28 '16 at 14:11
  • Thanks @adv12. I have quite a bit of refactoring to do. – skwear Oct 28 '16 at 14:13