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