Search code examples
c#arrayspointersunsafe

Is there a faster way to loop over an array in safe mode


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.


Solution

  • 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.