From 0effa70a8a0173b02606962eea3e0cfe57318cea Mon Sep 17 00:00:00 2001 From: Lane Date: Sun, 26 Nov 2023 23:51:01 -0600 Subject: [PATCH 1/4] ignore build step for proper CI builds --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 6565635..983b9aa 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ build: docs: ProcessedREADME.md doxygen -pack: ProcessedREADME.md build +pack: ProcessedREADME.md $(DN) pack -c $(BUILD) /p:ContinuousIntegrationBuild=true DotMP clean: From 23e8e2810c137e2237bd5792964b87c7e179ac87 Mon Sep 17 00:00:00 2001 From: Lane Date: Sun, 26 Nov 2023 23:51:31 -0600 Subject: [PATCH 2/4] compatibility with .NET Standard 2.1 and .NET Framework 4.7.1 --- DotMP-Tests/DotMP-Tests.csproj | 2 +- DotMP-Tests/ParallelTests.cs | 26 ++++++++++++---------- DotMP/Atomic.cs | 40 ++++++++++++++++++++++++++++++++++ DotMP/DependencyGraph.cs | 11 ++++------ DotMP/DotMP.csproj | 7 +++++- DotMP/ForkedRegion.cs | 9 ++++++-- DotMP/Parallel.cs | 30 +++++++++++++++---------- DotMP/Schedule.cs | 24 ++++++++++++++++---- DotMP/Tasking.cs | 2 +- 9 files changed, 112 insertions(+), 39 deletions(-) diff --git a/DotMP-Tests/DotMP-Tests.csproj b/DotMP-Tests/DotMP-Tests.csproj index 5d02965..3cb05b2 100644 --- a/DotMP-Tests/DotMP-Tests.csproj +++ b/DotMP-Tests/DotMP-Tests.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + net471;net6.0;net7.0;net8.0 diff --git a/DotMP-Tests/ParallelTests.cs b/DotMP-Tests/ParallelTests.cs index 4f38d36..cae40d9 100644 --- a/DotMP-Tests/ParallelTests.cs +++ b/DotMP-Tests/ParallelTests.cs @@ -292,10 +292,10 @@ public void Reduction_collapse_works() DotMP.Parallel.ParallelForReductionCollapse((256, 512), (512, 600), op: Operations.Add, reduce_to: ref total_iters_executed, num_threads: 8, chunk_size: 7, schedule: Schedule.Static, - action: (ref int total_iters_executed, int i, int j) => + action: (ref int total_iters_executed_loc, int i, int j) => { DotMP.Atomic.Inc(ref iters_hit[i, j]); - total_iters_executed += 1; + total_iters_executed_loc += 1; }); for (int i = 0; i < 1024; i++) @@ -315,10 +315,10 @@ public void Reduction_collapse_works() DotMP.Parallel.ParallelForReductionCollapse((35, 64), (16, 100), (10, 62), op: Operations.Add, reduce_to: ref total_iters_executed, num_threads: 8, chunk_size: 3, schedule: Schedule.Dynamic, - action: (ref int total_iters_executed, int i, int j, int k) => + action: (ref int total_iters_executed_loc, int i, int j, int k) => { DotMP.Atomic.Inc(ref iters_hit_3[i, j, k]); - total_iters_executed += 1; + total_iters_executed_loc += 1; }); for (int i = 0; i < 128; i++) @@ -339,10 +339,10 @@ public void Reduction_collapse_works() DotMP.Parallel.ParallelForReductionCollapse((1, 31), (10, 16), (5, 20), (21, 30), op: Operations.Add, reduce_to: ref total_iters_executed, num_threads: 8, chunk_size: 11, schedule: Schedule.Static, - action: (ref int total_iters_executed, int i, int j, int k, int l) => + action: (ref int total_iters_executed_loc, int i, int j, int k, int l) => { DotMP.Atomic.Inc(ref iters_hit_4[i, j, k, l]); - total_iters_executed += 1; + total_iters_executed_loc += 1; }); total_iters_executed.Should().Be((31 - 1) * (16 - 10) * (20 - 5) * (30 - 21)); @@ -351,10 +351,10 @@ public void Reduction_collapse_works() DotMP.Parallel.ParallelForReductionCollapse(new (int, int)[] { (1, 31), (10, 16), (5, 20), (21, 30) }, op: Operations.Add, reduce_to: ref total_iters_executed, num_threads: 8, chunk_size: 11, schedule: Schedule.Static, - action: (ref int total_iters_executed, int[] indices) => + action: (ref int total_iters_executed_loc, int[] indices) => { DotMP.Atomic.Inc(ref iters_hit_4[indices[0], indices[1], indices[2], indices[3]]); - total_iters_executed += 1; + total_iters_executed_loc += 1; }); total_iters_executed.Should().Be((31 - 1) * (16 - 10) * (20 - 5) * (30 - 21)); @@ -664,6 +664,7 @@ public void Atomic_works() long_totals[2].Should().Be((long)threads * 2); ulong_totals[2].Should().Be((ulong)threads * 2); +#if NET6_0_OR_GREATER //and DotMP.Parallel.ParallelRegion(num_threads: threads, action: () => { @@ -713,6 +714,7 @@ public void Atomic_works() uint_totals[4].Should().Be((uint)res); long_totals[4].Should().Be((long)res); ulong_totals[4].Should().Be((ulong)res); +#endif uint_totals[5] = threads * 2 + 1; ulong_totals[5] = threads * 2 + 3; @@ -767,14 +769,14 @@ public void Ordered_works() [Fact] public void Reduction_works() { - int total = 0; + int total_int = 0; - DotMP.Parallel.ParallelForReduction(0, 1024, DotMP.Operations.Add, ref total, num_threads: 8, chunk_size: 1, schedule: DotMP.Schedule.Static, action: (ref int total, int i) => + DotMP.Parallel.ParallelForReduction(0, 1024, DotMP.Operations.Add, ref total_int, num_threads: 8, chunk_size: 1, schedule: DotMP.Schedule.Static, action: (ref int total, int i) => { total += i; }); - total.Should().Be(1024 * 1023 / 2); + total_int.Should().Be(1024 * 1023 / 2); long total_long = 0; @@ -1692,7 +1694,7 @@ private static uint CreateRegion() DotMP.Parallel.ParallelRegion(() => { - Interlocked.Add(ref threads_spawned, 1); + DotMP.Atomic.Inc(ref threads_spawned); }); return threads_spawned; diff --git a/DotMP/Atomic.cs b/DotMP/Atomic.cs index 7c7648f..a01c149 100644 --- a/DotMP/Atomic.cs +++ b/DotMP/Atomic.cs @@ -70,6 +70,7 @@ public static int Dec(ref int target) return Interlocked.Decrement(ref target); } +#if NET6_0_OR_GREATER /// /// Performs a bitwise And operation on two specified 32-bit integers and replaces the first integer with the result, as an atomic operation. /// @@ -91,6 +92,7 @@ public static int Or(ref int target, int value) { return Interlocked.Or(ref target, value); } +#endif #endregion #region UInt32 @@ -102,7 +104,11 @@ public static int Or(ref int target, int value) /// The new value stored as a result of the operation. public static uint Add(ref uint target, uint value) { +#if NET6_0_OR_GREATER return Interlocked.Add(ref target, value); +#else + return unchecked((uint)Interlocked.Add(ref Unsafe.As(ref target), Unsafe.As(ref value))); +#endif } /// @@ -114,7 +120,11 @@ public static uint Add(ref uint target, uint value) public static uint Sub(ref uint target, uint value) { uint neg = ~value + 1; +#if NET6_0_OR_GREATER return Interlocked.Add(ref target, neg); +#else + return unchecked((uint)Interlocked.Add(ref Unsafe.As(ref target), Unsafe.As(ref neg))); +#endif } /// @@ -124,7 +134,11 @@ public static uint Sub(ref uint target, uint value) /// The new value stored as a result of the operation. public static uint Inc(ref uint target) { +#if NET6_0_OR_GREATER return Interlocked.Increment(ref target); +#else + return unchecked((uint)Interlocked.Increment(ref Unsafe.As(ref target))); +#endif } /// @@ -134,9 +148,14 @@ public static uint Inc(ref uint target) /// The new value stored as a result of the operation. public static uint Dec(ref uint target) { +#if NET6_0_OR_GREATER return Interlocked.Decrement(ref target); +#else + return unchecked((uint)Interlocked.Decrement(ref Unsafe.As(ref target))); +#endif } +#if NET6_0_OR_GREATER /// /// Performs a bitwise And operation on two specified 32-bit unsigned integers and replaces the first integer with the result, as an atomic operation. /// @@ -158,6 +177,7 @@ public static uint Or(ref uint target, uint value) { return Interlocked.Or(ref target, value); } +#endif #endregion #region Int64 @@ -203,6 +223,7 @@ public static long Dec(ref long target) return Interlocked.Decrement(ref target); } +#if NET6_0_OR_GREATER /// /// Performs a bitwise And operation on two specified 64-bit integers and replaces the first integer with the result, as an atomic operation. /// @@ -224,6 +245,7 @@ public static long Or(ref long target, long value) { return Interlocked.Or(ref target, value); } +#endif #endregion #region UInt64 @@ -235,7 +257,11 @@ public static long Or(ref long target, long value) /// The new value stored as a result of the operation. public static ulong Add(ref ulong target, ulong value) { +#if NET6_0_OR_GREATER return Interlocked.Add(ref target, value); +#else + return unchecked((ulong)Interlocked.Add(ref Unsafe.As(ref target), Unsafe.As(ref value))); +#endif } /// @@ -247,7 +273,11 @@ public static ulong Add(ref ulong target, ulong value) public static ulong Sub(ref ulong target, ulong value) { ulong neg = ~value + 1; +#if NET6_0_OR_GREATER return Interlocked.Add(ref target, neg); +#else + return unchecked((ulong)Interlocked.Add(ref Unsafe.As(ref target), Unsafe.As(ref neg))); +#endif } /// @@ -257,7 +287,11 @@ public static ulong Sub(ref ulong target, ulong value) /// The new value stored as a result of the operation. public static ulong Inc(ref ulong target) { +#if NET6_0_OR_GREATER return Interlocked.Increment(ref target); +#else + return unchecked((ulong)Interlocked.Increment(ref Unsafe.As(ref target))); +#endif } /// @@ -267,9 +301,14 @@ public static ulong Inc(ref ulong target) /// The new value stored as a result of the operation. public static ulong Dec(ref ulong target) { +#if NET6_0_OR_GREATER return Interlocked.Decrement(ref target); +#else + return unchecked((ulong)Interlocked.Decrement(ref Unsafe.As(ref target))); +#endif } +#if NET6_0_OR_GREATER /// /// Performs a bitwise And operation on two specified 64-bit unsigned integers and replaces the first integer with the result, as an atomic operation. /// @@ -291,6 +330,7 @@ public static ulong Or(ref ulong target, ulong value) { return Interlocked.Or(ref target, value); } +#endif #endregion } } diff --git a/DotMP/DependencyGraph.cs b/DotMP/DependencyGraph.cs index 47f3a48..b6c5f75 100644 --- a/DotMP/DependencyGraph.cs +++ b/DotMP/DependencyGraph.cs @@ -19,8 +19,6 @@ using System.Collections.Generic; using System.Threading; -#nullable enable - namespace DotMP { /// @@ -48,7 +46,6 @@ internal IntWrapper(int @int) /// internal class DAG : IDisposable where T : struct - where U : class? { /// /// Counter for remaining tasks in queue. @@ -113,9 +110,9 @@ internal void AddItem(T id, U item, T[] dependencies) else satisfies_dependency[d].Add(id); - unmet_dependencies.TryAdd(id, new IntWrapper(dependency_count)); + unmet_dependencies.Add(id, new IntWrapper(dependency_count)); - satisfies_dependency.TryAdd(id, new List()); + satisfies_dependency.Add(id, new List()); if (dependency_count == 0) no_dependencies.Add(id); @@ -130,7 +127,7 @@ internal void AddItem(T id, U item, T[] dependencies) /// The ID of the item returned from the DAG. /// The number of tasks remaining in the queue. /// Whether or not there was an item to be returned. - internal bool GetNextItem(out U? item, out T id, out int tasks_remaining) + internal bool GetNextItem(out U item, out T id, out int tasks_remaining) { tasks_remaining = this.tasks_remaining; @@ -140,7 +137,7 @@ internal bool GetNextItem(out U? item, out T id, out int tasks_remaining) } else { - item = null; + item = default; return false; } } diff --git a/DotMP/DotMP.csproj b/DotMP/DotMP.csproj index e8cc69f..8978569 100644 --- a/DotMP/DotMP.csproj +++ b/DotMP/DotMP.csproj @@ -1,7 +1,7 @@ - net6.0;net7.0;net8.0 + netstandard2.1;net471;net6.0;net7.0;net8.0 DotMP DotMP 1.6.0 @@ -23,4 +23,9 @@ + + + + + diff --git a/DotMP/ForkedRegion.cs b/DotMP/ForkedRegion.cs index f07f909..29d9015 100644 --- a/DotMP/ForkedRegion.cs +++ b/DotMP/ForkedRegion.cs @@ -64,10 +64,15 @@ internal Thread CreateThread(Action omp_fn, int tid, uint num_threads) } catch (Exception ex) { +#if NET6_0_OR_GREATER this.ex ??= ex; +#else + if (this.ex == null) + this.ex = ex; +#endif Parallel.canceled = true; - if (ex is not ThreadInterruptedException) + if (!(ex is ThreadInterruptedException)) for (int i = 0; i < num_threads; i++) if (i != tid) threads[i].Interrupt(); @@ -182,7 +187,7 @@ internal void StartThreadpool() in_parallel = false; - if (reg.ex is not null) + if (reg.ex != null) System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(reg.ex).Throw(); } } diff --git a/DotMP/Parallel.cs b/DotMP/Parallel.cs index a7f4f8c..1701b04 100644 --- a/DotMP/Parallel.cs +++ b/DotMP/Parallel.cs @@ -74,7 +74,11 @@ public static class Parallel /// The number of threads to be used in the loop. private static void FixArgs(int start, int end, ref IScheduler sched, ref uint? chunk_size, uint num_threads) { +#if NET6_0_OR_GREATER sched ??= Schedule.Static; +#else + if (sched == null) sched = Schedule.Static; +#endif if (sched == Schedule.Runtime) { @@ -161,16 +165,16 @@ private static void ValidateParams(int start = 0, int end = 0, IScheduler schedu if (start < 0 || end < 0) throw new InvalidArgumentsException(string.Format("Start ({0}) and end ({1}) of loop must both be positive integers.", start, end)); - if (num_threads is not null && num_threads < 1) + if (num_threads != null && num_threads < 1) throw new InvalidArgumentsException(string.Format("Number of threads ({0}) should be a positive integer.", num_threads)); - if (chunk_size is not null && chunk_size < 1) + if (chunk_size != null && chunk_size < 1) throw new InvalidArgumentsException(string.Format("Chunk size ({0}) should be a positive integer.", chunk_size)); - if ((num_tasks is not null && num_tasks < 1) || (grainsize is not null && grainsize < 1)) + if ((num_tasks != null && num_tasks < 1) || (grainsize != null && grainsize < 1)) throw new InvalidArgumentsException(string.Format("Number of tasks ({0}) and grain size ({1}) must both be positive integers.", num_tasks, grainsize)); - if (schedule is not null && schedule is not StaticScheduler && schedule is not DynamicScheduler && schedule is not GuidedScheduler && schedule is not RuntimeScheduler && schedule is not WorkStealingScheduler && chunk_size is null) + if (schedule != null && !(schedule is StaticScheduler) && !(schedule is DynamicScheduler) && !(schedule is GuidedScheduler) && !(schedule is RuntimeScheduler) && !(schedule is WorkStealingScheduler) && chunk_size is null) throw new InvalidArgumentsException(string.Format("Chunk size must be specified with user-defined schedulers, as it cannot be inferred.")); } @@ -346,12 +350,12 @@ private static void For(int start, int end, ForAction forAction, ISchedule Barrier(); ws.in_for = true; - Interlocked.Increment(ref freg.in_workshare); + Atomic.Inc(ref freg.in_workshare); ws.PerformLoop(forAction); ws.in_for = false; - Interlocked.Decrement(ref freg.in_workshare); + Atomic.Dec(ref freg.in_workshare); Barrier(); Master(ordered.Clear); @@ -602,11 +606,15 @@ public static void ParallelRegion(Action action, uint? num_threads = null) if (num_threads == null && Parallel.num_threads == 0) num_threads = (uint)GetNumProcs(); else +#if NET6_0_OR_GREATER num_threads ??= Parallel.num_threads; +#else + if (num_threads == null) num_threads = Parallel.num_threads; +#endif ForkedRegion freg = new ForkedRegion(num_threads.Value, action); - if (barrier is not null) barrier.Dispose(); + if (barrier != null) barrier.Dispose(); barrier = new Barrier((int)num_threads.Value); task_nesting.Dispose(); @@ -996,7 +1004,7 @@ public static TaskUUID[] Taskloop(int start, int end, Action action, uint? grainsize = (uint)((end - start) / fr.reg.num_threads) / 8; if (grainsize < 1) grainsize = 1; } - else if (num_tasks is not null) + else if (num_tasks != null) { grainsize = (uint)(end - start) / num_tasks; if (grainsize < 1) grainsize = 1; @@ -1202,7 +1210,7 @@ public static void Single(int id, Action action) throw new CannotPerformNestedWorksharingException("Cannot use DotMP Single nested within other worksharing constructs."); } - Interlocked.Increment(ref freg.in_workshare); + Atomic.Inc(ref freg.in_workshare); lock (single_thread) { @@ -1218,7 +1226,7 @@ public static void Single(int id, Action action) action(); } - Interlocked.Decrement(ref freg.in_workshare); + Atomic.Dec(ref freg.in_workshare); Barrier(); } @@ -1270,7 +1278,7 @@ public static int GetNumThreads() { var freg = new ForkedRegion(); - return (freg.reg is not null) + return (freg.reg != null) ? (int)freg.reg.num_threads : 1; } diff --git a/DotMP/Schedule.cs b/DotMP/Schedule.cs index 48b8d97..f3c25bb 100644 --- a/DotMP/Schedule.cs +++ b/DotMP/Schedule.cs @@ -34,7 +34,7 @@ public interface IScheduler /// The end of the loop, exclusive. /// The number of threads. /// Provided chunk size. - public void LoopInit(int start, int end, uint num_threads, uint chunk_size); + void LoopInit(int start, int end, uint num_threads, uint chunk_size); /// /// Called between each chunk to calculate the bounds of the next chunk. @@ -42,7 +42,7 @@ public interface IScheduler /// The thread ID to provide a chunk to. /// The start of the chunk, inclusive. /// The end of the chunk, exclusive. - public void LoopNext(int thread_id, out int start, out int end); + void LoopNext(int thread_id, out int start, out int end); } #endregion @@ -431,6 +431,12 @@ private struct Queue /// Counts the remaining threads with work so threads know when to stop attempting to steal. /// private volatile uint threads_with_remaining_work; +#if !NET6_0_OR_GREATER + /// + /// Random instance for compatibility with non-.NET 6 compiles. + /// + private ThreadLocal localRandom; +#endif /// /// Override method for LoopInit, is called when first starting a work-stealing loop. @@ -465,6 +471,10 @@ public override void LoopInit(int start, int end, uint num_threads, uint chunk_s queues[num_threads - 1].end = end; queues[num_threads - 1].work_remaining = true; queues[num_threads - 1].qlock = new object(); + +#if !NET6_0_OR_GREATER + localRandom = new ThreadLocal(() => new Random()); +#endif } } @@ -505,17 +515,19 @@ public override void LoopNext(int thread_id, out int start, out int end) /// The thread ID. private void StealHandler(int thread_id) { +#pragma warning disable CS0420 if (queues[thread_id].work_remaining) { - Interlocked.Decrement(ref threads_with_remaining_work); + Atomic.Dec(ref threads_with_remaining_work); queues[thread_id].work_remaining = false; } if (DoSteal(thread_id)) { - Interlocked.Increment(ref threads_with_remaining_work); + Atomic.Inc(ref threads_with_remaining_work); queues[thread_id].work_remaining = true; } +#pragma warning restore CS0420 } /// @@ -528,7 +540,11 @@ private bool DoSteal(int thread_id) { checked { +#if NET6_0_OR_GREATER int rng = Random.Shared.Next((int)num_threads); +#else + int rng = localRandom.Value.Next((int)num_threads); +#endif int new_start, new_end; lock (queues[rng].qlock) diff --git a/DotMP/Tasking.cs b/DotMP/Tasking.cs index db2aa23..c165c42 100644 --- a/DotMP/Tasking.cs +++ b/DotMP/Tasking.cs @@ -148,7 +148,7 @@ public sealed class TaskUUID /// internal TaskUUID() { - uuid = Interlocked.Increment(ref next_uuid); + uuid = Atomic.Inc(ref next_uuid); } /// From 365360f9a6d95ddcfac922461595457e56ed2191 Mon Sep 17 00:00:00 2001 From: Lane Date: Sun, 26 Nov 2023 23:54:03 -0600 Subject: [PATCH 3/4] prepare for new CI with .NET Framework --- .github/workflows/integration.yml | 7 +++++++ DotMP-Tests/DotMP-Tests.csproj | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 1b27d99..ebcaa6b 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -13,12 +13,19 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Setup .NET uses: actions/setup-dotnet@v3 with: dotnet-version: 8.0.x + + - name: Install Mono + run: sudo apt-get install mono-complete -y + shell: bash + - name: Run integration tests run: dotnet test --verbosity normal -p:CollectCoverage=true -p:CoverletOutputFormat=opencover DotMP-Tests + - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 with: diff --git a/DotMP-Tests/DotMP-Tests.csproj b/DotMP-Tests/DotMP-Tests.csproj index 3cb05b2..03b6e58 100644 --- a/DotMP-Tests/DotMP-Tests.csproj +++ b/DotMP-Tests/DotMP-Tests.csproj @@ -1,7 +1,7 @@ - net471;net6.0;net7.0;net8.0 + net471;net8.0 From 3b30d32611a91a7b323e143d2a83870cd7692e6c Mon Sep 17 00:00:00 2001 From: Lane Date: Mon, 27 Nov 2023 00:09:52 -0600 Subject: [PATCH 4/4] move to .net 8 opencover upload --- .github/workflows/integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index ebcaa6b..3583c1e 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -29,6 +29,6 @@ jobs: - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v3 with: - files: coverage.net6.0.opencover.xml + files: coverage.net8.0.opencover.xml directory: DotMP-Tests token: ${{ secrets.CODECOV_TOKEN }}