diff --git a/EvenPerfectBitScanner.Benchmarks/AtomicBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/AtomicBenchmarks.cs new file mode 100644 index 0000000..f417668 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/AtomicBenchmarks.cs @@ -0,0 +1,238 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; +using ILGPU; +using Open.Numeric.Primes; + +namespace EvenPerfectBitScanner.Benchmarks; + +[ShortRunJob(RuntimeMoniker.Net80)] +[MemoryDiagnoser] +public class AtomicBenchmarks +{ + private ulong[] _candidates = []; + private int[] _candidatesInt = []; + + [ParamsSource(nameof(GetBenchmarkCases))] + public PrimeBenchmarkCase BenchmarkCase { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + _candidates = BenchmarkCase.Candidates; + _candidatesInt = [.. _candidates.Select(x => (int)x)]; + } + + [Benchmark] + public int AtomicAddInt() + { + int[] values = _candidatesInt; + int primeCount; + int oldValue = 0; + for (int i = 0; i < values.Length; i++) + { + primeCount = values[i]; + oldValue ^= Atomic.Add(ref primeCount, 1); + } + + return oldValue; + } + + [Benchmark(Baseline = true)] + public int InterlockedIncInt() + { + int[] values = _candidatesInt; + int primeCount; + int oldValue = 0; + for (int i = 0; i < values.Length; i++) + { + primeCount = values[i]; + oldValue ^= Interlocked.Increment(ref primeCount); + } + + return oldValue; + } + + [Benchmark] + public ulong AtomicAddUlong() + { + ulong[] values = _candidates; + ulong primeCount; + ulong oldValue = 0UL; + for (int i = 0; i < values.Length; i++) + { + primeCount = values[i]; + oldValue ^= Atomic.Add(ref primeCount, 1); + } + + return oldValue; + } + + [Benchmark] + public ulong InterlockedIncUlong() + { + ulong[] values = _candidates; + ulong primeCount; + ulong oldValue = 0UL; + for (int i = 0; i < values.Length; i++) + { + primeCount = values[i]; + oldValue ^= Interlocked.Increment(ref primeCount); + } + + return oldValue; + } + + public static IEnumerable GetBenchmarkCases() + { + yield return PrimeBenchmarkCase.FromRange( + "≤100", + "Admissible odd values ≤ 100 (k ≤ 5, not divisible by 5)", + 3UL, + 100UL, + int.MaxValue); + + yield return PrimeBenchmarkCase.FromRange( + "≤4_000_000", + "Sampled admissible odd values ≤ 4,000,000 (k ≤ 5, not divisible by 5)", + 3UL, + 4_000_000UL, + 4_096); + + yield return PrimeBenchmarkCase.FromRange( + "≥16_000_000", + "Sampled admissible odd values ≥ 16,000,000 (k ≤ 5, not divisible by 5)", + 16_000_001UL, + 16_500_000UL, + 4_096); + + yield return PrimeBenchmarkCase.FromRange( + "≥32_000_000", + "Sampled admissible odd values ≥ 32,000,000 (k ≤ 5, not divisible by 5)", + 32_000_001UL, + 32_500_000UL, + 4_096); + + yield return PrimeBenchmarkCase.FromRange( + "≥64_000_000", + "Sampled admissible odd values ≥ 64,000,000 (k ≤ 5, not divisible by 5)", + 64_000_001UL, + 64_500_000UL, + 4_096); + + yield return PrimeBenchmarkCase.FromRange( + "≥100_000_000", + "Sampled admissible odd values ≥ 100,000,000 (k ≤ 5, not divisible by 5)", + 100_000_001UL, + 100_500_000UL, + 4_096); + + yield return PrimeBenchmarkCase.FromRange( + "≥138_000_000", + "Sampled admissible odd values ≥ 138,000,000 (k ≤ 5, not divisible by 5)", + 138_000_001UL, + 138_500_000UL, + 4_096); + + yield return PrimeBenchmarkCase.FromRange( + "≥276_000_000", + "Sampled admissible odd values ≥ 276,000,000 (k ≤ 5, not divisible by 5)", + 276_000_001UL, + 276_500_000UL, + 4_096); + + yield return PrimeBenchmarkCase.FromRange( + "≥552_000_000", + "Sampled admissible odd values ≥ 552,000,000 (k ≤ 5, not divisible by 5)", + 552_000_001UL, + 552_500_000UL, + 4_096); + } + + public readonly record struct PrimeBenchmarkCase(string Name, string Description, ulong[] Candidates) + { + public override string ToString() + { + return Name; + } + + public static PrimeBenchmarkCase FromRange( + string name, + string description, + ulong startInclusive, + ulong endInclusive, + int maxCount) + { + ulong[] values = CollectRange(startInclusive, endInclusive, maxCount); + return new PrimeBenchmarkCase(name, description, values); + } + } + + private static ulong[] CollectRange(ulong startInclusive, ulong endInclusive, int maxCount) + { + maxCount = maxCount < 0 ? 0 : maxCount; + if (maxCount == 0) + { + return []; + } + + int estimatedLength = (int)Math.Min((ulong)maxCount, endInclusive - startInclusive + 1UL); + ulong[] buffer = new ulong[estimatedLength]; + int count = 0; + + for (ulong value = startInclusive; value <= endInclusive && count < maxCount; value++) + { + if (!IsAdmissibleMersenneDivisorCandidate(value)) + { + continue; + } + + if (count == buffer.Length) + { + Array.Resize(ref buffer, buffer.Length * 2); + } + + buffer[count++] = value; + } + + if (count == buffer.Length) + { + return buffer; + } + + ulong[] result = new ulong[count]; + Array.Copy(buffer, result, count); + return result; + } + + private static bool IsAdmissibleMersenneDivisorCandidate(ulong value) + { + if ((value & 1UL) == 0UL || value % 5UL == 0UL || value <= 1UL) + { + return false; + } + + ulong qMinusOne = value - 1UL; + + for (ulong k = 1; k <= 5; k++) + { + ulong denominator = k << 1; + if (qMinusOne % denominator != 0UL) + { + continue; + } + + ulong p = qMinusOne / denominator; + if (p < 2UL) + { + continue; + } + + if (Prime.Numbers.IsPrime(p)) + { + return true; + } + } + + return false; + } +} diff --git a/PerfectNumbers.Core/Gpu/AcceleratorPool.cs b/PerfectNumbers.Core/Gpu/AcceleratorPool.cs index 81828b4..be07471 100644 --- a/PerfectNumbers.Core/Gpu/AcceleratorPool.cs +++ b/PerfectNumbers.Core/Gpu/AcceleratorPool.cs @@ -1,3 +1,4 @@ +using System.Runtime.CompilerServices; using ILGPU; using ILGPU.Runtime; @@ -11,10 +12,11 @@ namespace PerfectNumbers.Core.Gpu private int _index; + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public int Rent() { - Atomic.CompareExchange(ref _index, Capacity, 0); - var index = Atomic.Add(ref _index, 1); + _ = Interlocked.CompareExchange(ref _index, Capacity, 0); + var index = Interlocked.Increment(ref _index); if (index >= Capacity) { index -= Capacity; @@ -23,6 +25,7 @@ namespace PerfectNumbers.Core.Gpu return index; } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public void Return(int acceleratorIndex) { // Intentionally left empty - there's nothing to do here. We're not really renting anything. diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs index fb3f1e6..87f2dae 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs @@ -377,12 +377,12 @@ internal static partial class PrimeOrderCalculator { while (true) { - if (Atomic.CompareExchange(ref _cpuCount, 0, 1) == 0) + if (Interlocked.CompareExchange(ref _cpuCount, 0, 1) == 0) { return true; } - if (Atomic.CompareExchange(ref _cpuCount, 1, 0) == 1) + if (Interlocked.CompareExchange(ref _cpuCount, 1, 0) == 1) { return false; } @@ -391,10 +391,10 @@ internal static partial class PrimeOrderCalculator } } - int cpuCount = Atomic.Add(ref _cpuCount, 1); + int cpuCount = Interlocked.Increment(ref _cpuCount); if (cpuCount == PerfectNumberConstants.GpuRatio) { - Atomic.Add(ref _cpuCount, -PerfectNumberConstants.GpuRatio); + Interlocked.Add(ref _cpuCount, -PerfectNumberConstants.GpuRatio); } else if (cpuCount > PerfectNumberConstants.GpuRatio) {