diff --git a/PerfectNumbers.Core/ArrayPools.cs b/PerfectNumbers.Core/ArrayPools.cs deleted file mode 100644 index 5e64061..0000000 --- a/PerfectNumbers.Core/ArrayPools.cs +++ /dev/null @@ -1,104 +0,0 @@ -using System.Runtime.CompilerServices; - -namespace PerfectNumbers.Core -{ - public static class Pools - { - private const int PoolCapacity = 10240; //PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount << 8; - - [ThreadStatic] - private static IntArrayPool? _intArrayPool; - internal static IntArrayPool ExclusiveIntArray - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _intArrayPool ??= new(PoolCapacity); - } - } - - - [ThreadStatic] - private static UlongArrayPool? _ulongArrayPool; - internal static UlongArrayPool ExclusiveUlongArray - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _ulongArrayPool ??= new(PoolCapacity); - } - } - } - - public struct IntArrayPool(int poolCapacity) - { - private readonly int[][] _arrays = new int[poolCapacity][]; - private int _count = 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public int[] Rent(int length) - { - int available = _count; - if (available == 0) - { - return new int[length]; - } - - int[][] arrays = _arrays; - available--; - _count = available; - - int[] candidate = arrays[available]; - return candidate.Length >= length ? candidate : new int[length]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Return(int[] array) - { - int[][] arrays = _arrays; - int available = _count; - - if (available < arrays.Length) - { - arrays[available] = array; - _count = available + 1; - } - } - } - - public struct UlongArrayPool(int poolCapacity) - { - private readonly ulong[][] _arrays = new ulong[poolCapacity][]; - private int _count = 0; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ulong[] Rent(int length) - { - int available = _count; - if (available == 0) - { - return new ulong[length]; - } - - ulong[][] arrays = _arrays; - available--; - _count = available; - - ulong[] candidate = arrays[available]; - return candidate.Length >= length ? candidate : new ulong[length]; - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Return(ulong[] array) - { - ulong[][] arrays = _arrays; - int available = _count; - - if (available < arrays.Length) - { - arrays[available] = array; - _count = available + 1; - } - } - } -} diff --git a/PerfectNumbers.Core/ConcurrentFixedCapacityStack.cs b/PerfectNumbers.Core/ConcurrentFixedCapacityStack.cs new file mode 100644 index 0000000..3efa874 --- /dev/null +++ b/PerfectNumbers.Core/ConcurrentFixedCapacityStack.cs @@ -0,0 +1,77 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core; + +public sealed class ConcurrentFixedCapacityStack(int capacity) +{ + private static readonly FixedCapacityArrayPool _arrayPool = FixedCapacityPools.ExclusiveArray; + + private readonly T[] _items = _arrayPool.Rent(capacity); + private readonly int _capacity = capacity; + + private int _count = 0; + public int Count + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => Volatile.Read(ref _count); + } + + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _capacity; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Clear() => Interlocked.Exchange(ref _count, 0); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public T? Pop() => TryPop(out T? item) ? item : default; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public bool TryPop(out T? item) + { + while (true) + { + int observed = Volatile.Read(ref _count); + if (observed == 0) + { + item = default; + return false; + } + + int next = observed - 1; + if (Interlocked.CompareExchange(ref _count, next, observed) == observed) + { + item = _items[next]; + return true; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Push(T item) + { + int next = Interlocked.Increment(ref _count); + if (next <= _capacity) + { + _items[next - 1] = item; + return; + } + + Interlocked.Decrement(ref _count); + throw new InvalidOperationException("Stack is full."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Return(in T[] items) => _arrayPool.Return(items); + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public T[] ToArray() + { + int count = Volatile.Read(ref _count); + T[] result = _arrayPool.Rent(count); + Array.Copy(_items, result, count); + return result; + } +} diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index 38a1ec6..8113c77 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -157,20 +157,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv } } - public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) - { - MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession(); - - if (session is not null) - { - session.Configure(gpu, _orderDevice); - return session; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) => ThreadStaticPools.RentMersenneCpuDivisorSession(gpu, _orderDevice); - return new MersenneCpuDivisorScanSession(gpu, _orderDevice); - } - - private bool CheckDivisors( + private bool CheckDivisors( PrimeOrderCalculatorAccelerator gpu, ulong prime, BigInteger allowedMax, @@ -564,7 +554,7 @@ private bool CheckDivisors64( out ulong foundDivisor) { bool canAdvance = step <= limit; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; if (!canAdvance) { if (divisor <= limit && residueStepper.IsAdmissible()) diff --git a/PerfectNumbers.Core/DivisorCycleCache.cs b/PerfectNumbers.Core/DivisorCycleCache.cs index aedfa4f..0ea54b2 100644 --- a/PerfectNumbers.Core/DivisorCycleCache.cs +++ b/PerfectNumbers.Core/DivisorCycleCache.cs @@ -55,6 +55,7 @@ public sealed class DivisorCycleCache { if (!skipPrimeOrderHeuristic) { + // TODO: Implement array-free version of GetCycleLengthsCpu so that we don't pay the array costs for single checks. Span result = stackalloc ulong[1]; ReadOnlySpan singleDivisor = stackalloc ulong[1] { divisor }; GetCycleLengthsCpu(singleDivisor, result); @@ -82,7 +83,7 @@ public sealed class DivisorCycleCache return cycleLength; } - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor); cycleLength = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData, skipPrimeOrderHeuristic: true); divisorPool.Return(divisorData); @@ -93,6 +94,7 @@ public sealed class DivisorCycleCache { if (!skipPrimeOrderHeuristic) { + // TODO: Implement array-free version of GetCycleLengthsHybrid so that we don't pay the array costs for single checks. Span result = stackalloc ulong[1]; ReadOnlySpan singleDivisor = stackalloc ulong[1] { divisor }; GetCycleLengthsHybrid(gpu, singleDivisor, result); @@ -120,7 +122,7 @@ public sealed class DivisorCycleCache return cycleLength; } - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor); cycleLength = MersenneDivisorCycles.CalculateCycleLengthHybrid(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true); divisorPool.Return(divisorData); @@ -158,7 +160,7 @@ public sealed class DivisorCycleCache return cycleLength; } - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor); cycleLength = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true); divisorPool.Return(divisorData); @@ -346,7 +348,7 @@ public sealed class DivisorCycleCache [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ComputeCyclesCpu(ReadOnlySpan divisors, Span cycles, ReadOnlySpan indices) { - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; for (int i = 0; i < indices.Length; i++) { int targetIndex = indices[i]; @@ -360,7 +362,7 @@ public sealed class DivisorCycleCache [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ComputeCyclesHybrid(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan divisors, Span cycles, ReadOnlySpan indices) { - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; for (int i = 0; i < indices.Length; i++) { int targetIndex = indices[i]; diff --git a/PerfectNumbers.Core/FixedCapacityArrayPool.cs b/PerfectNumbers.Core/FixedCapacityArrayPool.cs new file mode 100644 index 0000000..62042d9 --- /dev/null +++ b/PerfectNumbers.Core/FixedCapacityArrayPool.cs @@ -0,0 +1,40 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core +{ + public struct FixedCapacityArrayPool(int poolCapacity) + { + private readonly T[][] _arrays = new T[poolCapacity][]; + private int _count = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public T[] Rent(int length) + { + int available = _count; + if (available == 0) + { + return new T[length]; + } + + T[][] arrays = _arrays; + available--; + _count = available; + + T[] candidate = arrays[available]; + return candidate.Length >= length ? candidate : new T[length]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Return(T[] array) + { + T[][] arrays = _arrays; + int available = _count; + + if (available < arrays.Length) + { + arrays[available] = array; + _count = available + 1; + } + } + } +} diff --git a/PerfectNumbers.Core/FixedCapacityPools.cs b/PerfectNumbers.Core/FixedCapacityPools.cs new file mode 100644 index 0000000..78dc992 --- /dev/null +++ b/PerfectNumbers.Core/FixedCapacityPools.cs @@ -0,0 +1,37 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core +{ + public static class FixedCapacityPools + { + [ThreadStatic] + private static FixedCapacityArrayPool? _arrayPool; + internal static FixedCapacityArrayPool ExclusiveArray + { + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _arrayPool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + } + } + + public static class FixedCapacityPools + { + [ThreadStatic] + private static IntArrayPool? _intArrayPool; + internal static IntArrayPool ExclusiveIntArray + { + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _intArrayPool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + } + + [ThreadStatic] + private static UlongArrayPool? _ulongArrayPool; + internal static UlongArrayPool ExclusiveUlongArray + { + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _ulongArrayPool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + } + } +} diff --git a/PerfectNumbers.Core/FixedCapacityStack.cs b/PerfectNumbers.Core/FixedCapacityStack.cs new file mode 100644 index 0000000..19bc6c4 --- /dev/null +++ b/PerfectNumbers.Core/FixedCapacityStack.cs @@ -0,0 +1,46 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core; + +public sealed class FixedCapacityStack(int capacity) +{ + private static readonly FixedCapacityArrayPool _arrayPool = FixedCapacityPools.ExclusiveArray; + + public int Count = 0; + private T[] _items = _arrayPool.Rent(capacity); + + private int _capacity = capacity; + public int Capacity + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _capacity; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + set + { + if (value < _capacity) + { + _arrayPool.Return(_items); + _items = _arrayPool.Rent(value); + _capacity = value; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Clear() => Count = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public T? Pop() => Count > 0 ? _items[Count--] : default; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Push(T item) + { + int newCount = Count + 1; + if (newCount < _items.Length) + { + _items[newCount] = item; + Count = newCount; + } + } +} diff --git a/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs b/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs index c6a5317..44af314 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/AcceleratorStreamPool.cs @@ -1,4 +1,4 @@ -using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using ILGPU.Runtime; namespace PerfectNumbers.Core.Gpu.Accelerators; @@ -7,13 +7,14 @@ public static class AcceleratorStreamPool { private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; private static readonly SemaphoreSlim[] _locks = new SemaphoreSlim[PerfectNumberConstants.RollingAccelerators]; - private static readonly ConcurrentQueue[] _streams = new ConcurrentQueue [PerfectNumberConstants.RollingAccelerators]; + private static readonly ConcurrentFixedCapacityStack[] _streams = new ConcurrentFixedCapacityStack[PerfectNumberConstants.RollingAccelerators]; private static SemaphoreSlim CreateLock() => new(PerfectNumberConstants.ThreadsByAccelerator); // private static int _rented; + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static AcceleratorStream Rent(int acceleratorIndex) { var streamLock = _locks[acceleratorIndex]; @@ -21,22 +22,23 @@ public static class AcceleratorStreamPool streamLock.Wait(); - return queue.TryDequeue(out var stream) + return queue.Pop() is { } stream ? stream : _accelerators[acceleratorIndex].CreateStream(); } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Return(int acceleratorIndex, AcceleratorStream stream) { var streamLock = _locks[acceleratorIndex]; var queue = _streams[acceleratorIndex]; - queue.Enqueue(stream); + queue.Push(stream); streamLock.Release(); } public static void WarmUp(int acceleratorIndex) { - _streams[acceleratorIndex] = new(); + _streams[acceleratorIndex] = new(PerfectNumberConstants.DefaultPoolCapacity); _locks[acceleratorIndex] = CreateLock(); } } diff --git a/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs b/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs index ba5db3d..e17d867 100644 --- a/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs +++ b/PerfectNumbers.Core/Gpu/Accelerators/PrimeOrderCalculatorAccelerator.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using System.Runtime.CompilerServices; using ILGPU; using ILGPU.Runtime; @@ -11,34 +10,31 @@ public sealed class PrimeOrderCalculatorAccelerator #region Pool // [ThreadStatic] - private static ConcurrentQueue? _pool; + private static ConcurrentFixedCapacityStack? _pool; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static PrimeOrderCalculatorAccelerator Rent(int primeTesterCapacity) { - var pool = _pool ??= []; - if (!pool.TryDequeue(out var gpu)) - { - int acceleratorIndex = AcceleratorPool.Shared.Rent(); - gpu = new(acceleratorIndex, PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount, PerfectNumberConstants.DefaultSpecialMaxFactorCapacity); - } - else + var pool = _pool ??= new(PerfectNumberConstants.DefaultThreadPoolCapacity); + if (pool.Pop() is { } gpu) { gpu.EnsureCapacity(PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity); + return gpu; } - return gpu; + int acceleratorIndex = AcceleratorPool.Shared.Rent(); + return new(acceleratorIndex, PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount, PerfectNumberConstants.DefaultSpecialMaxFactorCapacity); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(PrimeOrderCalculatorAccelerator gpu) => _pool!.Enqueue(gpu); + public static void Return(PrimeOrderCalculatorAccelerator gpu) => _pool!.Push(gpu); internal static void Clear() { var pool = _pool; if (pool != null) { - while (pool.TryDequeue(out var gpu)) + while (pool.Pop() is { } gpu) { gpu.Dispose(); } @@ -50,7 +46,7 @@ public sealed class PrimeOrderCalculatorAccelerator var pool = _pool; if (pool != null) { - while (pool.TryDequeue(out var gpu)) + while (pool.Pop() is { } gpu) { gpu.Dispose(); } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index db721db..a2abc94 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -22,9 +22,9 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN private BigInteger _minK = BigInteger.One; // private bool _isConfigured; - private readonly ConcurrentBag[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag())]; + private readonly ConcurrentFixedCapacityStack[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentFixedCapacityStack(PerfectNumberConstants.DefaultPoolCapacity))]; - private readonly ConcurrentBag _sessionPool = []; + private readonly ConcurrentFixedCapacityStack _sessionPool = new(PerfectNumberConstants.DefaultPoolCapacity); public int GpuBatchSize { @@ -272,7 +272,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN ArrayView1D exponentViewDevice = exponentBuffer.View; ArrayView1D hitsView = hitsBuffer.View; ArrayView1D hitIndexView = hitIndexBuffer.View; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; static bool ProcessRange( PrimeOrderCalculatorAccelerator gpu, ulong prime, @@ -291,7 +291,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN Span offsetSpan, Span countSpan, Span cycleSpan, - Queue divisorPool, + FixedCapacityStack divisorPool, int chunkCountBaseline, int acceleratorIndex, ref bool composite, @@ -598,6 +598,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN _kernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>(DivisorByDivisorKernels.CheckKernel)).CreateLauncherDelegate, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView, ArrayView>>(); } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] internal void Reset() { } @@ -613,7 +614,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN Accelerator accelerator = _accelerator; int desiredCapacity = requiredCapacity > _capacity ? requiredCapacity : _capacity; _capacity = desiredCapacity; - + // lock (accelerator) { _exponentsBuffer = accelerator.Allocate1D(desiredCapacity); @@ -706,15 +707,13 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN return sentinel == 0; } - public void Return() - { - _owner._sessionPool.Add(this); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Return() => _owner._sessionPool.Push(this); } - public ulong DivisorLimit { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] get { // The EvenPerfectBitScanner driver configures the GPU tester before exposing it to callers, so the previous synchronization guard remains commented out. @@ -732,22 +731,22 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN } } - - + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] private MersenneNumberDivisorByDivisorAccelerator RentBatchResources(int acceleratorIndex, int capacity) { var queue = _resourcePool[acceleratorIndex]; var accelerator = AcceleratorPool.Shared.Accelerators[acceleratorIndex]; - if (!queue.TryTake(out MersenneNumberDivisorByDivisorAccelerator? resources)) + if (queue.Pop() is { } resources) { - return new MersenneNumberDivisorByDivisorAccelerator(accelerator, capacity); + resources.EnsureCapacity(accelerator, capacity); + return resources; } - resources.EnsureCapacity(accelerator, capacity); - return resources; + return new MersenneNumberDivisorByDivisorAccelerator(accelerator, capacity); } - private void ReturnBatchResources(int acceleratorIndex, MersenneNumberDivisorByDivisorAccelerator resources) => _resourcePool[acceleratorIndex].Add(resources); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void ReturnBatchResources(int acceleratorIndex, MersenneNumberDivisorByDivisorAccelerator resources) => _resourcePool[acceleratorIndex].Push(resources); private sealed class AcceleratorReferenceComparer : IEqualityComparer { @@ -758,6 +757,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj); } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public ulong GetAllowedMaxDivisor(ulong prime) { // The configuration is immutable after setup, so the cached limit can be read without locking. @@ -774,6 +774,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN return ComputeAllowedMaxDivisorGpu(prime, _divisorLimit); } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession(PrimeOrderCalculatorAccelerator gpu) { // The tester is configured once at startup and the session pool relies on thread-safe collections, so the previous synchronization guard stays commented out. @@ -793,7 +794,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN // return new DivisorScanSession(this); // } - if (_sessionPool.TryTake(out DivisorScanSession? session)) + if (_sessionPool.Pop() is { } session) { session.Reset(); return session; diff --git a/PerfectNumbers.Core/IntArrayPool.cs b/PerfectNumbers.Core/IntArrayPool.cs new file mode 100644 index 0000000..1d87e61 --- /dev/null +++ b/PerfectNumbers.Core/IntArrayPool.cs @@ -0,0 +1,40 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core +{ + public struct IntArrayPool(int poolCapacity) + { + private readonly int[][] _arrays = new int[poolCapacity][]; + private int _count = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public int[] Rent(int length) + { + int available = _count; + if (available == 0) + { + return new int[length]; + } + + int[][] arrays = _arrays; + available--; + _count = available; + + int[] candidate = arrays[available]; + return candidate.Length >= length ? candidate : new int[length]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Return(int[] array) + { + int[][] arrays = _arrays; + int available = _count; + + if (available < arrays.Length) + { + arrays[available] = array; + _count = available + 1; + } + } + } +} diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 9ebb2a5..d9a00e8 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -394,7 +394,7 @@ public class MersenneDivisorCycles if (divisor <= ulong.MaxValue) { ulong divisor64 = (ulong)divisor; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64); ulong cycle = CalculateCycleLengthCpu(divisor64, divisorData); divisorPool.Return(divisorData); @@ -441,7 +441,7 @@ public class MersenneDivisorCycles if (divisor <= ulong.MaxValue) { ulong divisor64 = (ulong)divisor; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64); ulong cycle = CalculateCycleLengthHybrid(gpu, divisor64, divisorData); divisorPool.Return(divisorData); @@ -489,7 +489,7 @@ public class MersenneDivisorCycles if (divisor <= ulong.MaxValue) { ulong divisor64 = (ulong)divisor; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; MontgomeryDivisorData divisorData = divisorPool.FromModulus(divisor64); ulong cycle = CalculateCycleLengthGpu(gpu, divisor64, divisorData); divisorPool.Return(divisorData); @@ -1161,7 +1161,6 @@ public class MersenneDivisorCycles ulong localStart = threadStart; ulong localEnd = threadEnd; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; tasks[taskIndex] = Task.Run(() => { ulong rangeLength = localEnd - localStart + 1UL; @@ -1169,6 +1168,7 @@ public class MersenneDivisorCycles // (ulong divisor, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent(checked((int)rangeLength)); int localCycleIndex = 0; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; var gpu = PrimeOrderCalculatorAccelerator.Rent(1); for (ulong divisor = localStart; divisor <= localEnd; divisor++) { diff --git a/PerfectNumbers.Core/MersennePrimeFactorTester.cs b/PerfectNumbers.Core/MersennePrimeFactorTester.cs index 1ecdce6..65b9678 100644 --- a/PerfectNumbers.Core/MersennePrimeFactorTester.cs +++ b/PerfectNumbers.Core/MersennePrimeFactorTester.cs @@ -1,4 +1,3 @@ -using System.Collections.Concurrent; using System.Runtime.CompilerServices; using System.Numerics; using UInt128 = System.UInt128; @@ -12,102 +11,102 @@ public static class MersennePrimeFactorTester // private static ulong _isPrimeFactorHits; // private static ulong _factor64Hits; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsPrimeFactor(ulong p, ulong q, CancellationToken ct) - { - if (q < 2UL) - { - return false; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool IsPrimeFactor(ulong p, ulong q, CancellationToken ct) + { + if (q < 2UL) + { + return false; + } - if ((q & 7UL) != 1UL && (q & 7UL) != 7UL) - { - return false; - } + if ((q & 7UL) != 1UL && (q & 7UL) != 7UL) + { + return false; + } - if (ct.IsCancellationRequested) - { - return false; - } + if (ct.IsCancellationRequested) + { + return false; + } - // if (!HeuristicPrimeTester.Exclusive.IsPrime(q, ct)) + // if (!HeuristicPrimeTester.Exclusive.IsPrime(q, ct)) // Atomic.Add(ref _isPrimeFactorHits, 1UL); // Console.WriteLine($"MersennePrimeFactorTester.IsPrimeFactor hits {Volatile.Read(ref _isPrimeFactorHits)}"); - if (!PrimeTester.IsPrimeCpu(q)) - { - return false; - } + if (!PrimeTester.IsPrimeCpu(q)) + { + return false; + } - if (p < 2UL || (p & 1UL) == 0) - { - return false; - } + if (p < 2UL || (p & 1UL) == 0) + { + return false; + } - ulong value = checked(p << 1); - // TODO: Replace this `%` with the benchmarked Math.DivRem fast path so the hot filter avoids - // 128-bit division when evaluating candidates. - if ((q - 1UL) % value != 0UL) - { - return false; - } + ulong value = checked(p << 1); + // TODO: Replace this `%` with the benchmarked Math.DivRem fast path so the hot filter avoids + // 128-bit division when evaluating candidates. + if ((q - 1UL) % value != 0UL) + { + return false; + } - ulong order = GetOrderOf2ModPrime(q, ct); - return !ct.IsCancellationRequested && order == p; - } + ulong order = GetOrderOf2ModPrime(q, ct); + return !ct.IsCancellationRequested && order == p; + } - // TODO: Replace this dictionary with the divisor-cycle order cache once the benchmarks confirm the - // shared cache can stream snapshot results without extra locking. - private static readonly Dictionary _orderCache = []; + // TODO: Replace this dictionary with the divisor-cycle order cache once the benchmarks confirm the + // shared cache can stream snapshot results without extra locking. + private static readonly Dictionary _orderCache = []; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ulong GetOrderOf2ModPrime(ulong q, CancellationToken ct) => - // TODO: Collapse this wrapper once all callers promote operands to UInt128 so we - // can jump straight to OrderOf2ModPrime without bouncing through multiple overloads. - (ulong)GetOrderOf2ModPrime((UInt128)q, ct); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static ulong GetOrderOf2ModPrime(ulong q, CancellationToken ct) => + // TODO: Collapse this wrapper once all callers promote operands to UInt128 so we + // can jump straight to OrderOf2ModPrime without bouncing through multiple overloads. + (ulong)GetOrderOf2ModPrime((UInt128)q, ct); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static UInt128 GetOrderOf2ModPrime(UInt128 q, CancellationToken ct) => - // TODO: Inline this shim once callers can invoke OrderOf2ModPrime directly; the extra - // hop shows up on hot perf traces while we chase large-order factors. - OrderOf2ModPrime(q, ct); + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static UInt128 GetOrderOf2ModPrime(UInt128 q, CancellationToken ct) => + // TODO: Inline this shim once callers can invoke OrderOf2ModPrime directly; the extra + // hop shows up on hot perf traces while we chase large-order factors. + OrderOf2ModPrime(q, ct); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static UInt128 OrderOf2ModPrime(UInt128 q, CancellationToken ct) - { - - lock (_orderCache) - { - // TODO: Replace this lock with the lock-free order cache once the divisor-cycle snapshot exposes - // deterministic ordering for single-cycle computations; we measured heavy contention in the - // factor benchmarks when many threads warm the cache concurrently. - if (_orderCache.TryGetValue(q, out var cached)) - { - return cached; - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static UInt128 OrderOf2ModPrime(UInt128 q, CancellationToken ct) + { + + lock (_orderCache) + { + // TODO: Replace this lock with the lock-free order cache once the divisor-cycle snapshot exposes + // deterministic ordering for single-cycle computations; we measured heavy contention in the + // factor benchmarks when many threads warm the cache concurrently. + if (_orderCache.TryGetValue(q, out var cached)) + { + return cached; + } + } ArrayPool uInt128Pool = ThreadStaticPools.UInt128Pool; - UInt128 phi = q - 1; - UInt128[] factors = phi <= ulong.MaxValue - ? Array.ConvertAll(Factor64((ulong)phi, ct), x => (UInt128)x) - : Factor128(phi, ct); - // TODO: Pull these factor arrays from ArrayPool once the factoring helpers adopt the pooled - // buffers measured faster in FactorizationBenchmarks. - UInt128 order = phi; + UInt128 phi = q - 1; + UInt128[] factors = phi <= ulong.MaxValue + ? Array.ConvertAll(Factor64((ulong)phi, ct), x => (UInt128)x) + : Factor128(phi, ct); + // TODO: Pull these factor arrays from ArrayPool once the factoring helpers adopt the pooled + // buffers measured faster in FactorizationBenchmarks. + UInt128 order = phi; - UInt128 candidate, prime; - int i, factorsCount = factors.Length; - for (i = 0; i < factorsCount; i++) - { - prime = factors[i]; - if (ct.IsCancellationRequested) - { - uInt128Pool.Return(factors); - return 0; - } + UInt128 candidate, prime; + int i, factorsCount = factors.Length; + for (i = 0; i < factorsCount; i++) + { + prime = factors[i]; + if (ct.IsCancellationRequested) + { + uInt128Pool.Return(factors); + return 0; + } // TODO: Swap this `%`/`/` loop for the Math.DivRem-based reducer highlighted in the // order benchmarks so we avoid repeated 128-bit division while trimming the order. @@ -130,440 +129,430 @@ public static class MersennePrimeFactorTester } } - uInt128Pool.Return(factors); - } + uInt128Pool.Return(factors); + } - // TODO: Integrate divisor-cycle data here so repeated order refinement uses cached cycle - // lengths when available and computes missing orders on the configured device without - // persisting them in the shared cache. - if (!ct.IsCancellationRequested) - { - lock (_orderCache) - { - _orderCache[q] = order; - } - } + // TODO: Integrate divisor-cycle data here so repeated order refinement uses cached cycle + // lengths when available and computes missing orders on the configured device without + // persisting them in the shared cache. + if (!ct.IsCancellationRequested) + { + lock (_orderCache) + { + _orderCache[q] = order; + } + } uInt128Pool.Return(factors); - return order; - } + return order; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static UInt128 ModPowWithCancellation(UInt128 baseValue, UInt128 exponent, UInt128 modulus, CancellationToken ct) - { - // Keep this helper local to the factoring path so we can preserve the token-aware early outs - // without carrying a separate overload on the shared UInt128 extensions. - if (modulus <= UInt128.One) - { - return UInt128.Zero; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static UInt128 ModPowWithCancellation(UInt128 baseValue, UInt128 exponent, UInt128 modulus, CancellationToken ct) + { + // Keep this helper local to the factoring path so we can preserve the token-aware early outs + // without carrying a separate overload on the shared UInt128 extensions. + if (modulus <= UInt128.One) + { + return UInt128.Zero; + } - UInt128 result = UInt128.One; - UInt128 baseResidue = baseValue % modulus; + UInt128 result = UInt128.One; + UInt128 baseResidue = baseValue % modulus; - while (exponent != UInt128.Zero) - { - if (ct.IsCancellationRequested) - { - return UInt128.Zero; - } + while (exponent != UInt128.Zero) + { + if (ct.IsCancellationRequested) + { + return UInt128.Zero; + } - if ((exponent & UInt128.One) != UInt128.Zero) - { - result = result.MulMod(baseResidue, modulus); - } + if ((exponent & UInt128.One) != UInt128.Zero) + { + result = result.MulMod(baseResidue, modulus); + } - if (ct.IsCancellationRequested) - { - return UInt128.Zero; - } + if (ct.IsCancellationRequested) + { + return UInt128.Zero; + } - baseResidue = baseResidue.MulMod(baseResidue, modulus); - exponent >>= 1; - } + baseResidue = baseResidue.MulMod(baseResidue, modulus); + exponent >>= 1; + } - return result; - } + return result; + } - private static readonly ConcurrentBag> ListPool = new(); - private static readonly ConcurrentBag> DictPool = new(); - private static readonly Dictionary FactorCache = new(); - private static readonly object FactorCacheLock = new(); + private static readonly ConcurrentFixedCapacityStack> ListPool = new(PerfectNumberConstants.DefaultPoolCapacity); + private static readonly ConcurrentFixedCapacityStack> DictPool = new(PerfectNumberConstants.DefaultPoolCapacity); - private static List RentList() => ListPool.TryTake(out var list) ? list : new List(4); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static List RentList() => ListPool.Pop() is { } list ? list : new List(4); - private static void ReturnList(List list) - { - list.Clear(); - ListPool.Add(list); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static void ReturnList(List list) + { + list.Clear(); + ListPool.Push(list); + } - private static Dictionary RentDictionary() => DictPool.TryTake(out var dict) ? dict : new Dictionary(); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static Dictionary RentDictionary() => DictPool.Pop() is { } dict ? dict : []; - private static void ReturnDictionary(Dictionary dict) - { - dict.Clear(); - DictPool.Add(dict); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static void ReturnDictionary(Dictionary dict) + { + dict.Clear(); + DictPool.Push(dict); + } - private static readonly ConcurrentBag> DictPool128 = new(); + private static readonly ConcurrentFixedCapacityStack> DictPool128 = new(PerfectNumberConstants.DefaultPoolCapacity); - private static Dictionary RentDictionary128() => - DictPool128.TryTake(out var dict) ? dict : new Dictionary(); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static Dictionary RentDictionary128() => + DictPool128.Pop() is { } dict ? dict : []; - private static void ReturnDictionary128(Dictionary dict) - { - dict.Clear(); - DictPool128.Add(dict); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static void ReturnDictionary128(Dictionary dict) + { + dict.Clear(); + DictPool128.Push(dict); + } - private static ulong[] Factor64(ulong n, CancellationToken ct) - { - // lock (FactorCacheLock) - // { - // if (FactorCache.TryGetValue(n, out var cached)) - // { - // return cached; - // } - // } + private static ulong[] Factor64(ulong n, CancellationToken ct) + { + // lock (FactorCacheLock) + // { + // if (FactorCache.TryGetValue(n, out var cached)) + // { + // return cached; + // } + // } - if (n == 1UL) - { - return []; - } + if (n == 1UL) + { + return []; + } - var dict = RentDictionary(); - var queue = new Queue(); // TODO: Replace this Queue with the pooled stack from the - // FactorizationBenchmarks fast path so factoring large - // composites avoids per-node allocations. - queue.Enqueue(n); + var dict = RentDictionary(); + var queue = ThreadStaticPools.RentUlongStack(PerfectNumberConstants.DefaultPoolCapacity); // TODO: Replace this Queue with the pooled stack from the + // FactorizationBenchmarks fast path so factoring large + // composites avoids per-node allocations. + queue.Push(n); - while (queue.Count != 0) - { - if (ct.IsCancellationRequested) - { - break; - } + while (queue.Count != 0) + { + if (ct.IsCancellationRequested) + { + break; + } - ulong m = queue.Dequeue(); - if (m == 1UL) - { - continue; - } + ulong m = queue.Pop(); + if (m == 1UL) + { + continue; + } // Atomic.Add(ref _factor64Hits, 1UL); // Console.WriteLine($"MersennePrimeFactorTester.Factor64 hits {Volatile.Read(ref _factor64Hits)}"); - // if (HeuristicPrimeTester.Exclusive.IsPrime(m, ct)) - if (PrimeTester.IsPrimeCpu(m)) - { - dict[m] = 1; - continue; - } + // if (HeuristicPrimeTester.Exclusive.IsPrime(m, ct)) + if (PrimeTester.IsPrimeCpu(m)) + { + dict[m] = 1; + continue; + } - if (ct.IsCancellationRequested) - { - break; - } + if (ct.IsCancellationRequested) + { + break; + } - ulong d = PollardRho(m, ct); - if (ct.IsCancellationRequested) - { - break; - } + ulong d = PollardRho(m, ct); + if (ct.IsCancellationRequested) + { + break; + } - queue.Enqueue(d); - queue.Enqueue(m / d); - } + queue.Push(d); + queue.Push(m / d); + } - ulong[] result = [.. dict.Keys]; - // TODO: Rent the result array from ArrayPool once the factoring pipeline consumes spans so we - // can recycle buffers across large factorizations. - // lock (FactorCacheLock) - // { - // FactorCache[n] = result; - // } + ThreadStaticPools.ReturnUlongStack(queue); + ulong[] result = [.. dict.Keys]; + // TODO: Rent the result array from ArrayPool once the factoring pipeline consumes spans so we + // can recycle buffers across large factorizations. + // lock (FactorCacheLock) + // { + // FactorCache[n] = result; + // } - ReturnDictionary(dict); - return result; - } + ReturnDictionary(dict); + return result; + } - private static UInt128[] Factor128(UInt128 n, CancellationToken ct) - { - if (n == 1) - { - return []; - } + private static UInt128[] Factor128(UInt128 n, CancellationToken ct) + { + if (n == 1) + { + return []; + } - var dict = RentDictionary128(); - var queue = new Queue(); // TODO: Replace this with the pooled UInt128 stack once the - // wide-factorization benchmarks finalize the faster span - // based traversal. - queue.Enqueue(n); + var dict = RentDictionary128(); + var queue = new Queue(); // TODO: Replace this with the pooled UInt128 stack once the + // wide-factorization benchmarks finalize the faster span + // based traversal. + queue.Enqueue(n); - while (queue.Count != 0) - { - if (ct.IsCancellationRequested) - { - break; - } + while (queue.Count != 0) + { + if (ct.IsCancellationRequested) + { + break; + } - UInt128 m = queue.Dequeue(); - if (m == 1) - { - continue; - } + UInt128 m = queue.Dequeue(); + if (m == 1) + { + continue; + } - if (IsPrime128(m, ct)) - { - dict[m] = 1; - continue; - } + if (IsPrime128(m, ct)) + { + dict[m] = 1; + continue; + } - if (ct.IsCancellationRequested) - { - break; - } + if (ct.IsCancellationRequested) + { + break; + } - UInt128 d = PollardRho128(m, ct); - if (ct.IsCancellationRequested) - { - break; - } + UInt128 d = PollardRho128(m, ct); + if (ct.IsCancellationRequested) + { + break; + } - queue.Enqueue(d); - queue.Enqueue(m / d); - } + queue.Enqueue(d); + queue.Enqueue(m / d); + } var result = ThreadStaticPools.UInt128Pool.Rent(dict.Count); - int index = 0; - foreach (var key in dict.Keys) - { - result[index++] = key; - } + int index = 0; + foreach (var key in dict.Keys) + { + result[index++] = key; + } - ReturnDictionary128(dict); - return result; - } + ReturnDictionary128(dict); + return result; + } - private static ulong PollardRho(ulong n, CancellationToken ct) - { - if ((n & 1UL) == 0UL) - { - return 2UL; - } + private static ulong PollardRho(ulong n, CancellationToken ct) + { + if ((n & 1UL) == 0UL) + { + return 2UL; + } - ulong c, d, x, y, diff; - while (true) - { - if (ct.IsCancellationRequested) - { - return 0UL; - } + ulong c, d, x, y, diff; + while (true) + { + if (ct.IsCancellationRequested) + { + return 0UL; + } - c = (DeterministicRandomCpu.NextUInt64() % (n - 1UL)) + 1UL; - x = (DeterministicRandomCpu.NextUInt64() % (n - 2UL)) + 2UL; - y = x; - d = 1UL; + c = (DeterministicRandomCpu.NextUInt64() % (n - 1UL)) + 1UL; + x = (DeterministicRandomCpu.NextUInt64() % (n - 2UL)) + 2UL; + y = x; + d = 1UL; - while (d == 1UL) - { - if (ct.IsCancellationRequested) - { - return 0UL; - } + while (d == 1UL) + { + if (ct.IsCancellationRequested) + { + return 0UL; + } - x = x.MulMod64(x, n).AddMod64(c, n); - if (ct.IsCancellationRequested) - { - return 0UL; - } + x = x.MulMod64(x, n).AddMod64(c, n); + y = y.MulMod64(y, n).AddMod64(c, n); + y = y.MulMod64(y, n).AddMod64(c, n); + diff = x > y ? x - y : y - x; + d = Gcd(diff, n, ct); + } - y = y.MulMod64(y, n).AddMod64(c, n); - if (ct.IsCancellationRequested) - { - return 0UL; - } + if (d != n) + { + return d; + } + } + } - y = y.MulMod64(y, n).AddMod64(c, n); - diff = x > y ? x - y : y - x; - if (ct.IsCancellationRequested) - { - return 0UL; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static ulong Gcd(ulong a, ulong b, CancellationToken ct) + { + ulong t; + while (b != 0UL) + { + if (ct.IsCancellationRequested) + { + return 1UL; // We're returning 1 to avoid division by 0 in any place + } - d = Gcd(diff, n, ct); - } + t = a % b; // TODO: Replace this with the binary GCD helper used in the gcd benchmarks so we + // avoid slow modulo operations while factoring large composites. + a = b; + b = t; + } - if (d != n) - { - return d; - } - } - } + return a; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong Gcd(ulong a, ulong b, CancellationToken ct) - { - ulong t; - while (b != 0UL) - { - if (ct.IsCancellationRequested) - { - return 1UL; // We're returning 1 to avoid division by 0 in any place - } + // TODO: Port this Miller–Rabin routine to UInt128 intrinsics so we avoid the BigInteger.ModPow calls that lag by orders of magnitude on the large inputs highlighted in the Pow2MontgomeryMod benchmarks. + private static bool IsPrime128(UInt128 n, CancellationToken ct) + { + if (n <= 3) + { + return n >= 2; + } - t = a % b; // TODO: Replace this with the binary GCD helper used in the gcd benchmarks so we - // avoid slow modulo operations while factoring large composites. - a = b; - b = t; - } + if ((n & 1) == 0) + { + return false; + } - return a; - } + BigInteger bn = (BigInteger)n; + BigInteger d = bn - 1; + int s = 0; + while ((d & 1) == 0) + { + d >>= 1; + s++; + } - // TODO: Port this Miller–Rabin routine to UInt128 intrinsics so we avoid the BigInteger.ModPow calls that lag by orders of magnitude on the large inputs highlighted in the Pow2MontgomeryMod benchmarks. - private static bool IsPrime128(UInt128 n, CancellationToken ct) - { - if (n <= 3) - { - return n >= 2; - } + BigInteger[] bases = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]; + for (int i = 0; i < bases.Length; i++) + { + BigInteger a = bases[i]; + if (a >= bn) + { + continue; + } - if ((n & 1) == 0) - { - return false; - } + if (ct.IsCancellationRequested) + { + return false; + } - BigInteger bn = (BigInteger)n; - BigInteger d = bn - 1; - int s = 0; - while ((d & 1) == 0) - { - d >>= 1; - s++; - } + BigInteger x = BigInteger.ModPow(a, d, bn); + if (x == 1 || x == bn - 1) + { + continue; + } - BigInteger[] bases = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37]; - for (int i = 0; i < bases.Length; i++) - { - BigInteger a = bases[i]; - if (a >= bn) - { - continue; - } + bool cont = false; + for (int r = 1; r < s; r++) + { + if (ct.IsCancellationRequested) + { + return false; + } - if (ct.IsCancellationRequested) - { - return false; - } + x = BigInteger.ModPow(x, 2, bn); + if (x == bn - 1) + { + cont = true; + break; + } + } - BigInteger x = BigInteger.ModPow(a, d, bn); - if (x == 1 || x == bn - 1) - { - continue; - } + if (!cont) + { + return false; + } + } - bool cont = false; - for (int r = 1; r < s; r++) - { - if (ct.IsCancellationRequested) - { - return false; - } + return true; + } - x = BigInteger.ModPow(x, 2, bn); - if (x == bn - 1) - { - cont = true; - break; - } - } + private static UInt128 PollardRho128(UInt128 n, CancellationToken ct) + { + if ((n & 1) == 0) + { + return 2; + } - if (!cont) - { - return false; - } - } + UInt128 c, d, x, y, diff; + while (true) + { + if (ct.IsCancellationRequested) + { + return 0; + } - return true; - } + c = (DeterministicRandomCpu.NextUInt128() % (n - 1)) + 1; + x = (DeterministicRandomCpu.NextUInt128() % (n - 2)) + 2; + y = x; + d = 1; - private static UInt128 PollardRho128(UInt128 n, CancellationToken ct) - { - if ((n & 1) == 0) - { - return 2; - } + while (d == 1) + { + if (ct.IsCancellationRequested) + { + return 0; + } - UInt128 c, d, x, y, diff; - while (true) - { - if (ct.IsCancellationRequested) - { - return 0; - } + x = x.MulMod(x, n).AddMod(c, n); + if (ct.IsCancellationRequested) + { + return 0; + } - c = (DeterministicRandomCpu.NextUInt128() % (n - 1)) + 1; - x = (DeterministicRandomCpu.NextUInt128() % (n - 2)) + 2; - y = x; - d = 1; + y = y.MulMod(y, n).AddMod(c, n); + if (ct.IsCancellationRequested) + { + return 0; + } - while (d == 1) - { - if (ct.IsCancellationRequested) - { - return 0; - } + y = y.MulMod(y, n).AddMod(c, n); + diff = x > y ? x - y : y - x; + if (ct.IsCancellationRequested) + { + return 0; + } - x = x.MulMod(x, n).AddMod(c, n); - if (ct.IsCancellationRequested) - { - return 0; - } + d = Gcd128(diff, n, ct); // TODO: Move this to the binary GCD implementation once the + // UInt128 benchmarks validate the optimized routine. + } - y = y.MulMod(y, n).AddMod(c, n); - if (ct.IsCancellationRequested) - { - return 0; - } + if (d != n) + { + return d; + } + } + } - y = y.MulMod(y, n).AddMod(c, n); - diff = x > y ? x - y : y - x; - if (ct.IsCancellationRequested) - { - return 0; - } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static UInt128 Gcd128(UInt128 a, UInt128 b, CancellationToken ct) + { + UInt128 t; + while (b != 0) + { + if (ct.IsCancellationRequested) + { + return 1; + } - d = Gcd128(diff, n, ct); // TODO: Move this to the binary GCD implementation once the - // UInt128 benchmarks validate the optimized routine. - } + t = a % b; // TODO: Replace with the binary GCD helper once the UInt128 benchmarks land so + // we remove the slow `%` from the wide gcd loop. + a = b; + b = t; + } - if (d != n) - { - return d; - } - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static UInt128 Gcd128(UInt128 a, UInt128 b, CancellationToken ct) - { - UInt128 t; - while (b != 0) - { - if (ct.IsCancellationRequested) - { - return 1; - } - - t = a % b; // TODO: Replace with the binary GCD helper once the UInt128 benchmarks land so - // we remove the slow `%` from the wide gcd loop. - a = b; - b = t; - } - - return a; - } + return a; + } } diff --git a/PerfectNumbers.Core/MontgomeryDivisorDataPool.cs b/PerfectNumbers.Core/MontgomeryDivisorDataPool.cs index 6e6b80a..e66a6a4 100644 --- a/PerfectNumbers.Core/MontgomeryDivisorDataPool.cs +++ b/PerfectNumbers.Core/MontgomeryDivisorDataPool.cs @@ -5,29 +5,34 @@ namespace PerfectNumbers.Core; public static class MontgomeryDivisorDataPool { [ThreadStatic] - private static Queue? _pool; - public static Queue Shared => _pool ??= new(); + private static FixedCapacityStack? _pool; + public static FixedCapacityStack Shared + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _pool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static ulong ComputeMontgomeryResidue(UInt128 value, ulong modulus) => (ulong)(value % modulus); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private static ulong ComputeMontgomeryResidue(UInt128 value, ulong modulus) => (ulong)(value % modulus); - private static ulong ComputeMontgomeryNPrime(ulong modulus) - { - ulong inv = modulus; - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - inv *= unchecked(2UL - modulus * inv); - return unchecked(0UL - inv); - } - + private static ulong ComputeMontgomeryNPrime(ulong modulus) + { + ulong inv = modulus; + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + inv *= unchecked(2UL - modulus * inv); + return unchecked(0UL - inv); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static MontgomeryDivisorData Rent(ulong modulus, ulong nPrime, ulong montgomeryOne, ulong montgomeryTwo, ulong montgomeryTwoSquared) { - var pool = _pool ??= new(); - if (pool.TryDequeue(out var pooled)) + var pool = _pool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + if (pool.Pop() is { } pooled) { pooled.Modulus = modulus; pooled.NPrime = nPrime; @@ -40,30 +45,30 @@ public static class MontgomeryDivisorDataPool return new(modulus, nPrime, montgomeryOne, montgomeryTwo, montgomeryTwoSquared); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(MontgomeryDivisorData divisorData) => _pool!.Enqueue(divisorData); - + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Return(MontgomeryDivisorData divisorData) => _pool!.Push(divisorData); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MontgomeryDivisorData FromModulus(this Queue queue, ulong modulus) - { - ulong nPrime = ComputeMontgomeryNPrime(modulus); - ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus); - ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus); - ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime); - return queue.Rent( - modulus, - nPrime, - montgomeryOne, - montgomeryTwo, - montgomeryTwoSquared); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static MontgomeryDivisorData Rent(this Queue queue, ulong modulus, ulong nPrime, ulong montgomeryOne, ulong montgomeryTwo, ulong montgomeryTwoSquared) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static MontgomeryDivisorData FromModulus(this FixedCapacityStack queue, ulong modulus) { - if (queue.TryDequeue(out var pooled)) + ulong nPrime = ComputeMontgomeryNPrime(modulus); + ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus); + ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus); + ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiplyCpu(montgomeryTwo, montgomeryTwo, modulus, nPrime); + + return queue.Rent( + modulus, + nPrime, + montgomeryOne, + montgomeryTwo, + montgomeryTwoSquared); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static MontgomeryDivisorData Rent(this FixedCapacityStack queue, ulong modulus, ulong nPrime, ulong montgomeryOne, ulong montgomeryTwo, ulong montgomeryTwoSquared) + { + if (queue.Pop() is { } pooled) { pooled.Modulus = modulus; pooled.NPrime = nPrime; @@ -76,6 +81,6 @@ public static class MontgomeryDivisorDataPool return new(modulus, nPrime, montgomeryOne, montgomeryTwo, montgomeryTwoSquared); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(this Queue queue, MontgomeryDivisorData divisorData) => queue.Enqueue(divisorData); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Return(this FixedCapacityStack queue, MontgomeryDivisorData divisorData) => queue.Push(divisorData); } diff --git a/PerfectNumbers.Core/PartialFactorResult.cs b/PerfectNumbers.Core/PartialFactorResult.cs index b711f69..c19aefd 100644 --- a/PerfectNumbers.Core/PartialFactorResult.cs +++ b/PerfectNumbers.Core/PartialFactorResult.cs @@ -1,3 +1,5 @@ +using System.Numerics; + namespace PerfectNumbers.Core { public sealed class PartialFactorResult @@ -77,7 +79,7 @@ namespace PerfectNumbers.Core return; } - int newSize = sourceCount + 1; + int newSize = (int)BitOperations.RoundUpToPowerOf2((uint)sourceCount + 1U); ulong[] extendedFactors = sourceFactors.Length >= newSize ? sourceFactors : ResizeFactors(sourceFactors, sourceCount, newSize); @@ -87,6 +89,7 @@ namespace PerfectNumbers.Core ? sourceExponents : ResizeExponents(sourceCount, newSize, sourceExponents); + newSize = sourceCount + 1; extendedFactors[sourceCount] = prime; extendedExponents[sourceCount] = 1; Array.Sort(extendedFactors, extendedExponents, 0, newSize); @@ -101,24 +104,24 @@ namespace PerfectNumbers.Core private static int[] ResizeExponents(int sourceCount, int newSize, in int[] sourceExponents) { - int[] extendedExponents = Pools.ExclusiveIntArray.Rent(newSize); + int[] extendedExponents = FixedCapacityPools.ExclusiveIntArray.Rent(newSize); Array.Copy(sourceExponents, 0, extendedExponents, 0, sourceCount); - Pools.ExclusiveIntArray.Return(sourceExponents); + FixedCapacityPools.ExclusiveIntArray.Return(sourceExponents); return extendedExponents; } private static ulong[] ResizeFactors(in ulong[] sourceFactors, int sourceCount, int newSize) { - ulong[] extendedFactors = Pools.ExclusiveUlongArray.Rent(newSize); + ulong[] extendedFactors = FixedCapacityPools.ExclusiveUlongArray.Rent(newSize); Array.Copy(sourceFactors, 0, extendedFactors, 0, sourceCount); - Pools.ExclusiveUlongArray.Return(sourceFactors); + FixedCapacityPools.ExclusiveUlongArray.Return(sourceFactors); return extendedFactors; } private void InitializeWithPrime(ulong prime) { - ulong[] factors = Pools.ExclusiveUlongArray.Rent(1); - int[] exponents = Pools.ExclusiveIntArray.Rent(1); + ulong[] factors = FixedCapacityPools.ExclusiveUlongArray.Rent(1); + int[] exponents = FixedCapacityPools.ExclusiveIntArray.Rent(1); factors[0] = prime; exponents[0] = 1; Factors = factors; @@ -142,8 +145,8 @@ namespace PerfectNumbers.Core Factors = null; Exponents = null; - Pools.ExclusiveUlongArray.Return(factors); - Pools.ExclusiveIntArray.Return(exponents); + FixedCapacityPools.ExclusiveUlongArray.Return(factors); + FixedCapacityPools.ExclusiveIntArray.Return(exponents); } _next = s_poolHead; diff --git a/PerfectNumbers.Core/PerfectNumberConstants.cs b/PerfectNumbers.Core/PerfectNumberConstants.cs index 2f8dadb..9652e67 100644 --- a/PerfectNumbers.Core/PerfectNumberConstants.cs +++ b/PerfectNumbers.Core/PerfectNumberConstants.cs @@ -7,13 +7,16 @@ public static class PerfectNumberConstants public const ulong BiggestKnownEvenPerfectP = 136279841UL; public const int ConsoleInterval = 100_000; public const int DefaultFactorsBuffer = 32; + public const int DefaultPoolCapacity = 10_240; public const int DefaultSmallPrimeFactorSlotCount = 64; + public const int DefaultThreadPoolCapacity = 131_072; public const int DefaultSpecialMaxFactorCapacity = 1024; public const ulong ExtraDivisorCycleSearchLimit = 64UL; public static int GpuRatio = 16384 - 1; public const int MaxQForDivisorCycles = 4_000_000; public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000; - public const int MaxOddPowersCount = 128; + public const int DefaultStringBuilderCapacity = 10_240; + public const int MaxOddPowersCount = 128; public const int PooledArrayThreshold = 64; public const int Pow2WindowSize = 8; public static int RollingAccelerators = 298; //SharedGpuContext.Device.MaxNumThreadsPerGroup; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 64ab716..95be940 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -108,7 +108,7 @@ internal static partial class PrimeOrderCalculator } ulong prime64 = (ulong)prime; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; divisorData = divisorPool.FromModulus(prime64); ulong order64 = CalculateCpu(prime64, previous, divisorData, config); divisorPool.Return(divisorData); @@ -226,7 +226,7 @@ internal static partial class PrimeOrderCalculator private static UInt128 FinishStrictlyWideCpu(in UInt128 prime, in MontgomeryDivisorData? divisorData) { UInt128 phi = prime - UInt128.One; - Queue> uInt128IntDictionaryPool = ThreadStaticPools.UInt128IntDictionaryPool; + FixedCapacityStack> uInt128IntDictionaryPool = ThreadStaticPools.UInt128IntDictionaryPool; Dictionary counts = uInt128IntDictionaryPool.Rent(capacity: 8); FactorCompletelyWide(phi, counts); int entryCount = counts.Count; @@ -236,7 +236,7 @@ internal static partial class PrimeOrderCalculator return phi; } - Queue>> keyValuePairUInt128IntegerPool = ThreadStaticPools.KeyValuePairUInt128IntegerPool; + FixedCapacityStack>> keyValuePairUInt128IntegerPool = ThreadStaticPools.KeyValuePairUInt128IntegerPool; var entries = keyValuePairUInt128IntegerPool.Rent(entryCount); entries.AddRange(counts); uInt128IntDictionaryPool.Return(counts); @@ -446,7 +446,7 @@ internal static partial class PrimeOrderCalculator } int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecksCapacity; - Queue> uInt128ListPool = ThreadStaticPools.UInt128ListPool; + FixedCapacityStack> uInt128ListPool = ThreadStaticPools.UInt128ListPool; List candidates = uInt128ListPool.Rent(capacity); FactorEntry128[] factorArray = orderFactors.Factors!; @@ -580,8 +580,8 @@ internal static partial class PrimeOrderCalculator int length = factors.Count; int newLength = length + 1; - ulong[] tempFactorsArray = Pools.ExclusiveUlongArray.Rent(newLength); - int[] tempExponentsArray = Pools.ExclusiveIntArray.Rent(newLength); + ulong[] tempFactorsArray = FixedCapacityPools.ExclusiveUlongArray.Rent(newLength); + int[] tempExponentsArray = FixedCapacityPools.ExclusiveIntArray.Rent(newLength); Span ulongBuffer = tempFactorsArray; factorSpan.CopyTo(ulongBuffer); @@ -629,7 +629,7 @@ internal static partial class PrimeOrderCalculator continue; } - heapCandidateArray ??= Pools.ExclusiveUlongArray.Rent(ExponentHardLimit); + heapCandidateArray ??= FixedCapacityPools.ExclusiveUlongArray.Rent(ExponentHardLimit); heapEvaluationArray ??= ThreadStaticPools.BoolPool.Rent(ExponentHardLimit); ProcessExponentLoweringPrime(heapCandidateArray.AsSpan(0, exponent), heapEvaluationArray.AsSpan(0, exponent), ref order, primeFactor, exponent, ref stepper); } @@ -638,11 +638,11 @@ internal static partial class PrimeOrderCalculator if (heapCandidateArray is not null) { - Pools.ExclusiveUlongArray.Return(heapCandidateArray); + FixedCapacityPools.ExclusiveUlongArray.Return(heapCandidateArray); ThreadStaticPools.BoolPool.Return(heapEvaluationArray!); } - Pools.ExclusiveUlongArray.Return(tempFactorsArray); + FixedCapacityPools.ExclusiveUlongArray.Return(tempFactorsArray); return order; } @@ -789,7 +789,7 @@ internal static partial class PrimeOrderCalculator continue; } - heapCandidateArray ??= Pools.ExclusiveUlongArray.Rent(ExponentHardLimit); + heapCandidateArray ??= FixedCapacityPools.ExclusiveUlongArray.Rent(ExponentHardLimit); if (ValidateOrderForFactor(heapCandidateArray.AsSpan(0, exponent), primeFactor, exponent, order, ref stepper)) { violates = true; @@ -801,7 +801,7 @@ internal static partial class PrimeOrderCalculator if (heapCandidateArray is not null) { - Pools.ExclusiveUlongArray.Return(heapCandidateArray); + FixedCapacityPools.ExclusiveUlongArray.Return(heapCandidateArray); } return !violates; @@ -1229,7 +1229,7 @@ internal static partial class PrimeOrderCalculator continue; } - heapCandidateArray ??= Pools.ExclusiveUlongArray.Rent(ExponentHardLimit); + heapCandidateArray ??= FixedCapacityPools.ExclusiveUlongArray.Rent(ExponentHardLimit); if (CheckCandidateViolation(heapCandidateArray.AsSpan(0, exponent), primeFactor, exponent, candidate, prime, ref powUsed, powBudget, ref stepper)) { violates = true; @@ -1241,7 +1241,7 @@ internal static partial class PrimeOrderCalculator if (heapCandidateArray is not null) { - Pools.ExclusiveUlongArray.Return(heapCandidateArray); + FixedCapacityPools.ExclusiveUlongArray.Return(heapCandidateArray); } factorization.Dispose(); @@ -1334,9 +1334,6 @@ internal static partial class PrimeOrderCalculator private static bool Pow2EqualsOneCpu(ulong exponent, in MontgomeryDivisorData divisorData) => exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL; - private static readonly ulong[] _emptyUlongArray = []; - private static readonly int[] _emptyIntArray = []; - [MethodImpl(MethodImplOptions.AggressiveOptimization)] private static PartialFactorResult PartialFactorCpu(ulong value, in PrimeOrderCalculatorConfig config) { @@ -1369,7 +1366,7 @@ internal static partial class PrimeOrderCalculator if (config.PollardRhoMilliseconds > 0) { long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); - Stack compositeStack = ThreadStaticPools.RentUlongStack(PrimeOrderConstants.GpuSmallPrimeFactorSlots); + FixedCapacityStack compositeStack = ThreadStaticPools.RentUlongStack(PrimeOrderConstants.GpuSmallPrimeFactorSlots); compositeStack.Push(remaining); CollectFactorsCpu(primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); @@ -1395,6 +1392,11 @@ internal static partial class PrimeOrderCalculator bool cofactorContainsComposite = false; int pendingCount = pending.Count; int index = 0; + if (pending.Capacity > PrimeOrderConstants.GpuSmallPrimeFactorSlots) + { + Console.WriteLine($"Initial capacity is too small. Required capacity: {pending.Capacity}"); + } + for (; index < pendingCount; index++) { PendingEntry entry = pending[index]; @@ -1470,8 +1472,8 @@ internal static partial class PrimeOrderCalculator // goto ReturnResult; // } - ulong[] factorsArray = Pools.ExclusiveUlongArray.Rent(factorCount); - int[] exponentsArray = Pools.ExclusiveIntArray.Rent(factorCount); + ulong[] factorsArray = FixedCapacityPools.ExclusiveUlongArray.Rent(factorCount); + int[] exponentsArray = FixedCapacityPools.ExclusiveIntArray.Rent(factorCount); for (int i = 0; i < factorCount; i++) { @@ -1500,7 +1502,7 @@ internal static partial class PrimeOrderCalculator } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void CollectFactorsCpu(Span primeSlots, Span exponentSlots, ref int factorCount, List pending, Stack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) + private static void CollectFactorsCpu(Span primeSlots, Span exponentSlots, ref int factorCount, List pending, FixedCapacityStack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) { while (compositeStack.Count > 0) { diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs index 9ef5553..c2349b6 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs @@ -117,7 +117,7 @@ internal static partial class PrimeOrderCalculator } ulong prime64 = (ulong)prime; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; divisorData = divisorPool.FromModulus(prime64); ulong order64 = CalculateGpu(gpu, prime64, previous, divisorData, config); divisorPool.Return(divisorData); @@ -377,7 +377,7 @@ internal static partial class PrimeOrderCalculator if (config.PollardRhoMilliseconds > 0) { long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); - Stack compositeStack = ThreadStaticPools.RentUlongStack(4); + FixedCapacityStack compositeStack = ThreadStaticPools.RentUlongStack(4); compositeStack.Push(remaining); CollectFactorsCpu(primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs index 0e04b77..fb3f1e6 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs @@ -67,7 +67,7 @@ internal static partial class PrimeOrderCalculator } ulong prime64 = (ulong)prime; - Queue divisorPool = MontgomeryDivisorDataPool.Shared; + FixedCapacityStack divisorPool = MontgomeryDivisorDataPool.Shared; divisorData = divisorPool.FromModulus(prime64); ulong order64 = CalculateHybrid(gpu, prime64, previous, divisorData, config); divisorPool.Return(divisorData); @@ -83,7 +83,7 @@ internal static partial class PrimeOrderCalculator } [MethodImpl(MethodImplOptions.AggressiveOptimization)] - private static void CollectFactorsHybrid(PrimeOrderCalculatorAccelerator gpu, Span primeSlots, Span exponentSlots, ref ulong[] primeSlotArray, ref int[] exponentSlotArray, ref int factorCount, List pending, Stack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) + private static void CollectFactorsHybrid(PrimeOrderCalculatorAccelerator gpu, Span primeSlots, Span exponentSlots, ref int factorCount, List pending, FixedCapacityStack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) { while (compositeStack.Count > 0) { @@ -169,10 +169,10 @@ internal static partial class PrimeOrderCalculator if (config.PollardRhoMilliseconds > 0) { long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); - Stack compositeStack = ThreadStaticPools.RentUlongStack(4); + FixedCapacityStack compositeStack = ThreadStaticPools.RentUlongStack(4); compositeStack.Push(remaining); - CollectFactorsHybrid(gpu, primeSlots, exponentSlots, primeSlotArray: ref primeSlotArray, ref exponentSlotArray, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); + CollectFactorsHybrid(gpu, primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); if (limitReached) { diff --git a/PerfectNumbers.Core/StringBuilderPool.cs b/PerfectNumbers.Core/StringBuilderPool.cs index f5f6f10..fc04d9e 100644 --- a/PerfectNumbers.Core/StringBuilderPool.cs +++ b/PerfectNumbers.Core/StringBuilderPool.cs @@ -1,23 +1,21 @@ -using System.Collections.Concurrent; +using System.Runtime.CompilerServices; using System.Text; namespace PerfectNumbers.Core; public static class StringBuilderPool { - private static readonly ConcurrentQueue _stringBuilderPool = new(); + private static readonly ConcurrentFixedCapacityStack _stringBuilderPool = new(PerfectNumberConstants.DefaultPoolCapacity); - public static StringBuilder Rent() - { - // TODO: Ensure oversized builders remain eligible for reuse without trimming so call sites keep their full capacity. - return _stringBuilderPool.TryDequeue(out var sb) ? sb : new(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static StringBuilder Rent() => _stringBuilderPool.Pop() is { } sb ? sb : new(PerfectNumberConstants.DefaultStringBuilderCapacity); - public static void Return(StringBuilder sb) - { - _ = sb.Clear(); - // TODO: Preserve the builder's capacity when returning it so the pool hands the same buffer back without shrinkage. - _stringBuilderPool.Enqueue(sb); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Return(StringBuilder sb) + { + _ = sb.Clear(); + // TODO: Preserve the builder's capacity when returning it so the pool hands the same buffer back without shrinkage. + _stringBuilderPool.Push(sb); + } } diff --git a/PerfectNumbers.Core/TaskThreadPool.cs b/PerfectNumbers.Core/TaskThreadPool.cs index aee115a..ff67920 100644 --- a/PerfectNumbers.Core/TaskThreadPool.cs +++ b/PerfectNumbers.Core/TaskThreadPool.cs @@ -1,241 +1,233 @@ using System.Collections.Concurrent; +using System.Runtime.CompilerServices; namespace PerfectNumbers.Core; internal sealed class TaskThreadPool { - private readonly struct WorkItem - { - public WorkItem(Task task) - { - Task = task; - } + private readonly struct WorkItem(Task task) + { + public readonly Task Task = task; + } - public Task Task { get; } - } + private readonly ConcurrentFixedCapacityStack _pendingTaskQueue = new(PerfectNumberConstants.DefaultThreadPoolCapacity); + private readonly HashSet _runningThreads = []; + private readonly object _lock = new(); + private readonly Action _taskExecutor; + private readonly int _maximumConcurrency; + private readonly SemaphoreSlim _workAvailable = new(0); + private readonly CancellationTokenSource _disposeCancellation = new(); + private int _pendingTaskCount; + private int _currentThreads; + private int _threadId; - private readonly ConcurrentQueue _pendingTaskQueue = new(); - private readonly HashSet _runningThreads = new(); - private readonly object _lock = new(); - private readonly Action _taskExecutor; - private readonly int _maximumConcurrency; - private readonly SemaphoreSlim _workAvailable = new(0); - private readonly CancellationTokenSource _disposeCancellation = new(); - private int _pendingTaskCount; - private int _currentThreads; - private int _threadId; + public TaskThreadPool(int minimumThreads, Action taskExecutor) + { + if (minimumThreads <= 0) + { + throw new ArgumentOutOfRangeException(nameof(minimumThreads)); + } - public TaskThreadPool(int minimumThreads, Action taskExecutor) - { - if (minimumThreads <= 0) - { - throw new ArgumentOutOfRangeException(nameof(minimumThreads)); - } + _taskExecutor = taskExecutor ?? throw new ArgumentNullException(nameof(taskExecutor)); + _maximumConcurrency = minimumThreads; - _taskExecutor = taskExecutor ?? throw new ArgumentNullException(nameof(taskExecutor)); - _maximumConcurrency = minimumThreads; + List threadsToStart = new(minimumThreads); - List threadsToStart = new(minimumThreads); + lock (_lock) + { + for (int i = 0; i < minimumThreads; i++) + { + Thread thread = CreateWorkerThread(); + _runningThreads.Add(thread); + _currentThreads++; + threadsToStart.Add(thread); + } + } - lock (_lock) - { - for (int i = 0; i < minimumThreads; i++) - { - Thread thread = CreateWorkerThread(); - _runningThreads.Add(thread); - _currentThreads++; - threadsToStart.Add(thread); - } - } + foreach (Thread thread in threadsToStart) + { + thread.Start(); + } + } - foreach (Thread thread in threadsToStart) - { - thread.Start(); - } - } + public int ThreadCount => _maximumConcurrency; - public int ThreadCount => _maximumConcurrency; + public void Queue(Task task) + { + ArgumentNullException.ThrowIfNull(task); - public void Queue(Task task) - { - if (task is null) - { - throw new ArgumentNullException(nameof(task)); - } + List? threadsToStart; - List? threadsToStart; + lock (_lock) + { + ObjectDisposedException.ThrowIf(_disposeCancellation.IsCancellationRequested, nameof(TaskThreadPool)); - lock (_lock) - { - if (_disposeCancellation.IsCancellationRequested) - { - throw new ObjectDisposedException(nameof(TaskThreadPool)); - } + _pendingTaskQueue.Push(new WorkItem(task)); + _pendingTaskCount++; - _pendingTaskQueue.Enqueue(new WorkItem(task)); - _pendingTaskCount++; + threadsToStart = EnsureWorkerCapacityLocked(null); + } - threadsToStart = EnsureWorkerCapacityLocked(null); - } + _workAvailable.Release(); - _workAvailable.Release(); + if (threadsToStart is not null) + { + foreach (Thread thread in threadsToStart) + { + thread.Start(); + } + } + } - if (threadsToStart is not null) - { - foreach (Thread thread in threadsToStart) - { - thread.Start(); - } - } - } + public IEnumerable GetScheduledTasks() + { + WorkItem[] snapshot = _pendingTaskQueue.ToArray(); - public IEnumerable GetScheduledTasks() - { - WorkItem[] snapshot = _pendingTaskQueue.ToArray(); + if (snapshot.Length == 0) + { + return []; + } - if (snapshot.Length == 0) - { - return Array.Empty(); - } + Task[] tasks = new Task[snapshot.Length]; - Task[] tasks = new Task[snapshot.Length]; + for (int i = 0; i < snapshot.Length; i++) + { + tasks[i] = snapshot[i].Task; + } - for (int i = 0; i < snapshot.Length; i++) - { - tasks[i] = snapshot[i].Task; - } + ConcurrentFixedCapacityStack.Return(snapshot); + return tasks; + } - return tasks; - } + public void Dispose() + { + Thread[] threadsToJoin; - public void Dispose() - { - Thread[] threadsToJoin; + lock (_lock) + { + _disposeCancellation.Cancel(); - lock (_lock) - { - _disposeCancellation.Cancel(); + threadsToJoin = new Thread[_runningThreads.Count]; + _runningThreads.CopyTo(threadsToJoin); + } - threadsToJoin = new Thread[_runningThreads.Count]; - _runningThreads.CopyTo(threadsToJoin); - } + if (threadsToJoin.Length > 0) + { + _workAvailable.Release(threadsToJoin.Length); + } - if (threadsToJoin.Length > 0) - { - _workAvailable.Release(threadsToJoin.Length); - } + foreach (Thread thread in threadsToJoin) + { + thread.Join(); + } - foreach (Thread thread in threadsToJoin) - { - thread.Join(); - } + _workAvailable.Dispose(); + _disposeCancellation.Dispose(); - _workAvailable.Dispose(); - _disposeCancellation.Dispose(); + lock (_lock) + { + _runningThreads.Clear(); - lock (_lock) - { - _runningThreads.Clear(); + while (_pendingTaskQueue.Pop() is { }) + { + } - while (_pendingTaskQueue.TryDequeue(out _)) - { - } + _pendingTaskCount = 0; + _currentThreads = 0; + } + } - _pendingTaskCount = 0; - _currentThreads = 0; - } - } + private bool TryDequeueWork(out WorkItem workItem) + { + CancellationToken cancellationToken = _disposeCancellation.Token; - private bool TryDequeueWork(out WorkItem workItem) - { - CancellationToken cancellationToken = _disposeCancellation.Token; + while (true) + { + try + { + _workAvailable.Wait(cancellationToken); + } + catch (OperationCanceledException) + { + workItem = default; + return false; + } - while (true) - { - try - { - _workAvailable.Wait(cancellationToken); - } - catch (OperationCanceledException) - { - workItem = default; - return false; - } + workItem = _pendingTaskQueue.Pop(); + if (workItem is { }) + { + Interlocked.Decrement(ref _pendingTaskCount); + return true; + } + } + } - if (_pendingTaskQueue.TryDequeue(out workItem)) - { - Interlocked.Decrement(ref _pendingTaskCount); - return true; - } - } - } + private void WorkerLoop() + { + try + { + if (!TryDequeueWork(out WorkItem workItem)) + { + return; + } - private void WorkerLoop() - { - try - { - if (!TryDequeueWork(out WorkItem workItem)) - { - return; - } + _taskExecutor(workItem.Task); + } + finally + { + CompleteWorker(); + } + } - _taskExecutor(workItem.Task); - } - finally - { - CompleteWorker(); - } - } + private void CompleteWorker() + { + List? threadsToStart = null; - private void CompleteWorker() - { - List? threadsToStart = null; + lock (_lock) + { + _runningThreads.Remove(Thread.CurrentThread); + _currentThreads--; - lock (_lock) - { - _runningThreads.Remove(Thread.CurrentThread); - _currentThreads--; + if (!_disposeCancellation.IsCancellationRequested) + { + threadsToStart = EnsureWorkerCapacityLocked(null); + } + } - if (!_disposeCancellation.IsCancellationRequested) - { - threadsToStart = EnsureWorkerCapacityLocked(null); - } - } + if (threadsToStart is not null) + { + foreach (Thread thread in threadsToStart) + { + thread.Start(); + } + } + } - if (threadsToStart is not null) - { - foreach (Thread thread in threadsToStart) - { - thread.Start(); - } - } - } + private List? EnsureWorkerCapacityLocked(List? threadsToStart) + { + int pendingTasks = Math.Min(_maximumConcurrency, Volatile.Read(ref _pendingTaskCount)); - private List? EnsureWorkerCapacityLocked(List? threadsToStart) - { - int pendingTasks = Math.Min(_maximumConcurrency, Volatile.Read(ref _pendingTaskCount)); + while (!_disposeCancellation.IsCancellationRequested && _currentThreads < pendingTasks) + { + Thread thread = CreateWorkerThread(); + _runningThreads.Add(thread); + _currentThreads++; - while (!_disposeCancellation.IsCancellationRequested && _currentThreads < pendingTasks) - { - Thread thread = CreateWorkerThread(); - _runningThreads.Add(thread); - _currentThreads++; + threadsToStart ??= []; + threadsToStart.Add(thread); + } - threadsToStart ??= new List(); - threadsToStart.Add(thread); - } + return threadsToStart; + } - return threadsToStart; - } + private Thread CreateWorkerThread() + { + int threadIndex = Interlocked.Increment(ref _threadId); - private Thread CreateWorkerThread() - { - int threadIndex = Interlocked.Increment(ref _threadId); - - return new Thread(WorkerLoop) - { - IsBackground = true, - Name = $"{nameof(TaskThreadPool)}-{threadIndex}" - }; - } + return new Thread(WorkerLoop) + { + IsBackground = true, + Name = $"{nameof(TaskThreadPool)}-{threadIndex}" + }; + } } diff --git a/PerfectNumbers.Core/ThreadStaticPools.cs b/PerfectNumbers.Core/ThreadStaticPools.cs index 64ed8f1..598eab4 100644 --- a/PerfectNumbers.Core/ThreadStaticPools.cs +++ b/PerfectNumbers.Core/ThreadStaticPools.cs @@ -6,40 +6,34 @@ using PeterO.Numbers; namespace PerfectNumbers.Core { - public static class ThreadStaticPools - { - [ThreadStatic] - private static ArrayPool? _eintegerPool; + public static class ThreadStaticPools + { + [ThreadStatic] + private static ArrayPool? _eintegerPool; - public static ArrayPool EIntegerPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _eintegerPool ??= ArrayPool.Create(); - } - } - - [ThreadStatic] - private static Queue>>? _keyValuePairUInt128IntegerPool; - - public static Queue>> KeyValuePairUInt128IntegerPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _keyValuePairUInt128IntegerPool ??= []; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List> Rent(this Queue>> pool, int capacity) + public static ArrayPool EIntegerPool { - if (pool.TryDequeue(out var pooled)) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _eintegerPool ??= ArrayPool.Create(); + } + + [ThreadStatic] + private static FixedCapacityStack>>? _keyValuePairUInt128IntegerPool; + + public static FixedCapacityStack>> KeyValuePairUInt128IntegerPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _keyValuePairUInt128IntegerPool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static List> Rent(this FixedCapacityStack>> pool, int capacity) + { + if (pool.Pop() is { } pooled) { if (capacity > pooled.Capacity) { - pooled.Capacity = capacity; + pooled.Capacity = capacity; } pooled.Clear(); @@ -49,29 +43,26 @@ namespace PerfectNumbers.Core return new(capacity); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(this Queue>> pool, List> list) => pool.Enqueue(list); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Return(this FixedCapacityStack>> pool, List> list) => pool.Push(list); - [ThreadStatic] - private static Queue>? _uint128ListPool; + [ThreadStatic] + private static FixedCapacityStack>? _uint128ListPool; - public static Queue> UInt128ListPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _uint128ListPool ??= []; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List Rent(this Queue> pool, int capacity) + public static FixedCapacityStack> UInt128ListPool { - if (pool.TryDequeue(out var pooled)) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _uint128ListPool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static List Rent(this FixedCapacityStack> pool, int capacity) + { + if (pool.Pop() is { } pooled) { if (capacity > pooled.Capacity) { - pooled.Capacity = capacity; + pooled.Capacity = capacity; } pooled.Clear(); @@ -81,25 +72,22 @@ namespace PerfectNumbers.Core return new(capacity); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(this Queue> pool, List list) => pool.Enqueue(list); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Return(this FixedCapacityStack> pool, List list) => pool.Push(list); - [ThreadStatic] - private static Queue>? _uint128IntDictionaryPool; + [ThreadStatic] + private static FixedCapacityStack>? _uint128IntDictionaryPool; - public static Queue> UInt128IntDictionaryPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _uint128IntDictionaryPool ??= []; - } - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary Rent(this Queue> pool, int capacity) + public static FixedCapacityStack> UInt128IntDictionaryPool { - if (pool.TryDequeue(out var pooled)) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _uint128IntDictionaryPool ??= new(PerfectNumberConstants.DefaultPoolCapacity); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Dictionary Rent(this FixedCapacityStack> pool, int capacity) + { + if (pool.Pop() is { } pooled) { pooled.EnsureCapacity(capacity); pooled.Clear(); @@ -109,368 +97,289 @@ namespace PerfectNumbers.Core return new(capacity); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void Return(this Queue> pool, Dictionary dictionary) => pool.Enqueue(dictionary); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void Return(this FixedCapacityStack> pool, Dictionary dictionary) => pool.Push(dictionary); [ThreadStatic] - private static ArrayPool? _factorEntryPool; + private static ArrayPool? _factorEntryPool; - public static ArrayPool FactorEntryPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _factorEntryPool ??= ArrayPool.Create(); - } - } + public static ArrayPool FactorEntryPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _factorEntryPool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _factorEntry128Pool; + [ThreadStatic] + private static ArrayPool? _factorEntry128Pool; - public static ArrayPool FactorEntry128Pool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _factorEntry128Pool ??= ArrayPool.Create(); - } - } + public static ArrayPool FactorEntry128Pool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _factorEntry128Pool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _gpuUInt128Pool; + [ThreadStatic] + private static ArrayPool? _gpuUInt128Pool; - public static ArrayPool GpuUInt128Pool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _gpuUInt128Pool ??= ArrayPool.Create(); - } - } + public static ArrayPool GpuUInt128Pool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _gpuUInt128Pool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession; + [ThreadStatic] + private static MersenneCpuDivisorScanSession? _mersenneCpuDivisorSession; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static MersenneCpuDivisorScanSession? RentMersenneCpuDivisorSession() - { - MersenneCpuDivisorScanSession? session = _mersenneCpuDivisorSession; - if (session is null) - { - return null; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static MersenneCpuDivisorScanSession RentMersenneCpuDivisorSession(Gpu.Accelerators.PrimeOrderCalculatorAccelerator gpu, ComputationDevice orderDevice) + { + if (_mersenneCpuDivisorSession is { } session) + { + session.Configure(gpu, orderDevice); + _mersenneCpuDivisorSession = null; + return session; + } - _mersenneCpuDivisorSession = null; - return session; - } + return new MersenneCpuDivisorScanSession(gpu, orderDevice); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ReturnMersenneCpuDivisorSession(MersenneCpuDivisorScanSession session) - { - _mersenneCpuDivisorSession = session; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static void ReturnMersenneCpuDivisorSession(MersenneCpuDivisorScanSession session) => _mersenneCpuDivisorSession = session; - [ThreadStatic] - private static ArrayPool? _uint128Pool; + [ThreadStatic] + private static ArrayPool? _uint128Pool; - public static ArrayPool UInt128Pool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _uint128Pool ??= ArrayPool.Create(); - } - } + public static ArrayPool UInt128Pool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _uint128Pool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _ulongPool; + [ThreadStatic] + private static ArrayPool? _ulongPool; - public static ArrayPool UlongPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _ulongPool ??= ArrayPool.Create(); - } - } + public static ArrayPool UlongPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _ulongPool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _boolPool; + [ThreadStatic] + private static ArrayPool? _boolPool; - public static ArrayPool BoolPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _boolPool ??= ArrayPool.Create(); - } - } + public static ArrayPool BoolPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _boolPool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _bytePool; + [ThreadStatic] + private static ArrayPool? _bytePool; - public static ArrayPool BytePool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _bytePool ??= ArrayPool.Create(); - } - } + public static ArrayPool BytePool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _bytePool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _montgomeryDivisorDataPool; + [ThreadStatic] + private static ArrayPool? _montgomeryDivisorDataPool; - public static ArrayPool MontgomeryDivisorDataPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _montgomeryDivisorDataPool ??= ArrayPool.Create(); - } - } + public static ArrayPool MontgomeryDivisorDataPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _montgomeryDivisorDataPool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _gpuDivisorPartialDataPool; + [ThreadStatic] + private static ArrayPool? _gpuDivisorPartialDataPool; - internal static ArrayPool GpuDivisorPartialDataPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _gpuDivisorPartialDataPool ??= ArrayPool.Create(); - } - } + internal static ArrayPool GpuDivisorPartialDataPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _gpuDivisorPartialDataPool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _intPool; + [ThreadStatic] + private static ArrayPool? _intPool; - public static ArrayPool IntPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _intPool ??= ArrayPool.Create(); - } - } + public static ArrayPool IntPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _intPool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static ArrayPool? _charPool; + [ThreadStatic] + private static ArrayPool? _charPool; - public static ArrayPool CharPool - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get - { - return _charPool ??= ArrayPool.Create(); - } - } + public static ArrayPool CharPool + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _charPool ??= ArrayPool.Create(); + } - [ThreadStatic] - private static List>? _ulongListPool; + [ThreadStatic] + private static FixedCapacityStack>? _ulongListPool; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static List RentUlongList(int capacityHint) - { - List>? pool = _ulongListPool; - if (pool is not null) - { - int poolCount = pool.Count; - if (poolCount > 0) - { - int lastIndex = poolCount - 1; - List list = pool[lastIndex]; - pool.RemoveAt(lastIndex); - if (list.Capacity < capacityHint) - { - list.Capacity = capacityHint; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static List RentUlongList(int capacityHint) + { + FixedCapacityStack>? pool = _ulongListPool; + if (pool is not null) + { + if (pool.Pop() is { } list) + { + if (list.Capacity < capacityHint) + { + list.Capacity = capacityHint; + } - return list; - } - } + return list; + } + } + else + { + _ulongListPool = new(capacityHint); + } - return new List(capacityHint); - } + return new List(capacityHint); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnUlongList(List list) - { - List>? pool = _ulongListPool; - if (pool is null) - { - pool = new List>(4); - _ulongListPool = pool; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void ReturnUlongList(List list) => _ulongListPool!.Push(list); - pool.Add(list); - } + [ThreadStatic] + private static ArrayPool<(ulong, ulong)>? _ulongUlongTupleArrayPool; - [ThreadStatic] - private static ArrayPool<(ulong, ulong)>? _ulongUlongTupleArrayPool; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static (ulong, ulong)[] RentUlongUlongArray(int capacityHint) + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static (ulong, ulong)[] RentUlongUlongArray(int capacityHint) { _ulongUlongTupleArrayPool ??= ArrayPool<(ulong, ulong)>.Create(); - ArrayPool<(ulong, ulong)>? pool = _ulongUlongTupleArrayPool; + ArrayPool<(ulong, ulong)>? pool = _ulongUlongTupleArrayPool; return pool.Rent(capacityHint); - } + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static void Return((ulong, ulong)[] array) => _ulongUlongTupleArrayPool!.Return(array, clearArray: false); [ThreadStatic] - private static List>? _primeOrderPendingEntryListPool; + private static FixedCapacityStack>? _primeOrderPendingEntryListPool; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static List RentPrimeOrderPendingEntryList(int capacityHint) - { - List>? pool = _primeOrderPendingEntryListPool; - if (pool is not null) - { - int poolCount = pool.Count; - if (poolCount > 0) - { - int lastIndex = poolCount - 1; - List list = pool[lastIndex]; - pool.RemoveAt(lastIndex); - if (list.Capacity < capacityHint) - { - list.Capacity = capacityHint; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static List RentPrimeOrderPendingEntryList(int capacityHint) + { + FixedCapacityStack>? pool = _primeOrderPendingEntryListPool; + if (pool is not null) + { + if (pool.Pop() is { } list) + { + if (list.Capacity < capacityHint) + { + list.Capacity = capacityHint; + } - return list; - } - } + return list; + } + } + else + { + _primeOrderPendingEntryListPool = new(PerfectNumberConstants.DefaultPoolCapacity); + } - return new List(capacityHint); - } + return new List(capacityHint); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ReturnPrimeOrderPendingEntryList(List list) - { - List>? pool = _primeOrderPendingEntryListPool; - if (pool is null) - { - pool = new List>(4); - _primeOrderPendingEntryListPool = pool; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static void ReturnPrimeOrderPendingEntryList(List list) => _primeOrderPendingEntryListPool!.Push(list); - pool.Add(list); - } + [ThreadStatic] + private static FixedCapacityStack>? _ulongStackPool; - [ThreadStatic] - private static List>? _ulongStackPool; + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static FixedCapacityStack RentUlongStack(int capacityHint) + { + FixedCapacityStack>? pool = _ulongStackPool; + if (pool is not null) + { + if (pool.Pop() is { } stack) + { + stack.Capacity = capacityHint; + return stack; + } + } + else + { + _ulongStackPool = new(PrimeOrderConstants.GpuSmallPrimeFactorSlots); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Stack RentUlongStack(int capacityHint) - { - List>? pool = _ulongStackPool; - if (pool is not null) - { - int poolCount = pool.Count; - if (poolCount > 0) - { - int lastIndex = poolCount - 1; - Stack stack = pool[lastIndex]; - pool.RemoveAt(lastIndex); + return new FixedCapacityStack(capacityHint); + } - if (stack.Count < capacityHint) - { - stack.EnsureCapacity(capacityHint); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void ReturnUlongStack(FixedCapacityStack stack) => _ulongStackPool!.Push(stack); - return stack; - } - } + [ThreadStatic] + private static Dictionary? _factorCountDictionary; - return new Stack(capacityHint); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Dictionary RentFactorCountDictionary() + { + Dictionary? dictionary = _factorCountDictionary; + if (dictionary is null) + { + return new Dictionary(8); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnUlongStack(Stack stack) - { - List>? pool = _ulongStackPool; - if (pool is null) - { - pool = new List>(4); - _ulongStackPool = pool; - } + _factorCountDictionary = null; + return dictionary; + } - pool.Add(stack); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void ReturnFactorCountDictionary(Dictionary dictionary) => _factorCountDictionary = dictionary; - [ThreadStatic] - private static Dictionary? _factorCountDictionary; + [ThreadStatic] + private static Dictionary? _factorScratchDictionary; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary RentFactorCountDictionary() - { - Dictionary? dictionary = _factorCountDictionary; - if (dictionary is null) - { - return new Dictionary(8); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static Dictionary RentFactorScratchDictionary() + { + if (_factorScratchDictionary is { } dictionary) + { + _factorScratchDictionary = null; + return dictionary; + } - _factorCountDictionary = null; - return dictionary; - } + return new Dictionary(8); + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnFactorCountDictionary(Dictionary dictionary) - { - _factorCountDictionary = dictionary; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void ReturnFactorScratchDictionary(Dictionary dictionary) => _factorScratchDictionary = dictionary; - [ThreadStatic] - private static Dictionary? _factorScratchDictionary; + [ThreadStatic] + private static ExponentRemainderStepperCpu _exponentRemainderStepperCpu; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Dictionary RentFactorScratchDictionary() - { - Dictionary? dictionary = _factorScratchDictionary; - if (dictionary is null) - { - return new Dictionary(8); - } + [ThreadStatic] + private static bool _hasExponentRemainderStepperCpu; - _factorScratchDictionary = null; - return dictionary; - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static ExponentRemainderStepperCpu RentExponentStepperCpu(in MontgomeryDivisorData divisorData, ulong cycleLength = 0UL) + { + if (_hasExponentRemainderStepperCpu && _exponentRemainderStepperCpu.MatchesDivisor(divisorData, cycleLength)) + { + ExponentRemainderStepperCpu stepper = _exponentRemainderStepperCpu; + _hasExponentRemainderStepperCpu = false; + stepper.Reset(); + return stepper; + } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static void ReturnFactorScratchDictionary(Dictionary dictionary) - { - _factorScratchDictionary = dictionary; - } + return new ExponentRemainderStepperCpu(divisorData, cycleLength); + } - [ThreadStatic] - private static ExponentRemainderStepperCpu _exponentRemainderStepperCpu; - - [ThreadStatic] - private static bool _hasExponentRemainderStepperCpu; - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static ExponentRemainderStepperCpu RentExponentStepperCpu(in MontgomeryDivisorData divisorData, ulong cycleLength = 0UL) - { - if (_hasExponentRemainderStepperCpu && _exponentRemainderStepperCpu.MatchesDivisor(divisorData, cycleLength)) - { - ExponentRemainderStepperCpu stepper = _exponentRemainderStepperCpu; - _hasExponentRemainderStepperCpu = false; - stepper.Reset(); - return stepper; - } - - return new ExponentRemainderStepperCpu(divisorData, cycleLength); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void ReturnExponentStepperCpu(ExponentRemainderStepperCpu stepper) - { - stepper.Reset(); - _exponentRemainderStepperCpu = stepper; - _hasExponentRemainderStepperCpu = true; - } - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + internal static void ReturnExponentStepperCpu(ExponentRemainderStepperCpu stepper) + { + _exponentRemainderStepperCpu = stepper; + _hasExponentRemainderStepperCpu = true; + } + } } diff --git a/PerfectNumbers.Core/ULongExtensions.cs b/PerfectNumbers.Core/ULongExtensions.cs index 090593d..e35ee15 100644 --- a/PerfectNumbers.Core/ULongExtensions.cs +++ b/PerfectNumbers.Core/ULongExtensions.cs @@ -647,9 +647,8 @@ public static partial class ULongExtensions { ulong tLow = unchecked(a * b); ulong m = unchecked(tLow * nPrime); - ulong mTimesModulusLow = unchecked(m * modulus); - - ulong result = unchecked(a.MulHighCpu(b) + m.MulHighCpu(modulus) + (unchecked(tLow + mTimesModulusLow) < tLow ? 1UL : 0UL)); + ulong result = unchecked(m * modulus); + result = unchecked(a.MulHighCpu(b) + m.MulHighCpu(modulus) + (unchecked(tLow + result) < tLow ? 1UL : 0UL)); if (result >= modulus) { result -= modulus; diff --git a/PerfectNumbers.Core/UlongArrayPool.cs b/PerfectNumbers.Core/UlongArrayPool.cs new file mode 100644 index 0000000..5f6bffd --- /dev/null +++ b/PerfectNumbers.Core/UlongArrayPool.cs @@ -0,0 +1,40 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core +{ + public struct UlongArrayPool(int poolCapacity) + { + private readonly ulong[][] _arrays = new ulong[poolCapacity][]; + private int _count = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public ulong[] Rent(int length) + { + int available = _count; + if (available == 0) + { + return new ulong[length]; + } + + ulong[][] arrays = _arrays; + available--; + _count = available; + + ulong[] candidate = arrays[available]; + return candidate.Length >= length ? candidate : new ulong[length]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public void Return(ulong[] array) + { + ulong[][] arrays = _arrays; + int available = _count; + + if (available < arrays.Length) + { + arrays[available] = array; + _count = available + 1; + } + } + } +} diff --git a/PerfectNumbers.Core/UnboundedTaskScheduler.cs b/PerfectNumbers.Core/UnboundedTaskScheduler.cs index 2d32739..33fcfbf 100644 --- a/PerfectNumbers.Core/UnboundedTaskScheduler.cs +++ b/PerfectNumbers.Core/UnboundedTaskScheduler.cs @@ -1,67 +1,72 @@ +using System.Runtime.CompilerServices; + namespace PerfectNumbers.Core; public sealed class UnboundedTaskScheduler : TaskScheduler { - private static UnboundedTaskScheduler? _instance; - private static int _configuredThreadCount = Environment.ProcessorCount; + private static UnboundedTaskScheduler? _instance; + private static int _configuredThreadCount = Environment.ProcessorCount; - public static UnboundedTaskScheduler Instance => _instance ??= new UnboundedTaskScheduler(_configuredThreadCount); + public static UnboundedTaskScheduler Instance + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _instance ??= new UnboundedTaskScheduler(_configuredThreadCount); + } - public static int ConfiguredThreadCount => _configuredThreadCount; + public static int ConfiguredThreadCount + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => _configuredThreadCount; + } - private TaskThreadPool _threadPool; + private TaskThreadPool _threadPool; - private UnboundedTaskScheduler(int threadCount) - { - _threadPool = new TaskThreadPool(threadCount, ExecuteTask); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private UnboundedTaskScheduler(int threadCount) => _threadPool = new TaskThreadPool(threadCount, ExecuteTask); - public static void ConfigureThreadCount(int threadCount) - { - _configuredThreadCount = threadCount; - PrimeOrderCalculator.PrimeOrderCalculatorConfig.ConfigureHeuristicDefault(threadCount); + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + public static void ConfigureThreadCount(int threadCount) + { + _configuredThreadCount = threadCount; + PrimeOrderCalculator.PrimeOrderCalculatorConfig.ConfigureHeuristicDefault(threadCount); - // On EvenPerfectBitScanner's execution path the scheduler instance is created after configuration, - // so updating an existing pool would never run. Leave the update path disabled for now. - // if (_instance is not null) - // { - // _instance.UpdateThreadPool(threadCount); - // } - } + // On EvenPerfectBitScanner's execution path the scheduler instance is created after configuration, + // so updating an existing pool would never run. Leave the update path disabled for now. + // if (_instance is not null) + // { + // _instance.UpdateThreadPool(threadCount); + // } + } - public override int MaximumConcurrencyLevel => int.MaxValue; + public override int MaximumConcurrencyLevel + { + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + get => int.MaxValue; + } - protected override void QueueTask(Task task) - { - _threadPool.Queue(task); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override void QueueTask(Task task) => _threadPool.Queue(task); - private void UpdateThreadPool(int threadCount) - { - if (_threadPool.ThreadCount == threadCount) - { - return; - } + private void UpdateThreadPool(int threadCount) + { + if (_threadPool.ThreadCount == threadCount) + { + return; + } - TaskThreadPool newPool = new(threadCount, ExecuteTask); - TaskThreadPool oldPool = _threadPool; - _threadPool = newPool; - oldPool.Dispose(); - } + TaskThreadPool newPool = new(threadCount, ExecuteTask); + TaskThreadPool oldPool = _threadPool; + _threadPool = newPool; + oldPool.Dispose(); + } - private void ExecuteTask(Task task) - { - TryExecuteTask(task); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + private void ExecuteTask(Task task) => TryExecuteTask(task); - protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) - { - return TryExecuteTask(task); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => TryExecuteTask(task); - protected override IEnumerable? GetScheduledTasks() - { - return _threadPool.GetScheduledTasks(); - } + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] + protected override IEnumerable? GetScheduledTasks() => _threadPool.GetScheduledTasks(); }