Pools renamed to FixedCapacityPools. Other fixed capacity collections added. Queues, concurrent queues and standard stacks replaced with fixed capacity stacks. MethodImplOptions.AggressiveInlining and .AggressiveOptimization added to simple methods and getters / setters. Variables optimized in few places.

This commit is contained in:
mlemanczyk committed 2025-12-11 21:10:19 +01:00
1 parent 547d43de84
commit 260aa19e44
25 files changed
+1372 -1300

No files matched your search

-104
View File
@@ -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;
}
}
}
}
@@ -0,0 +1,77 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
public sealed class ConcurrentFixedCapacityStack<T>(int capacity)
{
private static readonly FixedCapacityArrayPool<T> _arrayPool = FixedCapacityPools<T>.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;
}
}
@@ -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<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
if (!canAdvance)
{
if (divisor <= limit && residueStepper.IsAdmissible())
+7 -5
View File
@@ -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<ulong> result = stackalloc ulong[1];
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
GetCycleLengthsCpu(singleDivisor, result);
@@ -82,7 +83,7 @@ public sealed class DivisorCycleCache
return cycleLength;
}
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<ulong> result = stackalloc ulong[1];
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
GetCycleLengthsHybrid(gpu, singleDivisor, result);
@@ -120,7 +122,7 @@ public sealed class DivisorCycleCache
return cycleLength;
}
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
{
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
{
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
for (int i = 0; i < indices.Length; i++)
{
int targetIndex = indices[i];
@@ -0,0 +1,40 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core
{
public struct FixedCapacityArrayPool<T>(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;
}
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core
{
public static class FixedCapacityPools<T>
{
[ThreadStatic]
private static FixedCapacityArrayPool<T>? _arrayPool;
internal static FixedCapacityArrayPool<T> 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);
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
public sealed class FixedCapacityStack<T>(int capacity)
{
private static readonly FixedCapacityArrayPool<T> _arrayPool = FixedCapacityPools<T>.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;
}
}
}
@@ -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<AcceleratorStream>[] _streams = new ConcurrentQueue<AcceleratorStream> [PerfectNumberConstants.RollingAccelerators];
private static readonly ConcurrentFixedCapacityStack<AcceleratorStream>[] _streams = new ConcurrentFixedCapacityStack<AcceleratorStream>[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();
}
}
@@ -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<PrimeOrderCalculatorAccelerator>? _pool;
private static ConcurrentFixedCapacityStack<PrimeOrderCalculatorAccelerator>? _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();
}
@@ -22,9 +22,9 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
private BigInteger _minK = BigInteger.One;
// private bool _isConfigured;
private readonly ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentBag<MersenneNumberDivisorByDivisorAccelerator>())];
private readonly ConcurrentFixedCapacityStack<MersenneNumberDivisorByDivisorAccelerator>[] _resourcePool = [.. AcceleratorPool.Shared.Accelerators.Select(x => new ConcurrentFixedCapacityStack<MersenneNumberDivisorByDivisorAccelerator>(PerfectNumberConstants.DefaultPoolCapacity))];
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = [];
private readonly ConcurrentFixedCapacityStack<DivisorScanSession> _sessionPool = new(PerfectNumberConstants.DefaultPoolCapacity);
public int GpuBatchSize
{
@@ -272,7 +272,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
ArrayView1D<ulong, Stride1D.Dense> exponentViewDevice = exponentBuffer.View;
ArrayView1D<byte, Stride1D.Dense> hitsView = hitsBuffer.View;
ArrayView1D<int, Stride1D.Dense> hitIndexView = hitIndexBuffer.View;
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
static bool ProcessRange(
PrimeOrderCalculatorAccelerator gpu,
ulong prime,
@@ -291,7 +291,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
Span<int> offsetSpan,
Span<int> countSpan,
Span<ulong> cycleSpan,
Queue<MontgomeryDivisorData> divisorPool,
FixedCapacityStack<MontgomeryDivisorData> divisorPool,
int chunkCountBaseline,
int acceleratorIndex,
ref bool composite,
@@ -598,6 +598,7 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
_kernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>(DivisorByDivisorKernels.CheckKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuDivisorPartialData>, ArrayView<int>, ArrayView<int>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>, ArrayView<int>>>();
}
[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<ulong>(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<Accelerator>
{
@@ -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;
+40
View File
@@ -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;
}
}
}
}
+4 -4
View File
@@ -394,7 +394,7 @@ public class MersenneDivisorCycles
if (divisor <= ulong.MaxValue)
{
ulong divisor64 = (ulong)divisor;
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<MontgomeryDivisorData> 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<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
for (ulong divisor = localStart; divisor <= localEnd; divisor++)
{
+429 -440
View File
@@ -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<UInt128, UInt128> _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<UInt128, UInt128> _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<UInt128> 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<List<ulong>> ListPool = new();
private static readonly ConcurrentBag<Dictionary<ulong, byte>> DictPool = new();
private static readonly Dictionary<ulong, ulong[]> FactorCache = new();
private static readonly object FactorCacheLock = new();
private static readonly ConcurrentFixedCapacityStack<List<ulong>> ListPool = new(PerfectNumberConstants.DefaultPoolCapacity);
private static readonly ConcurrentFixedCapacityStack<Dictionary<ulong, byte>> DictPool = new(PerfectNumberConstants.DefaultPoolCapacity);
private static List<ulong> RentList() => ListPool.TryTake(out var list) ? list : new List<ulong>(4);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static List<ulong> RentList() => ListPool.Pop() is { } list ? list : new List<ulong>(4);
private static void ReturnList(List<ulong> list)
{
list.Clear();
ListPool.Add(list);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void ReturnList(List<ulong> list)
{
list.Clear();
ListPool.Push(list);
}
private static Dictionary<ulong, byte> RentDictionary() => DictPool.TryTake(out var dict) ? dict : new Dictionary<ulong, byte>();
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static Dictionary<ulong, byte> RentDictionary() => DictPool.Pop() is { } dict ? dict : [];
private static void ReturnDictionary(Dictionary<ulong, byte> dict)
{
dict.Clear();
DictPool.Add(dict);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void ReturnDictionary(Dictionary<ulong, byte> dict)
{
dict.Clear();
DictPool.Push(dict);
}
private static readonly ConcurrentBag<Dictionary<UInt128, byte>> DictPool128 = new();
private static readonly ConcurrentFixedCapacityStack<Dictionary<UInt128, byte>> DictPool128 = new(PerfectNumberConstants.DefaultPoolCapacity);
private static Dictionary<UInt128, byte> RentDictionary128() =>
DictPool128.TryTake(out var dict) ? dict : new Dictionary<UInt128, byte>();
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static Dictionary<UInt128, byte> RentDictionary128() =>
DictPool128.Pop() is { } dict ? dict : [];
private static void ReturnDictionary128(Dictionary<UInt128, byte> dict)
{
dict.Clear();
DictPool128.Add(dict);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static void ReturnDictionary128(Dictionary<UInt128, byte> 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<ulong>(); // 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<UInt128>(); // 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<UInt128>(); // 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;
}
}
@@ -5,29 +5,34 @@ namespace PerfectNumbers.Core;
public static class MontgomeryDivisorDataPool
{
[ThreadStatic]
private static Queue<MontgomeryDivisorData>? _pool;
public static Queue<MontgomeryDivisorData> Shared => _pool ??= new();
private static FixedCapacityStack<MontgomeryDivisorData>? _pool;
public static FixedCapacityStack<MontgomeryDivisorData> 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<MontgomeryDivisorData> 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<MontgomeryDivisorData> queue, ulong modulus, ulong nPrime, ulong montgomeryOne, ulong montgomeryTwo, ulong montgomeryTwoSquared)
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static MontgomeryDivisorData FromModulus(this FixedCapacityStack<MontgomeryDivisorData> 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<MontgomeryDivisorData> 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<MontgomeryDivisorData> queue, MontgomeryDivisorData divisorData) => queue.Enqueue(divisorData);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Return(this FixedCapacityStack<MontgomeryDivisorData> queue, MontgomeryDivisorData divisorData) => queue.Push(divisorData);
}
+12 -9
View File
@@ -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;
@@ -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;
+22 -20
View File
@@ -108,7 +108,7 @@ internal static partial class PrimeOrderCalculator
}
ulong prime64 = (ulong)prime;
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<Dictionary<UInt128, int>> uInt128IntDictionaryPool = ThreadStaticPools.UInt128IntDictionaryPool;
FixedCapacityStack<Dictionary<UInt128, int>> uInt128IntDictionaryPool = ThreadStaticPools.UInt128IntDictionaryPool;
Dictionary<UInt128, int> counts = uInt128IntDictionaryPool.Rent(capacity: 8);
FactorCompletelyWide(phi, counts);
int entryCount = counts.Count;
@@ -236,7 +236,7 @@ internal static partial class PrimeOrderCalculator
return phi;
}
Queue<List<KeyValuePair<UInt128, int>>> keyValuePairUInt128IntegerPool = ThreadStaticPools.KeyValuePairUInt128IntegerPool;
FixedCapacityStack<List<KeyValuePair<UInt128, int>>> 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<List<UInt128>> uInt128ListPool = ThreadStaticPools.UInt128ListPool;
FixedCapacityStack<List<UInt128>> uInt128ListPool = ThreadStaticPools.UInt128ListPool;
List<UInt128> 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<ulong> 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<ulong> compositeStack = ThreadStaticPools.RentUlongStack(PrimeOrderConstants.GpuSmallPrimeFactorSlots);
FixedCapacityStack<ulong> 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<ulong> primeSlots, Span<int> exponentSlots, ref int factorCount, List<PendingEntry> pending, Stack<ulong> compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached)
private static void CollectFactorsCpu(Span<ulong> primeSlots, Span<int> exponentSlots, ref int factorCount, List<PendingEntry> pending, FixedCapacityStack<ulong> compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached)
{
while (compositeStack.Count > 0)
{
@@ -117,7 +117,7 @@ internal static partial class PrimeOrderCalculator
}
ulong prime64 = (ulong)prime;
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<ulong> compositeStack = ThreadStaticPools.RentUlongStack(4);
FixedCapacityStack<ulong> compositeStack = ThreadStaticPools.RentUlongStack(4);
compositeStack.Push(remaining);
CollectFactorsCpu(primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached);
@@ -67,7 +67,7 @@ internal static partial class PrimeOrderCalculator
}
ulong prime64 = (ulong)prime;
Queue<MontgomeryDivisorData> divisorPool = MontgomeryDivisorDataPool.Shared;
FixedCapacityStack<MontgomeryDivisorData> 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<ulong> primeSlots, Span<int> exponentSlots, ref ulong[] primeSlotArray, ref int[] exponentSlotArray, ref int factorCount, List<PendingEntry> pending, Stack<ulong> compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached)
private static void CollectFactorsHybrid(PrimeOrderCalculatorAccelerator gpu, Span<ulong> primeSlots, Span<int> exponentSlots, ref int factorCount, List<PendingEntry> pending, FixedCapacityStack<ulong> 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<ulong> compositeStack = ThreadStaticPools.RentUlongStack(4);
FixedCapacityStack<ulong> 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)
{
+11 -13
View File
@@ -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<StringBuilder> _stringBuilderPool = new();
private static readonly ConcurrentFixedCapacityStack<StringBuilder> _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);
}
}
+183 -191
View File
@@ -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<WorkItem> _pendingTaskQueue = new(PerfectNumberConstants.DefaultThreadPoolCapacity);
private readonly HashSet<Thread> _runningThreads = [];
private readonly object _lock = new();
private readonly Action<Task> _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<WorkItem> _pendingTaskQueue = new();
private readonly HashSet<Thread> _runningThreads = new();
private readonly object _lock = new();
private readonly Action<Task> _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<Task> taskExecutor)
{
if (minimumThreads <= 0)
{
throw new ArgumentOutOfRangeException(nameof(minimumThreads));
}
public TaskThreadPool(int minimumThreads, Action<Task> 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<Thread> threadsToStart = new(minimumThreads);
List<Thread> 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<Thread>? threadsToStart;
List<Thread>? 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<Task> GetScheduledTasks()
{
WorkItem[] snapshot = _pendingTaskQueue.ToArray();
public IEnumerable<Task> GetScheduledTasks()
{
WorkItem[] snapshot = _pendingTaskQueue.ToArray();
if (snapshot.Length == 0)
{
return [];
}
if (snapshot.Length == 0)
{
return Array.Empty<Task>();
}
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<WorkItem>.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<Thread>? threadsToStart = null;
private void CompleteWorker()
{
List<Thread>? 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<Thread>? EnsureWorkerCapacityLocked(List<Thread>? threadsToStart)
{
int pendingTasks = Math.Min(_maximumConcurrency, Volatile.Read(ref _pendingTaskCount));
private List<Thread>? EnsureWorkerCapacityLocked(List<Thread>? 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<Thread>();
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}"
};
}
}
+273 -364
View File
@@ -6,40 +6,34 @@ using PeterO.Numbers;
namespace PerfectNumbers.Core
{
public static class ThreadStaticPools
{
[ThreadStatic]
private static ArrayPool<EInteger>? _eintegerPool;
public static class ThreadStaticPools
{
[ThreadStatic]
private static ArrayPool<EInteger>? _eintegerPool;
public static ArrayPool<EInteger> EIntegerPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _eintegerPool ??= ArrayPool<EInteger>.Create();
}
}
[ThreadStatic]
private static Queue<List<KeyValuePair<UInt128, int>>>? _keyValuePairUInt128IntegerPool;
public static Queue<List<KeyValuePair<UInt128, int>>> KeyValuePairUInt128IntegerPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _keyValuePairUInt128IntegerPool ??= [];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<KeyValuePair<UInt128, int>> Rent(this Queue<List<KeyValuePair<UInt128, int>>> pool, int capacity)
public static ArrayPool<EInteger> EIntegerPool
{
if (pool.TryDequeue(out var pooled))
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _eintegerPool ??= ArrayPool<EInteger>.Create();
}
[ThreadStatic]
private static FixedCapacityStack<List<KeyValuePair<UInt128, int>>>? _keyValuePairUInt128IntegerPool;
public static FixedCapacityStack<List<KeyValuePair<UInt128, int>>> KeyValuePairUInt128IntegerPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _keyValuePairUInt128IntegerPool ??= new(PerfectNumberConstants.DefaultPoolCapacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static List<KeyValuePair<UInt128, int>> Rent(this FixedCapacityStack<List<KeyValuePair<UInt128, int>>> 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<List<KeyValuePair<UInt128, int>>> pool, List<KeyValuePair<UInt128, int>> list) => pool.Enqueue(list);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Return(this FixedCapacityStack<List<KeyValuePair<UInt128, int>>> pool, List<KeyValuePair<UInt128, int>> list) => pool.Push(list);
[ThreadStatic]
private static Queue<List<UInt128>>? _uint128ListPool;
[ThreadStatic]
private static FixedCapacityStack<List<UInt128>>? _uint128ListPool;
public static Queue<List<UInt128>> UInt128ListPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _uint128ListPool ??= [];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<UInt128> Rent(this Queue<List<UInt128>> pool, int capacity)
public static FixedCapacityStack<List<UInt128>> 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<UInt128> Rent(this FixedCapacityStack<List<UInt128>> 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<List<UInt128>> pool, List<UInt128> list) => pool.Enqueue(list);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Return(this FixedCapacityStack<List<UInt128>> pool, List<UInt128> list) => pool.Push(list);
[ThreadStatic]
private static Queue<Dictionary<UInt128, int>>? _uint128IntDictionaryPool;
[ThreadStatic]
private static FixedCapacityStack<Dictionary<UInt128, int>>? _uint128IntDictionaryPool;
public static Queue<Dictionary<UInt128, int>> UInt128IntDictionaryPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _uint128IntDictionaryPool ??= [];
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<UInt128, int> Rent(this Queue<Dictionary<UInt128, int>> pool, int capacity)
public static FixedCapacityStack<Dictionary<UInt128, int>> 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<UInt128, int> Rent(this FixedCapacityStack<Dictionary<UInt128, int>> 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<Dictionary<UInt128, int>> pool, Dictionary<UInt128, int> dictionary) => pool.Enqueue(dictionary);
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void Return(this FixedCapacityStack<Dictionary<UInt128, int>> pool, Dictionary<UInt128, int> dictionary) => pool.Push(dictionary);
[ThreadStatic]
private static ArrayPool<FactorEntry>? _factorEntryPool;
private static ArrayPool<FactorEntry>? _factorEntryPool;
public static ArrayPool<FactorEntry> FactorEntryPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorEntryPool ??= ArrayPool<FactorEntry>.Create();
}
}
public static ArrayPool<FactorEntry> FactorEntryPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _factorEntryPool ??= ArrayPool<FactorEntry>.Create();
}
[ThreadStatic]
private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
[ThreadStatic]
private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
public static ArrayPool<FactorEntry128> FactorEntry128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorEntry128Pool ??= ArrayPool<FactorEntry128>.Create();
}
}
public static ArrayPool<FactorEntry128> FactorEntry128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _factorEntry128Pool ??= ArrayPool<FactorEntry128>.Create();
}
[ThreadStatic]
private static ArrayPool<GpuUInt128>? _gpuUInt128Pool;
[ThreadStatic]
private static ArrayPool<GpuUInt128>? _gpuUInt128Pool;
public static ArrayPool<GpuUInt128> GpuUInt128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _gpuUInt128Pool ??= ArrayPool<GpuUInt128>.Create();
}
}
public static ArrayPool<GpuUInt128> GpuUInt128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _gpuUInt128Pool ??= ArrayPool<GpuUInt128>.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<UInt128>? _uint128Pool;
[ThreadStatic]
private static ArrayPool<UInt128>? _uint128Pool;
public static ArrayPool<UInt128> UInt128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _uint128Pool ??= ArrayPool<UInt128>.Create();
}
}
public static ArrayPool<UInt128> UInt128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _uint128Pool ??= ArrayPool<UInt128>.Create();
}
[ThreadStatic]
private static ArrayPool<ulong>? _ulongPool;
[ThreadStatic]
private static ArrayPool<ulong>? _ulongPool;
public static ArrayPool<ulong> UlongPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _ulongPool ??= ArrayPool<ulong>.Create();
}
}
public static ArrayPool<ulong> UlongPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _ulongPool ??= ArrayPool<ulong>.Create();
}
[ThreadStatic]
private static ArrayPool<bool>? _boolPool;
[ThreadStatic]
private static ArrayPool<bool>? _boolPool;
public static ArrayPool<bool> BoolPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _boolPool ??= ArrayPool<bool>.Create();
}
}
public static ArrayPool<bool> BoolPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _boolPool ??= ArrayPool<bool>.Create();
}
[ThreadStatic]
private static ArrayPool<byte>? _bytePool;
[ThreadStatic]
private static ArrayPool<byte>? _bytePool;
public static ArrayPool<byte> BytePool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _bytePool ??= ArrayPool<byte>.Create();
}
}
public static ArrayPool<byte> BytePool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _bytePool ??= ArrayPool<byte>.Create();
}
[ThreadStatic]
private static ArrayPool<MontgomeryDivisorData>? _montgomeryDivisorDataPool;
[ThreadStatic]
private static ArrayPool<MontgomeryDivisorData>? _montgomeryDivisorDataPool;
public static ArrayPool<MontgomeryDivisorData> MontgomeryDivisorDataPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _montgomeryDivisorDataPool ??= ArrayPool<MontgomeryDivisorData>.Create();
}
}
public static ArrayPool<MontgomeryDivisorData> MontgomeryDivisorDataPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _montgomeryDivisorDataPool ??= ArrayPool<MontgomeryDivisorData>.Create();
}
[ThreadStatic]
private static ArrayPool<GpuDivisorPartialData>? _gpuDivisorPartialDataPool;
[ThreadStatic]
private static ArrayPool<GpuDivisorPartialData>? _gpuDivisorPartialDataPool;
internal static ArrayPool<GpuDivisorPartialData> GpuDivisorPartialDataPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _gpuDivisorPartialDataPool ??= ArrayPool<GpuDivisorPartialData>.Create();
}
}
internal static ArrayPool<GpuDivisorPartialData> GpuDivisorPartialDataPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _gpuDivisorPartialDataPool ??= ArrayPool<GpuDivisorPartialData>.Create();
}
[ThreadStatic]
private static ArrayPool<int>? _intPool;
[ThreadStatic]
private static ArrayPool<int>? _intPool;
public static ArrayPool<int> IntPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _intPool ??= ArrayPool<int>.Create();
}
}
public static ArrayPool<int> IntPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _intPool ??= ArrayPool<int>.Create();
}
[ThreadStatic]
private static ArrayPool<char>? _charPool;
[ThreadStatic]
private static ArrayPool<char>? _charPool;
public static ArrayPool<char> CharPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _charPool ??= ArrayPool<char>.Create();
}
}
public static ArrayPool<char> CharPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
get => _charPool ??= ArrayPool<char>.Create();
}
[ThreadStatic]
private static List<List<ulong>>? _ulongListPool;
[ThreadStatic]
private static FixedCapacityStack<List<ulong>>? _ulongListPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static List<ulong> RentUlongList(int capacityHint)
{
List<List<ulong>>? pool = _ulongListPool;
if (pool is not null)
{
int poolCount = pool.Count;
if (poolCount > 0)
{
int lastIndex = poolCount - 1;
List<ulong> list = pool[lastIndex];
pool.RemoveAt(lastIndex);
if (list.Capacity < capacityHint)
{
list.Capacity = capacityHint;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static List<ulong> RentUlongList(int capacityHint)
{
FixedCapacityStack<List<ulong>>? 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<ulong>(capacityHint);
}
return new List<ulong>(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnUlongList(List<ulong> list)
{
List<List<ulong>>? pool = _ulongListPool;
if (pool is null)
{
pool = new List<List<ulong>>(4);
_ulongListPool = pool;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void ReturnUlongList(List<ulong> 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<List<PrimeOrderCalculator.PendingEntry>>? _primeOrderPendingEntryListPool;
private static FixedCapacityStack<List<PrimeOrderCalculator.PendingEntry>>? _primeOrderPendingEntryListPool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static List<PrimeOrderCalculator.PendingEntry> RentPrimeOrderPendingEntryList(int capacityHint)
{
List<List<PrimeOrderCalculator.PendingEntry>>? pool = _primeOrderPendingEntryListPool;
if (pool is not null)
{
int poolCount = pool.Count;
if (poolCount > 0)
{
int lastIndex = poolCount - 1;
List<PrimeOrderCalculator.PendingEntry> list = pool[lastIndex];
pool.RemoveAt(lastIndex);
if (list.Capacity < capacityHint)
{
list.Capacity = capacityHint;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static List<PrimeOrderCalculator.PendingEntry> RentPrimeOrderPendingEntryList(int capacityHint)
{
FixedCapacityStack<List<PrimeOrderCalculator.PendingEntry>>? 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<PrimeOrderCalculator.PendingEntry>(capacityHint);
}
return new List<PrimeOrderCalculator.PendingEntry>(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void ReturnPrimeOrderPendingEntryList(List<PrimeOrderCalculator.PendingEntry> list)
{
List<List<PrimeOrderCalculator.PendingEntry>>? pool = _primeOrderPendingEntryListPool;
if (pool is null)
{
pool = new List<List<PrimeOrderCalculator.PendingEntry>>(4);
_primeOrderPendingEntryListPool = pool;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
internal static void ReturnPrimeOrderPendingEntryList(List<PrimeOrderCalculator.PendingEntry> list) => _primeOrderPendingEntryListPool!.Push(list);
pool.Add(list);
}
[ThreadStatic]
private static FixedCapacityStack<FixedCapacityStack<ulong>>? _ulongStackPool;
[ThreadStatic]
private static List<Stack<ulong>>? _ulongStackPool;
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static FixedCapacityStack<ulong> RentUlongStack(int capacityHint)
{
FixedCapacityStack<FixedCapacityStack<ulong>>? 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<ulong> RentUlongStack(int capacityHint)
{
List<Stack<ulong>>? pool = _ulongStackPool;
if (pool is not null)
{
int poolCount = pool.Count;
if (poolCount > 0)
{
int lastIndex = poolCount - 1;
Stack<ulong> stack = pool[lastIndex];
pool.RemoveAt(lastIndex);
return new FixedCapacityStack<ulong>(capacityHint);
}
if (stack.Count < capacityHint)
{
stack.EnsureCapacity(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void ReturnUlongStack(FixedCapacityStack<ulong> stack) => _ulongStackPool!.Push(stack);
return stack;
}
}
[ThreadStatic]
private static Dictionary<ulong, int>? _factorCountDictionary;
return new Stack<ulong>(capacityHint);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static Dictionary<ulong, int> RentFactorCountDictionary()
{
Dictionary<ulong, int>? dictionary = _factorCountDictionary;
if (dictionary is null)
{
return new Dictionary<ulong, int>(8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnUlongStack(Stack<ulong> stack)
{
List<Stack<ulong>>? pool = _ulongStackPool;
if (pool is null)
{
pool = new List<Stack<ulong>>(4);
_ulongStackPool = pool;
}
_factorCountDictionary = null;
return dictionary;
}
pool.Add(stack);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void ReturnFactorCountDictionary(Dictionary<ulong, int> dictionary) => _factorCountDictionary = dictionary;
[ThreadStatic]
private static Dictionary<ulong, int>? _factorCountDictionary;
[ThreadStatic]
private static Dictionary<ulong, int>? _factorScratchDictionary;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<ulong, int> RentFactorCountDictionary()
{
Dictionary<ulong, int>? dictionary = _factorCountDictionary;
if (dictionary is null)
{
return new Dictionary<ulong, int>(8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static Dictionary<ulong, int> RentFactorScratchDictionary()
{
if (_factorScratchDictionary is { } dictionary)
{
_factorScratchDictionary = null;
return dictionary;
}
_factorCountDictionary = null;
return dictionary;
}
return new Dictionary<ulong, int>(8);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReturnFactorCountDictionary(Dictionary<ulong, int> dictionary)
{
_factorCountDictionary = dictionary;
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static void ReturnFactorScratchDictionary(Dictionary<ulong, int> dictionary) => _factorScratchDictionary = dictionary;
[ThreadStatic]
private static Dictionary<ulong, int>? _factorScratchDictionary;
[ThreadStatic]
private static ExponentRemainderStepperCpu _exponentRemainderStepperCpu;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Dictionary<ulong, int> RentFactorScratchDictionary()
{
Dictionary<ulong, int>? dictionary = _factorScratchDictionary;
if (dictionary is null)
{
return new Dictionary<ulong, int>(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<ulong, int> 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;
}
}
}
+2 -3
View File
@@ -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;
+40
View File
@@ -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;
}
}
}
}
+53 -48
View File
@@ -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<Task>? GetScheduledTasks()
{
return _threadPool.GetScheduledTasks();
}
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
protected override IEnumerable<Task>? GetScheduledTasks() => _threadPool.GetScheduledTasks();
}