diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml new file mode 100644 index 0000000..781bf4b --- /dev/null +++ b/.github/workflows/dotnet.yml @@ -0,0 +1,29 @@ +# This workflow will build a .NET project + +name: .NET + +on: + push: + branches: [ "master", "develop" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Setup .NET + uses: actions/setup-dotnet@v3 + with: + dotnet-version: 6.0.x + - name: Install FFTW + run: sudo apt-get install libfftw3-bin + - name: Restore dependencies + run: dotnet restore + - name: Build + run: dotnet build --no-restore + - name: Test + run: dotnet test --no-build --verbosity normal diff --git a/SharpFFTW.Tests/Double/TestManaged.cs b/SharpFFTW.Tests/Double/TestManaged.cs index 9e1026e..66a9705 100644 --- a/SharpFFTW.Tests/Double/TestManaged.cs +++ b/SharpFFTW.Tests/Double/TestManaged.cs @@ -1,46 +1,34 @@  namespace SharpFFTW.Tests.Double { + using NUnit.Framework; using SharpFFTW; using SharpFFTW.Double; using System; + using System.Collections.Concurrent; + using System.Threading; + using System.Threading.Tasks; /// /// Test managed FFTW interface (1D). /// public class TestManaged { - /// - /// Run examples. - /// - /// Logical size of the transform. - public static void Run(int length) + [Test] + public void Test() { - Console.WriteLine("Starting managed test with FFT size = " + length + " (Type: double)"); - Console.WriteLine(); - - try - { - Example1(length); - Example2(length); - Example3(length); - } - catch (BadImageFormatException) - { - Util.Write("Couldn't load native FFTW image (Type: double)", false); - } - catch (Exception e) - { - Util.Write(e.Message, false); - } + const int SIZE = 8192; - Console.WriteLine(); + Assert.True(Example1(SIZE)); + Assert.True(Example2(SIZE)); + Assert.True(Example3(SIZE)); + Assert.True(Example4(2000, false)); } /// /// Complex to complex transform. /// - static void Example1(int length) + bool Example1(int length) { Console.Write("Test 1: complex transform ... "); @@ -69,13 +57,13 @@ static void Example1(int length) input.CopyTo(data); // Check and see how we did. - Util.CheckResults(length, length, data); + return Util.CheckResults(length, length, data); } /// /// Real to complex transform. /// - static void Example2(int length) + bool Example2(int length) { Console.Write("Test 2: real to complex transform ... "); @@ -103,13 +91,13 @@ static void Example2(int length) input.CopyTo(data); // Check and see how we did. - Util.CheckResults(n, n, data); + return Util.CheckResults(n, n, data); } /// /// Real to half-complex transform. /// - static void Example3(int length) + bool Example3(int length) { Console.Write("Test 3: real to half-complex transform ... "); @@ -137,7 +125,68 @@ static void Example3(int length) input.CopyTo(data); // Check and see how we did. - Util.CheckResults(n, n, data); + return Util.CheckResults(n, n, data); + } + + /// + /// Parallel execution. + /// + bool Example4(int tasks, bool print) + { + Console.Write("Test 4: parallel real to complex transform ... "); + + var plans = new ConcurrentDictionary>(); + + const int size = 4096; + + var result = Parallel.For(0, tasks, (i, state) => + { + int thread = Thread.CurrentThread.ManagedThreadId; + + var (input, _, plan1, plan2) = plans.GetOrAdd(thread, (i) => + { + var input = new RealArray(size); + var output = new ComplexArray(size / 2 + 1); + + var plan1 = Plan.Create1(size, input, output, Options.Estimate); + var plan2 = Plan.Create1(size, output, input, Options.Estimate); + + return new Tuple(input, output, plan1, plan2); + }); + + var data = Util.GenerateSignal(size); + + input.Set(data); + + plan1.Execute(); + plan2.Execute(); + + Array.Clear(data, 0, size); + + input.CopyTo(data); + + var success = Util.CheckResults(size, size, data); + + if (print) + { + Console.WriteLine($"{i,5}: current thread = {thread}, success = {success}"); + } + + if (!success) + { + state.Break(); + } + }); + + foreach (var (input, output, plan1, plan2) in plans.Values) + { + plan1.Dispose(); + plan2.Dispose(); + input.Dispose(); + output.Dispose(); + } + + return result.IsCompleted; } } } \ No newline at end of file diff --git a/SharpFFTW.Tests/Double/TestNative.cs b/SharpFFTW.Tests/Double/TestNative.cs index 8b96078..1cf3f6c 100644 --- a/SharpFFTW.Tests/Double/TestNative.cs +++ b/SharpFFTW.Tests/Double/TestNative.cs @@ -1,6 +1,7 @@  namespace SharpFFTW.Tests.Double { + using NUnit.Framework; using SharpFFTW; using SharpFFTW.Double; using System; @@ -11,33 +12,16 @@ namespace SharpFFTW.Tests.Double /// public class TestNative { - /// - /// Run examples. - /// - /// Logical size of the transform. - public static void Run(int length) + [Test] + public void Test() { - Console.WriteLine("Starting native test with FFT size = " + length + " (Type: double)"); - Console.WriteLine(); - - try - { - Example1(length); - Example2(length); - } - catch (BadImageFormatException) - { - Util.Write("Couldn't load native FFTW image (Type: double)", false); - } - catch (Exception e) - { - Util.Write(e.Message, false); - } - - Console.WriteLine(); + const int SIZE = 8192; + + Assert.True(Example1(SIZE)); + Assert.True(Example2(SIZE)); } - static void Example1(int length) + bool Example1(int length) { Console.Write("Test 1: complex transform ... "); @@ -73,16 +57,18 @@ static void Example1(int length) Marshal.Copy(pin, fin, 0, size); // Check and see how we did. - Util.CheckResults(length, length, fin); + bool success = Util.CheckResults(length, length, fin); // Don't forget to free the memory after finishing. NativeMethods.fftw_free(pin); NativeMethods.fftw_free(pout); NativeMethods.fftw_destroy_plan(plan1); NativeMethods.fftw_destroy_plan(plan2); + + return success; } - static void Example2(int length) + bool Example2(int length) { Console.Write("Test 2: complex transform ... "); @@ -116,7 +102,7 @@ static void Example2(int length) NativeMethods.fftw_execute(plan2); // Check and see how we did. - Util.CheckResults(length, length, fin); + bool success = Util.CheckResults(length, length, fin); // Don't forget to free the memory after finishing. NativeMethods.fftw_destroy_plan(plan1); @@ -124,6 +110,8 @@ static void Example2(int length) hin.Free(); hout.Free(); + + return success; } } } \ No newline at end of file diff --git a/SharpFFTW.Tests/Double/Util.cs b/SharpFFTW.Tests/Double/Util.cs index 853c180..cefedcf 100644 --- a/SharpFFTW.Tests/Double/Util.cs +++ b/SharpFFTW.Tests/Double/Util.cs @@ -16,7 +16,7 @@ public static double[] GenerateSignal(int n) return data; } - public static void CheckResults(int n, int scale, double[] data, double eps = 1e-3) + public static bool CheckResults(int n, int scale, double[] data, double eps = 1e-3) { for (int i = 0; i < n; i++) { @@ -26,14 +26,25 @@ public static void CheckResults(int n, int scale, double[] data, double eps = 1e // Check against original value. if (double.IsNaN(result) || Math.Abs(result - (i % 50)) > eps) { - Write("failed", false); - return; + return false; } } // Yeah, alright. So this was kind of a trivial test and of course // it's gonna work. But still. - Write("ok", true); + return true; + } + + public static void PrintResult(bool succsess) + { + if (succsess) + { + Write("ok", true); + } + else + { + Write("failed", false); + } } public static void Write(string message, bool ok) diff --git a/SharpFFTW.Tests/Program.cs b/SharpFFTW.Tests/Program.cs deleted file mode 100644 index fbb1c88..0000000 --- a/SharpFFTW.Tests/Program.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace SharpFFTW.Tests -{ - class Program - { - const int FFT_SIZE = 8192; - - static void Main(string[] args) - { - Double.TestNative.Run(FFT_SIZE); - Double.TestManaged.Run(FFT_SIZE); - - Single.TestNative.Run(FFT_SIZE); - Single.TestManaged.Run(FFT_SIZE); - - Console.WriteLine("\nDone. Press any key to exit."); - Console.ReadLine(); - } - } -} diff --git a/SharpFFTW.Tests/Setup.cs b/SharpFFTW.Tests/Setup.cs new file mode 100644 index 0000000..4d3dbc6 --- /dev/null +++ b/SharpFFTW.Tests/Setup.cs @@ -0,0 +1,14 @@ +using NUnit.Framework; + +namespace SharpFFTW.Tests +{ + [SetUpFixture] + public class Setup + { + [OneTimeSetUp] + public void GlobalSetup() + { + Library.SetImportResolver(); + } + } +} diff --git a/SharpFFTW.Tests/SharpFFTW.Tests.csproj b/SharpFFTW.Tests/SharpFFTW.Tests.csproj index 985dced..dbd76ed 100644 --- a/SharpFFTW.Tests/SharpFFTW.Tests.csproj +++ b/SharpFFTW.Tests/SharpFFTW.Tests.csproj @@ -1,17 +1,22 @@ - + net6.0 false + true AnyCPU;x64 - - Exe + + + + + + - \ No newline at end of file + diff --git a/SharpFFTW.Tests/Single/TestManaged.cs b/SharpFFTW.Tests/Single/TestManaged.cs index 709ec8a..6ca4484 100644 --- a/SharpFFTW.Tests/Single/TestManaged.cs +++ b/SharpFFTW.Tests/Single/TestManaged.cs @@ -1,46 +1,34 @@  namespace SharpFFTW.Tests.Single { + using NUnit.Framework; using SharpFFTW; using SharpFFTW.Single; using System; + using System.Collections.Concurrent; + using System.Threading; + using System.Threading.Tasks; /// /// Test managed FFTW interface (1D). /// public class TestManaged { - /// - /// Run examples. - /// - /// Logical size of the transform. - public static void Run(int length) + [Test] + public void Test() { - Console.WriteLine("Starting managed test with FFT size = " + length + " (Type: single)"); - Console.WriteLine(); - - try - { - Example1(length); - Example2(length); - Example3(length); - } - catch (BadImageFormatException) - { - Util.Write("Couldn't load native FFTW image (Type: single)", false); - } - catch (Exception e) - { - Util.Write(e.Message, false); - } + const int SIZE = 8192; - Console.WriteLine(); + Assert.True(Example1(SIZE)); + Assert.True(Example2(SIZE)); + Assert.True(Example3(SIZE)); + Assert.True(Example4(2000, false)); } /// /// Complex to complex transform. /// - static void Example1(int length) + bool Example1(int length) { Console.Write("Test 1: complex transform ... "); @@ -69,13 +57,13 @@ static void Example1(int length) input.CopyTo(data); // Check and see how we did. - Util.CheckResults(length, length, data); + return Util.CheckResults(length, length, data); } /// /// Real to complex transform. /// - static void Example2(int length) + bool Example2(int length) { Console.Write("Test 2: real to complex transform ... "); @@ -103,13 +91,13 @@ static void Example2(int length) input.CopyTo(data); // Check and see how we did. - Util.CheckResults(n, n, data); + return Util.CheckResults(n, n, data); } /// /// Real to half-complex transform. /// - static void Example3(int length) + bool Example3(int length) { Console.Write("Test 3: real to half-complex transform ... "); @@ -137,7 +125,68 @@ static void Example3(int length) input.CopyTo(data); // Check and see how we did. - Util.CheckResults(n, n, data); + return Util.CheckResults(n, n, data); + } + + /// + /// Parallel execution. + /// + bool Example4(int tasks, bool print) + { + Console.Write("Test 4: parallel real to complex transform ... "); + + var plans = new ConcurrentDictionary>(); + + const int size = 4096; + + var result = Parallel.For(0, tasks, (i, state) => + { + int thread = Thread.CurrentThread.ManagedThreadId; + + var (input, output, plan1, plan2) = plans.GetOrAdd(thread, (i) => + { + var input = new RealArray(size); + var output = new ComplexArray(size / 2 + 1); + + var plan1 = Plan.Create1(size, input, output, Options.Estimate); + var plan2 = Plan.Create1(size, output, input, Options.Estimate); + + return new Tuple(input, output, plan1, plan2); + }); + + var data = Util.GenerateSignal(size); + + input.Set(data); + + plan1.Execute(); + plan2.Execute(); + + Array.Clear(data, 0, size); + + input.CopyTo(data); + + var success = Util.CheckResults(size, size, data); + + if (print) + { + Console.WriteLine($"{i,5}: current thread = {thread}, success = {success}"); + } + + if (!success) + { + state.Break(); + } + }); + + foreach (var (input, output, plan1, plan2) in plans.Values) + { + plan1.Dispose(); + plan2.Dispose(); + input.Dispose(); + output.Dispose(); + } + + return result.IsCompleted; } } } \ No newline at end of file diff --git a/SharpFFTW.Tests/Single/TestNative.cs b/SharpFFTW.Tests/Single/TestNative.cs index 01930e6..694354e 100644 --- a/SharpFFTW.Tests/Single/TestNative.cs +++ b/SharpFFTW.Tests/Single/TestNative.cs @@ -1,6 +1,7 @@  namespace SharpFFTW.Tests.Single { + using NUnit.Framework; using SharpFFTW; using SharpFFTW.Single; using System; @@ -11,33 +12,16 @@ namespace SharpFFTW.Tests.Single /// public class TestNative { - /// - /// Run examples. - /// - /// Logical size of the transform. - public static void Run(int length) + [Test] + public void Test() { - Console.WriteLine("Starting native test with FFT size = " + length + " (Type: single)"); - Console.WriteLine(); - - try - { - Example1(length); - Example2(length); - } - catch (BadImageFormatException) - { - Util.Write("Couldn't load native FFTW image (Type: single)", false); - } - catch (Exception e) - { - Util.Write(e.Message, false); - } - - Console.WriteLine(); + const int SIZE = 8192; + + Assert.True(Example1(SIZE)); + Assert.True(Example2(SIZE)); } - static void Example1(int length) + bool Example1(int length) { Console.Write("Test 1: complex transform ... "); @@ -73,16 +57,18 @@ static void Example1(int length) Marshal.Copy(pin, fin, 0, size); // Check and see how we did. - Util.CheckResults(length, length, fin); + bool success = Util.CheckResults(length, length, fin); // Don't forget to free the memory after finishing. NativeMethods.fftwf_free(pin); NativeMethods.fftwf_free(pout); NativeMethods.fftwf_destroy_plan(plan1); NativeMethods.fftwf_destroy_plan(plan2); + + return success; } - static void Example2(int length) + bool Example2(int length) { Console.Write("Test 2: complex transform ... "); @@ -116,7 +102,7 @@ static void Example2(int length) NativeMethods.fftwf_execute(plan2); // Check and see how we did. - Util.CheckResults(length, length, fin); + bool success = Util.CheckResults(length, length, fin); // Don't forget to free the memory after finishing. NativeMethods.fftwf_destroy_plan(plan1); @@ -124,6 +110,8 @@ static void Example2(int length) hin.Free(); hout.Free(); + + return success; } } } \ No newline at end of file diff --git a/SharpFFTW.Tests/Single/Util.cs b/SharpFFTW.Tests/Single/Util.cs index f4f1aa2..efd8748 100644 --- a/SharpFFTW.Tests/Single/Util.cs +++ b/SharpFFTW.Tests/Single/Util.cs @@ -16,7 +16,7 @@ public static float[] GenerateSignal(int n) return data; } - public static void CheckResults(int n, int scale, float[] data, double eps = 1e-3) + public static bool CheckResults(int n, int scale, float[] data, float eps = 1e-3f) { for (int i = 0; i < n; i++) { @@ -24,16 +24,27 @@ public static void CheckResults(int n, int scale, float[] data, double eps = 1e- float result = data[i] / scale; // Check against original value. - if (double.IsNaN(result) || Math.Abs(result - (i % 50)) > eps) + if (float.IsNaN(result) || Math.Abs(result - (i % 50)) > eps) { - Write("failed", false); - return; + return false; } } // Yeah, alright. So this was kind of a trivial test and of course // it's gonna work. But still. - Write("ok", true); + return true; + } + + public static void PrintResult(bool succsess) + { + if (succsess) + { + Write("ok", true); + } + else + { + Write("failed", false); + } } public static void Write(string message, bool ok) diff --git a/SharpFFTW.sln b/SharpFFTW.sln index 0aa906d..9beb613 100644 --- a/SharpFFTW.sln +++ b/SharpFFTW.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28917.181 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33110.190 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharpFFTW.Tests", "SharpFFTW.Tests\SharpFFTW.Tests.csproj", "{DA20704B-23A7-41A4-B4E2-174A0FED5F3D}" ProjectSection(ProjectDependencies) = postProject @@ -32,7 +32,9 @@ Global EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {DA20704B-23A7-41A4-B4E2-174A0FED5F3D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA20704B-23A7-41A4-B4E2-174A0FED5F3D}.Debug|Any CPU.Build.0 = Debug|Any CPU {DA20704B-23A7-41A4-B4E2-174A0FED5F3D}.Debug|x64.ActiveCfg = Debug|x64 + {DA20704B-23A7-41A4-B4E2-174A0FED5F3D}.Debug|x64.Build.0 = Debug|x64 {DA20704B-23A7-41A4-B4E2-174A0FED5F3D}.Release|Any CPU.ActiveCfg = Release|x86 {DA20704B-23A7-41A4-B4E2-174A0FED5F3D}.Release|x64.ActiveCfg = Release|x64 {BEA875B8-E28A-49C5-8E7E-6512DA65F7E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/SharpFFTW/AbstractArray.cs b/SharpFFTW/AbstractArray.cs index f9a817a..8f87bb6 100644 --- a/SharpFFTW/AbstractArray.cs +++ b/SharpFFTW/AbstractArray.cs @@ -2,11 +2,12 @@ namespace SharpFFTW { using System; - using System.Runtime.InteropServices; + using System.Threading; /// /// Abstract base class for native FFTW arrays. /// + /// Supported types are and . public abstract class AbstractArray : IDisposable where T : struct, IEquatable, IFormattable { @@ -24,18 +25,24 @@ public abstract class AbstractArray : IDisposable private T[] data; /// - /// Creates a new array of complex numbers. + /// Creates a new . /// /// Logical length of the array. public AbstractArray(int length) { - this.Length = length; + Length = length; } /// - /// Copy contents of given array to native memory. + /// Copy items from the given array + /// to native memory. /// /// The data to copy. + /// + /// For real valued data the array length must be at + /// least , for complex valued data the length must be at + /// least 2 * Length. + /// public abstract void Set(T[] source); /// @@ -57,20 +64,33 @@ public AbstractArray(int length) #region IDisposable implementation - protected bool hasDisposed = false; + protected int _disposeCount; /// public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); + if (Interlocked.Increment(ref _disposeCount) == 1) + { + Dispose(true); + GC.SuppressFinalize(this); + } } + /// + /// Performs application-defined tasks associated with disposing of resources. + /// + /// Indicates whether the method call comes from a Dispose method + /// (value is true) or from a finalizer (value is false). + /// + /// Override this method in derived classes. Unmanaged resources should always be + /// released when this method is called. Managed resources may only be disposed + /// of if is true. + /// public abstract void Dispose(bool disposing); ~AbstractArray() { - Dispose(true); + Dispose(false); } #endregion diff --git a/SharpFFTW/AbstractPlan.cs b/SharpFFTW/AbstractPlan.cs index 16fc05e..4ead4de 100644 --- a/SharpFFTW/AbstractPlan.cs +++ b/SharpFFTW/AbstractPlan.cs @@ -59,20 +59,33 @@ public AbstractArray Output #region IDisposable implementation - protected bool hasDisposed = false; + protected int _disposeCount; /// public void Dispose() { - Dispose(true); - GC.SuppressFinalize(this); + if (Interlocked.Increment(ref _disposeCount) == 1) + { + Dispose(true); + GC.SuppressFinalize(this); + } } + /// + /// Performs application-defined tasks associated with disposing of resources. + /// + /// Indicates whether the method call comes from a Dispose method + /// (value is true) or from a finalizer (value is false). + /// + /// Override this method in derived classes. Unmanaged resources should always be + /// released when this method is called. Managed resources may only be disposed + /// of if is true. + /// public abstract void Dispose(bool disposing); ~AbstractPlan() { - Dispose(true); + Dispose(false); } #endregion diff --git a/SharpFFTW/Double/ComplexArray.cs b/SharpFFTW/Double/ComplexArray.cs index 1005edc..578c1e0 100644 --- a/SharpFFTW/Double/ComplexArray.cs +++ b/SharpFFTW/Double/ComplexArray.cs @@ -22,7 +22,7 @@ public class ComplexArray : AbstractArray public ComplexArray(int length) : base(length) { - Handle = NativeMethods.fftw_malloc(this.Length * SIZE); + Handle = NativeMethods.fftw_malloc(Length * SIZE); } /// @@ -32,7 +32,7 @@ public ComplexArray(int length) public ComplexArray(double[] data) : this(data.Length / 2) { - this.Set(data); + Set(data); } /// @@ -42,22 +42,17 @@ public ComplexArray(double[] data) public ComplexArray(Complex[] data) : this(data.Length) { - this.Set(data); + Set(data); } /// public override void Dispose(bool disposing) { - if (!hasDisposed) + if (Handle != IntPtr.Zero) { - if (Handle != IntPtr.Zero) - { - NativeMethods.fftw_free(Handle); - Handle = IntPtr.Zero; - } + NativeMethods.fftw_free(Handle); + Handle = IntPtr.Zero; } - - hasDisposed = disposing; } /// @@ -68,9 +63,9 @@ public override void Set(double[] source) { int size = 2 * Length; - if (source.Length != size) + if (source.Length < size) { - throw new ArgumentException("Array length mismatch."); + throw new ArgumentException("Array length mismatch.", nameof(source)); } Marshal.Copy(source, 0, Handle, size); @@ -82,9 +77,9 @@ public override void Set(double[] source) /// Array of complex numbers. public void Set(Complex[] source) { - if (source.Length != this.Length) + if (source.Length != Length) { - throw new ArgumentException("Array length mismatch."); + throw new ArgumentException("Array length mismatch.", nameof(source)); } var temp = GetTemporaryData(2 * Length); @@ -95,19 +90,17 @@ public void Set(Complex[] source) temp[2 * i + 1] = source[i].Imaginary; } - Marshal.Copy(temp, 0, Handle, this.Length * 2); + Marshal.Copy(temp, 0, Handle, Length * 2); } - /// - /// Set the data to zeros. - /// + /// public override void Clear() { var temp = GetTemporaryData(2 * Length); Array.Clear(temp, 0, temp.Length); - Marshal.Copy(temp, 0, Handle, this.Length * 2); + Marshal.Copy(temp, 0, Handle, Length * 2); } /// @@ -116,9 +109,9 @@ public override void Clear() /// Array of complex numbers. public void CopyTo(Complex[] target) { - if (target.Length != this.Length) + if (target.Length < Length) { - throw new Exception(); + throw new ArgumentException("Array length mismatch.", nameof(target)); } var temp = GetTemporaryData(2 * Length); @@ -141,7 +134,7 @@ public override void CopyTo(double[] target) if (target.Length < size) { - throw new Exception(); + throw new ArgumentException("Array length mismatch.", nameof(target)); } Marshal.Copy(Handle, target, 0, size); diff --git a/SharpFFTW/Double/Plan.cs b/SharpFFTW/Double/Plan.cs index da53130..73e0896 100644 --- a/SharpFFTW/Double/Plan.cs +++ b/SharpFFTW/Double/Plan.cs @@ -53,22 +53,19 @@ public override string ToString() /// public override void Dispose(bool disposing) { - if (!hasDisposed) + if (handle != IntPtr.Zero) { - if (handle != IntPtr.Zero) - { - NativeMethods.fftw_destroy_plan(handle); - handle = IntPtr.Zero; - } - - if (ownsArrays) - { - input.Dispose(); - output.Dispose(); - } + mutex.WaitOne(); + NativeMethods.fftw_destroy_plan(handle); + handle = IntPtr.Zero; + mutex.ReleaseMutex(); } - hasDisposed = disposing; + if (disposing && ownsArrays) + { + input.Dispose(); + output.Dispose(); + } } #region 1D plan creation diff --git a/SharpFFTW/Double/RealArray.cs b/SharpFFTW/Double/RealArray.cs index 3b0ec87..6b512a7 100644 --- a/SharpFFTW/Double/RealArray.cs +++ b/SharpFFTW/Double/RealArray.cs @@ -21,7 +21,7 @@ public class RealArray : AbstractArray public RealArray(int length) : base(length) { - Handle = NativeMethods.fftw_malloc(this.Length * SIZE); + Handle = NativeMethods.fftw_malloc(Length * SIZE); } /// @@ -31,50 +31,40 @@ public RealArray(int length) public RealArray(double[] data) : this(data.Length) { - this.Set(data); + Set(data); } /// public override void Dispose(bool disposing) { - if (!hasDisposed) + if (Handle != IntPtr.Zero) { - if (Handle != IntPtr.Zero) - { - NativeMethods.fftw_free(Handle); - Handle = IntPtr.Zero; - } + NativeMethods.fftw_free(Handle); + Handle = IntPtr.Zero; } - - hasDisposed = disposing; } - /// - /// Set the data to an array of doubles. - /// - /// Array of doubles, alternating real and imaginary. + /// public override void Set(double[] source) { int size = Length; - if (source.Length != size) + if (source.Length < size) { - throw new ArgumentException("Array length mismatch."); + throw new ArgumentException("Array length mismatch.", nameof(source)); } Marshal.Copy(source, 0, Handle, size); } - /// - /// Set the data to zeros. - /// + /// public override void Clear() { var temp = GetTemporaryData(Length); Array.Clear(temp, 0, temp.Length); - Marshal.Copy(temp, 0, Handle, this.Length); + Marshal.Copy(temp, 0, Handle, Length); } /// @@ -85,9 +75,9 @@ public override void CopyTo(double[] target) { int size = Length; - if (target.Length != size) + if (target.Length < size) { - throw new Exception(); + throw new ArgumentException("Array length mismatch.", nameof(target)); } Marshal.Copy(Handle, target, 0, size); diff --git a/SharpFFTW/Library.cs b/SharpFFTW/Library.cs new file mode 100644 index 0000000..e475e3b --- /dev/null +++ b/SharpFFTW/Library.cs @@ -0,0 +1,28 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +namespace SharpFFTW +{ +#if NET5_0_OR_GREATER + public class Library + { + public static void SetImportResolver() + { + NativeLibrary.SetDllImportResolver(Assembly.GetExecutingAssembly(), DllImportResolver); + } + + private static IntPtr DllImportResolver(string library, Assembly assembly, DllImportSearchPath? searchPath) + { + // Trying to find FFTW on Ubuntu. + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && library.StartsWith("fftw")) + { + return NativeLibrary.Load("lib" + library + ".so.3", assembly, searchPath); + } + + // Otherwise, fall back to default import resolver. + return IntPtr.Zero; + } + } +#endif +} diff --git a/SharpFFTW/Single/ComplexArray.cs b/SharpFFTW/Single/ComplexArray.cs index 094c1ca..7873a09 100644 --- a/SharpFFTW/Single/ComplexArray.cs +++ b/SharpFFTW/Single/ComplexArray.cs @@ -21,7 +21,7 @@ public class ComplexArray : AbstractArray public ComplexArray(int length) : base(length) { - Handle = NativeMethods.fftwf_malloc(this.Length * SIZE); + Handle = NativeMethods.fftwf_malloc(Length * SIZE); } /// @@ -31,7 +31,7 @@ public ComplexArray(int length) public ComplexArray(float[] data) : this(data.Length / 2) { - this.Set(data); + Set(data); } /// @@ -41,22 +41,17 @@ public ComplexArray(float[] data) public ComplexArray(Complex32[] data) : this(data.Length) { - this.Set(data); + Set(data); } /// public override void Dispose(bool disposing) { - if (!hasDisposed) + if (Handle != IntPtr.Zero) { - if (Handle != IntPtr.Zero) - { - NativeMethods.fftwf_free(Handle); - Handle = IntPtr.Zero; - } + NativeMethods.fftwf_free(Handle); + Handle = IntPtr.Zero; } - - hasDisposed = disposing; } /// @@ -67,9 +62,9 @@ public override void Set(float[] source) { int size = 2 * Length; - if (source.Length != size) + if (source.Length < size) { - throw new ArgumentException("Array length mismatch."); + throw new ArgumentException("Array length mismatch.", nameof(source)); } Marshal.Copy(source, 0, Handle, size); @@ -81,9 +76,9 @@ public override void Set(float[] source) /// Array of complex numbers. public void Set(Complex32[] source) { - if (source.Length != this.Length) + if (source.Length < Length) { - throw new ArgumentException("Array length mismatch."); + throw new ArgumentException("Array length mismatch.", nameof(source)); } var temp = GetTemporaryData(2 * Length); @@ -94,19 +89,17 @@ public void Set(Complex32[] source) temp[2 * i + 1] = source[i].Imaginary; } - Marshal.Copy(temp, 0, Handle, this.Length * 2); + Marshal.Copy(temp, 0, Handle, Length * 2); } - /// - /// Set the data to zeros. - /// + /// public override void Clear() { var temp = GetTemporaryData(2 * Length); Array.Clear(temp, 0, temp.Length); - Marshal.Copy(temp, 0, Handle, this.Length * 2); + Marshal.Copy(temp, 0, Handle, Length * 2); } /// @@ -115,9 +108,9 @@ public override void Clear() /// Array of complex numbers. public void CopyTo(Complex32[] target) { - if (target.Length != this.Length) + if (target.Length < Length) { - throw new Exception(); + throw new ArgumentException("Array length mismatch.", nameof(target)); } var temp = GetTemporaryData(2 * Length); @@ -138,9 +131,9 @@ public override void CopyTo(float[] target) { int size = 2 * Length; - if (target.Length != size) + if (target.Length < size) { - throw new Exception(); + throw new ArgumentException("Array length mismatch.", nameof(target)); } Marshal.Copy(Handle, target, 0, size); diff --git a/SharpFFTW/Single/Plan.cs b/SharpFFTW/Single/Plan.cs index 45b1f50..2d5b39d 100644 --- a/SharpFFTW/Single/Plan.cs +++ b/SharpFFTW/Single/Plan.cs @@ -46,29 +46,26 @@ public override string ToString() { // NOTE: this leaks native memory, since the returned char* pointer isn't free'd. var p = NativeMethods.fftwf_sprint_plan(handle); - + return Marshal.PtrToStringAnsi(p); } /// public override void Dispose(bool disposing) { - if (!hasDisposed) + if (handle != IntPtr.Zero) { - if (handle != IntPtr.Zero) - { - NativeMethods.fftwf_destroy_plan(handle); - handle = IntPtr.Zero; - } - - if (ownsArrays) - { - input.Dispose(); - output.Dispose(); - } + mutex.WaitOne(); + NativeMethods.fftwf_destroy_plan(handle); + handle = IntPtr.Zero; + mutex.ReleaseMutex(); } - hasDisposed = disposing; + if (disposing && ownsArrays) + { + input.Dispose(); + output.Dispose(); + } } #region 1D plan creation diff --git a/SharpFFTW/Single/RealArray.cs b/SharpFFTW/Single/RealArray.cs index abce9e8..be48448 100644 --- a/SharpFFTW/Single/RealArray.cs +++ b/SharpFFTW/Single/RealArray.cs @@ -21,7 +21,7 @@ public class RealArray : AbstractArray public RealArray(int length) : base(length) { - Handle = NativeMethods.fftwf_malloc(this.Length * SIZE); + Handle = NativeMethods.fftwf_malloc(Length * SIZE); } /// @@ -31,50 +31,40 @@ public RealArray(int length) public RealArray(float[] data) : this(data.Length) { - this.Set(data); + Set(data); } /// public override void Dispose(bool disposing) { - if (!hasDisposed) + if (Handle != IntPtr.Zero) { - if (Handle != IntPtr.Zero) - { - NativeMethods.fftwf_free(Handle); - Handle = IntPtr.Zero; - } + NativeMethods.fftwf_free(Handle); + Handle = IntPtr.Zero; } - - hasDisposed = disposing; } - /// - /// Set the data to an array of floats. - /// - /// Array of 4-byte floating point numbers. + /// public override void Set(float[] source) { int size = Length; - if (source.Length != size) + if (source.Length < size) { - throw new ArgumentException("Array length mismatch."); + throw new ArgumentException("Array length mismatch.", nameof(source)); } Marshal.Copy(source, 0, Handle, size); } - /// - /// Set the data to zeros. - /// + /// public override void Clear() { var temp = GetTemporaryData(Length); Array.Clear(temp, 0, temp.Length); - Marshal.Copy(temp, 0, Handle, this.Length); + Marshal.Copy(temp, 0, Handle, Length); } /// @@ -85,9 +75,9 @@ public override void CopyTo(float[] target) { int size = Length; - if (target.Length != size) + if (target.Length < size) { - throw new Exception(); + throw new ArgumentException("Array length mismatch.", nameof(target)); } Marshal.Copy(Handle, target, 0, size); diff --git a/fftbench/fftbench.Common/fftbench.Common.csproj b/fftbench/fftbench.Common/fftbench.Common.csproj index 0e1ec43..6327ab2 100644 --- a/fftbench/fftbench.Common/fftbench.Common.csproj +++ b/fftbench/fftbench.Common/fftbench.Common.csproj @@ -15,7 +15,7 @@ - +