





|
Using FFTW in C#
FFTW (Fastest Fourier Transform in the West) is a discrete fourier transform
library that rightfully boasts its superiority. Its trick is that it generates the transform code on-the-fly, so
when you use it on a variety of different computers it's able to optimize very well. Now, since most of my (recent)
programming experience has been in C#, I figured I'd just use PInvoke to access the FFTW library functionality.
Anyway, to use this, just download the dll and the newest release of FFTW and start using it. I quite literally
just exported all of the function calls from FFTW, so anything that's in the original documentation will work in C# as well.
Which brings me to my next point: Please read through the FFTW
documentation to figure out how FFTW works. Since this is just a wrapper library, the only thing I will actually
explain here is the memory management and such. With that said, if you have any questions about FFTW, I'd
be happy to answer them (though there are better people to ask).
Anyway, the only thing worth noting is the creation and accessing of data from within C#, if one should choose to
use safe code (which I typically do). You can either choose to create the arrays using FFTW's malloc(), which
is identical to the basic malloc() except that it makes sure the data is aligned on a certain boundary. This
returns an IntPtr, which means you can use Marshal.Copy to read or write data, and then the FFTW free() to
release the memory. Your other option is to declare a float[] or double[] array in C# and then create a
GCHandle.Pinned object to ensure that the array does not move (and use the GetAddrOfPinnedObject() to get the
actual IntPtr). Using malloc(), the actual transform is faster by up to 3x (but not always!), and you know
you're using an FFTW-friendly method. However, you need to store two copies of the data that you copy back and
forth using Marshal.Copy, which takes up space and time. Using GCHandle.Pinned, however, the behavior is less
predictable. I was doing speed tests, and some arrays seemed to be up to 2-3x slower than their GCHandle.Pinned
counterparts while others were indistinguishable. I assume this was due to whatever alignment, but I could find
no good way of fixing it. If anybody has a solution short of manually aligning via
unsafe code, do let me know.
So, what's the speed loss of using FFTW from C#, you ask? Well, the bulk of it (aside from the aformentioned
memory issues) is the PInvoke overhead. This is the nonnegligible amount of time it takes .NET to call an
external function, and the time you will care about it is when you call FFTW's execute() function. Basically,
the fewer times you make that call, the better. Most of the time you should be okay, but if you're planning
on doing an extremely large number of small fourier transforms, you might be better off writing a C++ dll that
makes the iterated calls for you, so you only call an external function once. Note that there is an
advanced interface
that will let you create plans for multiple consecutive FFTs provided that the arrays are spaced regularly
in memory.
Since I have received a number of emails on the subject, I figured it might be useful to actually provide some sample code.
So, without further ado, here is the code to create and use all types of managed and unmanaged arrays, create
plans, execute them, and then clean them up afterwards.
Keep in mind that the following code demonstrates all the various types of arrays simultaneously. You will most likely
not need to use each type of array, just one or two for your specific purpose. If you are receiving your data from some other library
that uses unmanaged memory, you can just allocate unmanaged memory for the input array and use a memcpy
call to transfer it; only use C# arrays if you plan on directly manipulating the data.
using System;
using System.Runtime.InteropServices;
using fftwlib;
namespace fftwlib_test
{
public class fftwtest
{
//pointers to unmanaged arrays
IntPtr pin, pout;
//managed arrays
float[] fin, fout;
//handles to managed arrays, keeps them pinned in memory
GCHandle hin, hout;
//pointers to the FFTW plan objects
IntPtr fplan1, fplan2, fplan3;
// Initializes FFTW and all arrays
// n: Logical size of the transform
public void InitFFTW(int n)
{
//create two unmanaged arrays, properly aligned
pin = fftwf.malloc(n*8);
pout = fftwf.malloc(n*8);
//create two managed arrays, possibly misalinged
//n*2 because we are dealing with complex numbers
fin = new float[n*2];
fout = new float[n*2];
//get handles and pin arrays so the GC doesn't move them
hin = GCHandle.Alloc(fin,GCHandleType.Pinned);
hout = GCHandle.Alloc(fout,GCHandleType.Pinned);
//fill our arrays with a sawtooth signal
for (int i=0;i<n*2;i++)
fin[i] = i%50;
for (int i=0;i<n*2;i++)
fout[i] = i%50;
//copy managed arrays to unmanaged arrays
Marshal.Copy(fin,0,pin,n*2);
Marshal.Copy(fout,0,pout,n*2);
//create a few test transforms
fplan1 = fftwf.dft_1d(n,pin,pout,fftw_direction.Forward,fftw_flags.Estimate);
fplan2 = fftwf.dft_1d(n,hin.AddrOfPinnedObject(),hout.AddrOfPinnedObject(),
fftw_direction.Forward,fftw_flags.Estimate);
fplan3 = fftwf.dft_1d(n,hout.AddrOfPinnedObject(),pin,
fftw_direction.Backward,fftw_flags.Measure);
}
public void TestAll()
{
TestPlan(fplan1);
TestPlan(fplan2);
TestPlan(fplan3);
}
// Tests a single plan, displaying results
//plan: Pointer to plan to test
public void TestPlan(IntPtr plan)
{
int start = System.Environment.TickCount;
for (int i=0;i<10000;i++)
fftwf.execute(plan);
Console.WriteLine("Time: {0} ms",
(System.Environment.TickCount-start));
//a: adds, b: muls, c: fmas
double a=0,b=0,c=0;
fftwf.flops(plan,ref a, ref b, ref c);
Console.WriteLine("Approx. flops: {0}",(a+b+2*c));
}
// Releases all memory used by FFTW/C#
public void FreeFFTW()
{
//it is essential that you call these after finishing
//may want to put the initializers in the constructor
//and these in the destructor
fftwf.free(pin);
fftwf.free(pout);
fftwf.destroy_plan(fplan1);
fftwf.destroy_plan(fplan2);
fftwf.destroy_plan(fplan3);
hin.Free();
hout.Free();
}
}
}
|
Download library (source included)
As always, questions, comments - let me know, and happy programming.
Oh, yeah, and given that I didn't really put any significant amount of work into this library, there aren't any
licensing issues to speak of. Use it for whatever you like, commercial or free, blah blah blah no guarantee not
liable for any damages all that stuff.
|