From 7a242b3bfa34da2e56735622b65fbf727257068f Mon Sep 17 00:00:00 2001 From: mlemanczyk Date: Wed, 10 Dec 2025 12:47:25 +0100 Subject: [PATCH] PrimeOrderCalculator.PartialFactorCpu simplified to work only with spans and throw exception if the number of factors is exceeded. PrimeOrderMode removed in favor of bool flag. --- .../Pow2ModBenchmarks.cs | 12 +- EvenPerfectBitScanner/Program.cs | 2 + .../Gpu/PrimeOrderGpuHeuristicsTests.cs | 2 +- .../PrimeOrderCalculatorTests.cs | 22 +- PerfectNumbers.Core/ArrayPools.cs | 104 +++++++ .../Gpu/CalculateOrderKernelConfig.cs | 4 +- .../Gpu/PrimeOrderGpuHeuristics.cs | 4 +- PerfectNumbers.Core/MersenneDivisorCycles.cs | 18 +- PerfectNumbers.Core/PartialFactorResult.cs | 149 +++++----- .../PrimeOrderCalculator.Cpu.cs | 278 +++++++----------- .../PrimeOrderCalculator.Gpu.cs | 16 +- .../PrimeOrderCalculator.Hybrid.cs | 4 +- .../PrimeOrderCalculator.Shared.cs | 2 +- PerfectNumbers.Core/PrimeOrderCalculator.cs | 20 +- PerfectNumbers.Core/UnboundedTaskScheduler.cs | 2 +- 15 files changed, 337 insertions(+), 302 deletions(-) create mode 100644 PerfectNumbers.Core/ArrayPools.cs diff --git a/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs index a50be82..772a025 100644 --- a/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs +++ b/EvenPerfectBitScanner.Benchmarks/Pow2ModBenchmarks.cs @@ -623,7 +623,7 @@ public class Pow2ModBenchmarks modulus, previousPrimeOrder, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (order != 0UL) { @@ -645,7 +645,7 @@ public class Pow2ModBenchmarks UInt128 order = PrimeOrderCalculator.CalculateCpu( modulus, previousWidePrimeOrder, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (order != UInt128.Zero) { @@ -693,7 +693,7 @@ public class Pow2ModBenchmarks modulus, _previousPrimeOrder, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (order != 0UL) { @@ -723,7 +723,7 @@ public class Pow2ModBenchmarks modulus, _previousPrimeOrder, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (order != 0UL) { @@ -750,7 +750,7 @@ public class Pow2ModBenchmarks UInt128 order = PrimeOrderCalculator.CalculateCpu( modulus, _previousWidePrimeOrder, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (order != UInt128.Zero) { @@ -778,7 +778,7 @@ public class Pow2ModBenchmarks gpu, modulus, _previousWidePrimeOrder, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (order != UInt128.Zero) { diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 55adfb2..0e95a9c 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -448,6 +448,8 @@ internal static class Program // Configure batch size for GPU primality sieve PrimeTester.GpuBatchSize = gpuPrimeBatch; + _ = AcceleratorPool.Shared; + Stopwatch? stopwatch = null; if (testMode) { diff --git a/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs b/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs index f4ebe65..453ebf2 100644 --- a/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/PrimeOrderGpuHeuristicsTests.cs @@ -398,7 +398,7 @@ public class PrimeOrderGpuHeuristicsTests PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0; MontgomeryDivisorData divisorData = MontgomeryDivisorDataPool.Shared.FromModulus(prime); - ulong result = PrimeOrderCalculator.CalculateGpu(gpu, prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + ulong result = PrimeOrderCalculator.CalculateGpu(gpu, prime, null, divisorData, PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); result.Should().Be(3UL); } diff --git a/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs b/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs index 0633928..37e5601 100644 --- a/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs +++ b/PerfectNumbers.Core.Tests/PrimeOrderCalculatorTests.cs @@ -50,7 +50,7 @@ public class PrimeOrderCalculatorTests prime, previousOrder: null, MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); PrimeOrderCalculatorAccelerator.Return(gpu); @@ -70,14 +70,14 @@ public class PrimeOrderCalculatorTests prime, previousOrder, MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); ulong strict = PrimeOrderCalculator.CalculateGpu( gpu, prime, previousOrder: null, MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.StrictDefault); PrimeOrderCalculatorAccelerator.Return(gpu); @@ -96,7 +96,7 @@ public class PrimeOrderCalculatorTests prime, previousOrder: null, MontgomeryDivisorDataPool.Shared.FromModulus(prime), - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); PrimeOrderCalculatorAccelerator.Return(gpu); result.Should().Be(expectedOrder); @@ -106,11 +106,11 @@ public class PrimeOrderCalculatorTests [Trait("Category", "Fast")] public void Calculate_heuristic_mode_returns_unresolved_when_phi_cannot_be_factored() { - var config = new PrimeOrderCalculator.PrimeOrderSearchConfig( + var config = new PrimeOrderCalculator.PrimeOrderCalculatorConfig( smallFactorLimit: 1, pollardRhoMilliseconds: 0, maxPowChecks: 8, - mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic); + strictMode: false); var gpu = PrimeOrderCalculatorAccelerator.Rent(1); ulong result = PrimeOrderCalculator.CalculateGpu( @@ -128,11 +128,11 @@ public class PrimeOrderCalculatorTests [Trait("Category", "Fast")] public void Calculate_heuristic_mode_attempts_candidates_before_falling_back_to_strict_mode() { - var heuristicConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig( + var heuristicConfig = new PrimeOrderCalculator.PrimeOrderCalculatorConfig( smallFactorLimit: 2, pollardRhoMilliseconds: 0, maxPowChecks: 1, - mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic); + strictMode: false); var gpu = PrimeOrderCalculatorAccelerator.Rent(1); try @@ -147,11 +147,11 @@ public class PrimeOrderCalculatorTests heuristic.Should().Be(ComputeOrderByDoubling(239UL)); - var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig( + var strictConfig = new PrimeOrderCalculator.PrimeOrderCalculatorConfig( smallFactorLimit: 2, pollardRhoMilliseconds: 0, maxPowChecks: 1, - mode: PrimeOrderCalculator.PrimeOrderMode.Strict); + strictMode: true); ulong strict = PrimeOrderCalculator.CalculateGpu( gpu, @@ -179,7 +179,7 @@ public class PrimeOrderCalculatorTests gpu, prime, previousOrder: null, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); PrimeOrderCalculatorAccelerator.Return(gpu); result.Should().Be((UInt128)1229782938247309446UL); diff --git a/PerfectNumbers.Core/ArrayPools.cs b/PerfectNumbers.Core/ArrayPools.cs new file mode 100644 index 0000000..5e64061 --- /dev/null +++ b/PerfectNumbers.Core/ArrayPools.cs @@ -0,0 +1,104 @@ +using System.Runtime.CompilerServices; + +namespace PerfectNumbers.Core +{ + public static class Pools + { + private const int PoolCapacity = 10240; //PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount << 8; + + [ThreadStatic] + private static IntArrayPool? _intArrayPool; + internal static IntArrayPool ExclusiveIntArray + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return _intArrayPool ??= new(PoolCapacity); + } + } + + + [ThreadStatic] + private static UlongArrayPool? _ulongArrayPool; + internal static UlongArrayPool ExclusiveUlongArray + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get + { + return _ulongArrayPool ??= new(PoolCapacity); + } + } + } + + public struct IntArrayPool(int poolCapacity) + { + private readonly int[][] _arrays = new int[poolCapacity][]; + private int _count = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int[] Rent(int length) + { + int available = _count; + if (available == 0) + { + return new int[length]; + } + + int[][] arrays = _arrays; + available--; + _count = available; + + int[] candidate = arrays[available]; + return candidate.Length >= length ? candidate : new int[length]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Return(int[] array) + { + int[][] arrays = _arrays; + int available = _count; + + if (available < arrays.Length) + { + arrays[available] = array; + _count = available + 1; + } + } + } + + public struct UlongArrayPool(int poolCapacity) + { + private readonly ulong[][] _arrays = new ulong[poolCapacity][]; + private int _count = 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ulong[] Rent(int length) + { + int available = _count; + if (available == 0) + { + return new ulong[length]; + } + + ulong[][] arrays = _arrays; + available--; + _count = available; + + ulong[] candidate = arrays[available]; + return candidate.Length >= length ? candidate : new ulong[length]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Return(ulong[] array) + { + ulong[][] arrays = _arrays; + int available = _count; + + if (available < arrays.Length) + { + arrays[available] = array; + _count = available + 1; + } + } + } +} diff --git a/PerfectNumbers.Core/Gpu/CalculateOrderKernelConfig.cs b/PerfectNumbers.Core/Gpu/CalculateOrderKernelConfig.cs index 1f87ff8..30e43e4 100644 --- a/PerfectNumbers.Core/Gpu/CalculateOrderKernelConfig.cs +++ b/PerfectNumbers.Core/Gpu/CalculateOrderKernelConfig.cs @@ -1,10 +1,10 @@ namespace PerfectNumbers.Core.Gpu; -public readonly struct CalculateOrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode) +public readonly struct CalculateOrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, bool strictMode) { public readonly ulong PreviousOrder = previousOrder; public readonly byte HasPreviousOrder = hasPreviousOrder; public readonly uint SmallFactorLimit = smallFactorLimit; public readonly int MaxPowChecks = maxPowChecks; - public readonly int Mode = mode; + public readonly int Mode = strictMode ? 1 : 0; } diff --git a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index 7b2430e..4528823 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -242,7 +242,7 @@ internal static partial class PrimeOrderGpuHeuristics PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong? previousOrder, - PrimeOrderCalculator.PrimeOrderSearchConfig config, + PrimeOrderCalculator.PrimeOrderCalculatorConfig config, in MontgomeryDivisorData divisorData, out ulong order) { @@ -258,7 +258,7 @@ internal static partial class PrimeOrderGpuHeuristics byte hasPrevious = previousOrder.HasValue ? (byte)1 : (byte)0; ulong previousValue = previousOrder ?? 0UL; - var kernelConfig = new CalculateOrderKernelConfig(previousValue, hasPrevious, limit, config.MaxPowChecks, (int)config.Mode); + var kernelConfig = new CalculateOrderKernelConfig(previousValue, hasPrevious, limit, config.MaxPowChecks, config.StrictMode); ref var buffers = ref gpu.CalculateOrderKernelBuffers; var kernelLauncher = gpu.CalculateOrderKernelLauncher; diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index ca5d664..9ebb2a5 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -404,7 +404,7 @@ public class MersenneDivisorCycles UInt128 wideOrder = PrimeOrderCalculator.CalculateCpu( divisor, previousOrder: null, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (wideOrder != UInt128.Zero) { return wideOrder; @@ -452,7 +452,7 @@ public class MersenneDivisorCycles gpu, divisor, previousOrder: null, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (wideOrder != UInt128.Zero) { return wideOrder; @@ -500,7 +500,7 @@ public class MersenneDivisorCycles gpu, divisor, previousOrder: null, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (wideOrder != UInt128.Zero) { return wideOrder; @@ -554,7 +554,7 @@ public class MersenneDivisorCycles divisor, previousOrder: null, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (computedOrder != 0UL) { cycleLength = computedOrder; @@ -639,7 +639,7 @@ public class MersenneDivisorCycles divisor, previousOrder: null, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (computedOrder != 0UL) { cycleLength = computedOrder; @@ -724,7 +724,7 @@ public class MersenneDivisorCycles divisor, previousOrder: null, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (computedOrder != 0UL) { cycleLength = computedOrder; @@ -1401,7 +1401,7 @@ public class MersenneDivisorCycles divisor, previousOrder: null, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (computedOrder != 0UL) { cycleLength = computedOrder; @@ -1434,7 +1434,7 @@ public class MersenneDivisorCycles divisor, previousOrder: null, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (computedOrder != 0UL) { cycleLength = computedOrder; @@ -1467,7 +1467,7 @@ public class MersenneDivisorCycles divisor, previousOrder: null, divisorData, - PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault); + PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault); if (computedOrder != 0UL) { cycleLength = computedOrder; diff --git a/PerfectNumbers.Core/PartialFactorResult.cs b/PerfectNumbers.Core/PartialFactorResult.cs index f0800f3..b711f69 100644 --- a/PerfectNumbers.Core/PartialFactorResult.cs +++ b/PerfectNumbers.Core/PartialFactorResult.cs @@ -1,75 +1,75 @@ namespace PerfectNumbers.Core { - public sealed class PartialFactorResult - { - [ThreadStatic] - private static PartialFactorResult? s_poolHead; + public sealed class PartialFactorResult + { + [ThreadStatic] + private static PartialFactorResult? s_poolHead; public ulong[]? Factors; public int[]? Exponents; - private PartialFactorResult? _next; - public ulong Cofactor; - public bool FullyFactored; + private PartialFactorResult? _next; + public ulong Cofactor; + public bool FullyFactored; public int Count; public bool CofactorIsPrime; - private PartialFactorResult() - { - } + private PartialFactorResult() + { + } - public static PartialFactorResult Rent(in ulong[]? factors, in int[]? exponents, ulong cofactor, bool fullyFactored, int count, bool cofactorIsPrime) - { - PartialFactorResult? instance = s_poolHead; - if (instance is null) - { - instance = new PartialFactorResult(); - } - else - { - s_poolHead = instance._next; - } + public static PartialFactorResult Rent(in ulong[]? factors, in int[]? exponents, ulong cofactor, bool fullyFactored, int count, bool cofactorIsPrime) + { + PartialFactorResult? instance = s_poolHead; + if (instance is null) + { + instance = new PartialFactorResult(); + } + else + { + s_poolHead = instance._next; + } - instance._next = null; + instance._next = null; instance.Factors = factors; instance.Exponents = exponents; instance.Cofactor = cofactor; - instance.FullyFactored = fullyFactored; + instance.FullyFactored = fullyFactored; instance.Count = count; instance.CofactorIsPrime = cofactorIsPrime; - return instance; - } + return instance; + } - public static PartialFactorResult Rent(ulong cofactor, bool fullyFactored, int count, bool cofactorIsPrime) - { - PartialFactorResult? instance = s_poolHead; - if (instance is null) - { - instance = new PartialFactorResult(); - } - else - { - s_poolHead = instance._next; - } + public static PartialFactorResult Rent(ulong cofactor, bool fullyFactored, int count, bool cofactorIsPrime) + { + PartialFactorResult? instance = s_poolHead; + if (instance is null) + { + instance = new PartialFactorResult(); + } + else + { + s_poolHead = instance._next; + } - instance._next = null; + instance._next = null; instance.Factors = null; instance.Exponents = null; instance.Cofactor = cofactor; - instance.FullyFactored = fullyFactored; + instance.FullyFactored = fullyFactored; instance.Count = count; instance.CofactorIsPrime = cofactorIsPrime; - return instance; - } + return instance; + } - public static readonly PartialFactorResult Empty = new() + public static readonly PartialFactorResult Empty = new() { Cofactor = 1UL, FullyFactored = true }; - public void WithAdditionalPrime(ulong prime) - { - ulong[]? sourceFactors = Factors; + public void WithAdditionalPrime(ulong prime) + { + ulong[]? sourceFactors = Factors; int sourceCount = Count; if (sourceFactors is null || sourceCount == 0) { @@ -78,14 +78,12 @@ namespace PerfectNumbers.Core } int newSize = sourceCount + 1; - int[] extendedExponents; - ulong[] extendedFactors = sourceFactors.Length >= newSize ? sourceFactors : ResizeFactors(sourceFactors, sourceCount, newSize); int[] sourceExponents = Exponents!; - extendedExponents = sourceExponents.Length >= newSize + int[] extendedExponents = sourceExponents.Length >= newSize ? sourceExponents : ResizeExponents(sourceCount, newSize, sourceExponents); @@ -99,33 +97,31 @@ namespace PerfectNumbers.Core FullyFactored = true; Count = newSize; CofactorIsPrime = false; - } + } - private static int[] ResizeExponents(int sourceCount, int newSize, int[] sourceExponents) + private static int[] ResizeExponents(int sourceCount, int newSize, in int[] sourceExponents) { - int[] extendedExponents = ThreadStaticPools.IntPool.Rent(newSize); + int[] extendedExponents = Pools.ExclusiveIntArray.Rent(newSize); Array.Copy(sourceExponents, 0, extendedExponents, 0, sourceCount); - ThreadStaticPools.IntPool.Return(sourceExponents, clearArray: false); + Pools.ExclusiveIntArray.Return(sourceExponents); return extendedExponents; } - private static ulong[] ResizeFactors(ulong[] sourceFactors, int sourceCount, int newSize) + private static ulong[] ResizeFactors(in ulong[] sourceFactors, int sourceCount, int newSize) { - ulong[] extendedFactors = ThreadStaticPools.UlongPool.Rent(newSize); + ulong[] extendedFactors = Pools.ExclusiveUlongArray.Rent(newSize); Array.Copy(sourceFactors, 0, extendedFactors, 0, sourceCount); - ThreadStaticPools.UlongPool.Return(sourceFactors, clearArray: false); + Pools.ExclusiveUlongArray.Return(sourceFactors); return extendedFactors; } private void InitializeWithPrime(ulong prime) { - ulong[] source = ThreadStaticPools.UlongPool.Rent(1); - int[] exponents = ThreadStaticPools.IntPool.Rent(1); - // source = new ulong[1]; - // exponents = new int[1]; - source[0] = prime; + ulong[] factors = Pools.ExclusiveUlongArray.Rent(1); + int[] exponents = Pools.ExclusiveIntArray.Rent(1); + factors[0] = prime; exponents[0] = 1; - Factors = source; + Factors = factors; Exponents = exponents; Cofactor = 1UL; FullyFactored = true; @@ -134,26 +130,25 @@ namespace PerfectNumbers.Core } public void Dispose() - { - if (this != Empty) - { - ulong[]? factors = Factors; - if (factors is not null) - { + { + if (this != Empty) + { + + ulong[]? factors = Factors; + + if (factors is not null) + { int[] exponents = Exponents!; Factors = null; Exponents = null; - // if (factors.Length > 1) - // { - ThreadStaticPools.UlongPool.Return(factors, clearArray: false); - ThreadStaticPools.IntPool.Return(exponents, clearArray: false); - // } - } + Pools.ExclusiveUlongArray.Return(factors); + Pools.ExclusiveIntArray.Return(exponents); + } - _next = s_poolHead; - s_poolHead = this; - } - } - } + _next = s_poolHead; + s_poolHead = this; + } + } + } } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index f5f238a..00a9644 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -12,13 +12,41 @@ namespace PerfectNumbers.Core; internal static partial class PrimeOrderCalculator { - internal struct PendingEntry(ulong value, bool knownComposite) + internal sealed class PendingEntry(ulong value, bool knownComposite) { - public readonly ulong Value = value; + [ThreadStatic] + private static PendingEntry? _poolHead; + private PendingEntry? _next; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static PendingEntry Rent(ulong value, bool knownComposite) + { + var poolHead = _poolHead; + if (poolHead == null) + { + return new(value, knownComposite); + } + + _poolHead = poolHead._next; + poolHead.Value = value; + poolHead.KnownComposite = knownComposite; + poolHead.HasKnownPrimality = knownComposite; + poolHead.IsPrime = false; + return poolHead; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void Return(PendingEntry entry) + { + entry._next = _poolHead; + _poolHead = entry; + } + + public ulong Value = value; public bool KnownComposite = knownComposite; public bool HasKnownPrimality = knownComposite; public bool IsPrime = false; - + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void WithPrimality(bool isPrime) { @@ -32,7 +60,7 @@ internal static partial class PrimeOrderCalculator ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config) + in PrimeOrderCalculatorConfig config) { // TODO: Is this condition ever met on EvenPerfectBitScanner's execution path? If not, we can add a clarification comment and comment out the entire block. We want to support p candidates at least greater or equal to 31. if (prime <= 3UL) @@ -61,7 +89,7 @@ internal static partial class PrimeOrderCalculator public static UInt128 CalculateCpu( in UInt128 prime, in UInt128? previousOrder, - in PrimeOrderSearchConfig config) + in PrimeOrderCalculatorConfig config) { MontgomeryDivisorData divisorData; UInt128 result; @@ -97,7 +125,7 @@ internal static partial class PrimeOrderCalculator return result; } - private static UInt128 CalculateWideInternalCpu(in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config) + private static UInt128 CalculateWideInternalCpu(in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderCalculatorConfig config) { if (prime <= UInt128.One) { @@ -127,7 +155,7 @@ internal static partial class PrimeOrderCalculator private static ulong RunHeuristicPipelineCpu( ulong prime, ulong? previousOrder, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, in MontgomeryDivisorData divisorData, ulong phi, PartialFactorResult phiFactors) @@ -145,7 +173,7 @@ internal static partial class PrimeOrderCalculator return candidateOrder; } - if (config.Mode == PrimeOrderMode.Strict) + if (config.StrictMode) { orderFactors?.Dispose(); return CalculateByFactorizationCpu(prime, divisorData); @@ -163,7 +191,7 @@ internal static partial class PrimeOrderCalculator PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong? previousOrder, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, in MontgomeryDivisorData divisorData, ulong phi, PartialFactorResult phiFactors) @@ -181,7 +209,7 @@ internal static partial class PrimeOrderCalculator return candidateOrder; } - if (config.Mode == PrimeOrderMode.Strict) + if (config.StrictMode) { orderFactors?.Dispose(); return CalculateByFactorizationCpu(prime, divisorData); @@ -199,7 +227,7 @@ internal static partial class PrimeOrderCalculator in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, in UInt128 phi, in PartialFactorResult128 phiFactors) { @@ -216,7 +244,7 @@ internal static partial class PrimeOrderCalculator return candidateOrder; } - if (config.Mode == PrimeOrderMode.Strict) + if (config.StrictMode) { return FinishStrictlyWideCpu(prime, divisorData); } @@ -354,7 +382,7 @@ internal static partial class PrimeOrderCalculator return order; } - private static bool TryConfirmOrderWideCpu(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config) + private static bool TryConfirmOrderWideCpu(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData? divisorData, in PrimeOrderCalculatorConfig config) { if (order == UInt128.Zero) { @@ -416,7 +444,7 @@ internal static partial class PrimeOrderCalculator in UInt128 order, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, out UInt128 result) { result = UInt128.Zero; @@ -608,15 +636,12 @@ internal static partial class PrimeOrderCalculator private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData) { - ArrayPool ulongPool = ThreadStaticPools.UlongPool; - ArrayPool intPool = ThreadStaticPools.IntPool; - ReadOnlySpan factorSpan = factors.Factors; ReadOnlySpan exponentsSpan = factors.Exponents; int length = factors.Count; - ulong[] tempFactorsArray = ulongPool.Rent(length + 1); - int[] tempExponentsArray = intPool.Rent(length + 1); + ulong[] tempFactorsArray = Pools.ExclusiveUlongArray.Rent(length + 1); + int[] tempExponentsArray = Pools.ExclusiveIntArray.Rent(length + 1); Span ulongBuffer = tempFactorsArray; factorSpan.CopyTo(ulongBuffer); @@ -664,7 +689,7 @@ internal static partial class PrimeOrderCalculator continue; } - heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(ExponentHardLimit); + heapCandidateArray ??= Pools.ExclusiveUlongArray.Rent(ExponentHardLimit); heapEvaluationArray ??= ThreadStaticPools.BoolPool.Rent(ExponentHardLimit); ProcessExponentLoweringPrime(heapCandidateArray.AsSpan(0, exponent), heapEvaluationArray.AsSpan(0, exponent), ref order, primeFactor, exponent, ref stepper); } @@ -673,11 +698,11 @@ internal static partial class PrimeOrderCalculator if (heapCandidateArray is not null) { - ThreadStaticPools.UlongPool.Return(heapCandidateArray); + Pools.ExclusiveUlongArray.Return(heapCandidateArray); ThreadStaticPools.BoolPool.Return(heapEvaluationArray!); } - ulongPool.Return(tempFactorsArray, clearArray: false); + Pools.ExclusiveUlongArray.Return(tempFactorsArray); return order; } @@ -735,7 +760,7 @@ internal static partial class PrimeOrderCalculator ulong prime, ulong order, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, out PartialFactorResult? reusableFactorization) { reusableFactorization = null; @@ -783,7 +808,7 @@ internal static partial class PrimeOrderCalculator ulong prime, ulong order, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, out PartialFactorResult? reusableFactorization) { reusableFactorization = null; @@ -873,7 +898,7 @@ internal static partial class PrimeOrderCalculator continue; } - heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(ExponentHardLimit); + heapCandidateArray ??= Pools.ExclusiveUlongArray.Rent(ExponentHardLimit); if (ValidateOrderForFactor(heapCandidateArray.AsSpan(0, exponent), primeFactor, exponent, order, ref stepper)) { violates = true; @@ -885,7 +910,7 @@ internal static partial class PrimeOrderCalculator if (heapCandidateArray is not null) { - ThreadStaticPools.UlongPool.Return(heapCandidateArray); + Pools.ExclusiveUlongArray.Return(heapCandidateArray); } return !violates; @@ -944,7 +969,7 @@ internal static partial class PrimeOrderCalculator ulong order, ulong? previousOrder, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, PartialFactorResult phiFactors, PartialFactorResult? cachedOrderFactors, out ulong result) @@ -1079,7 +1104,7 @@ internal static partial class PrimeOrderCalculator ulong order, ulong? previousOrder, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, PartialFactorResult phiFactors, PartialFactorResult? cachedOrderFactors, out ulong result) @@ -1211,7 +1236,7 @@ internal static partial class PrimeOrderCalculator ulong order, ulong? previousOrder, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, PartialFactorResult phiFactors, PartialFactorResult? cachedOrderFactors, out ulong result) @@ -1437,7 +1462,7 @@ internal static partial class PrimeOrderCalculator int group = GetGroup(value, threshold1, threshold2, threshold3); if (group == 0) { - return new CandidateKey(int.MaxValue, long.MaxValue, long.MaxValue); + return CandidateKey.Max; } bool isGe = !hasPrevious || value >= previous; @@ -1453,14 +1478,15 @@ internal static partial class PrimeOrderCalculator else { ulong reference = hasPrevious ? previous : 0UL; - ulong distance = hasPrevious ? (value > reference ? value - reference : reference - value) : value; - secondary = (long)distance; + reference = hasPrevious ? (value > reference ? value - reference : reference - value) : value; + secondary = (long)reference; tertiary = (long)value; } return new CandidateKey(primary, secondary, tertiary); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int ComputePrimary(int group, bool isGe, int previousGroup) { int groupOffset = group switch @@ -1484,6 +1510,7 @@ internal static partial class PrimeOrderCalculator return groupOffset + (isGe ? 0 : 1); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetGroup(ulong value, ulong threshold1, ulong threshold2, ulong threshold3) { if (value <= threshold1) @@ -1504,8 +1531,6 @@ internal static partial class PrimeOrderCalculator return 0; } - private static readonly Comparer _comparer = Comparer.Default; - private static void BuildCandidates(ulong order, ulong[] factors, int[] exponents, int count, List candidates, int limit) { if (count == 0) @@ -1578,7 +1603,7 @@ internal static partial class PrimeOrderCalculator } } - private static bool TryConfirmCandidateCpu(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget) + private static bool TryConfirmCandidateCpu(ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderCalculatorConfig config, ref int powUsed, int powBudget) { PartialFactorResult factorization = PartialFactorCpu(candidate, config); @@ -1638,7 +1663,7 @@ internal static partial class PrimeOrderCalculator continue; } - heapCandidateArray ??= ThreadStaticPools.UlongPool.Rent(ExponentHardLimit); + heapCandidateArray ??= Pools.ExclusiveUlongArray.Rent(ExponentHardLimit); if (CheckCandidateViolation(heapCandidateArray.AsSpan(0, exponent), primeFactor, exponent, candidate, prime, ref powUsed, powBudget, ref stepper)) { violates = true; @@ -1650,14 +1675,14 @@ internal static partial class PrimeOrderCalculator if (heapCandidateArray is not null) { - ThreadStaticPools.UlongPool.Return(heapCandidateArray); + Pools.ExclusiveUlongArray.Return(heapCandidateArray); } factorization.Dispose(); return !violates; } - private static bool TryConfirmCandidateHybrid(PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget) + private static bool TryConfirmCandidateHybrid(PrimeOrderCalculatorAccelerator gpu, ulong prime, ulong candidate, in MontgomeryDivisorData divisorData, in PrimeOrderCalculatorConfig config, ref int powUsed, int powBudget) { PartialFactorResult factorization = PartialFactorHybrid(gpu, candidate, config); @@ -1839,14 +1864,12 @@ internal static partial class PrimeOrderCalculator return exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL; } - // private static ulong _partialFactorHits; - // private static ulong _partialFactorPendingHits; - // private static ulong _partialFactorCofactorHits; - - private static bool _goToGpu = true; private static int _cpuCount; - private static PartialFactorResult PartialFactorCpu(ulong value, in PrimeOrderSearchConfig config) + private static readonly ulong[] _emptyUlongArray = []; + private static readonly int[] _emptyIntArray = []; + + private static PartialFactorResult PartialFactorCpu(ulong value, in PrimeOrderCalculatorConfig config) { if (value <= 1UL) { @@ -1856,8 +1879,6 @@ internal static partial class PrimeOrderCalculator // stackalloc is faster than pooling Span primeSlots = stackalloc ulong[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; Span exponentSlots = stackalloc int[PrimeOrderConstants.GpuSmallPrimeFactorSlots]; - ulong[] primeSlotArray = []; - int[] exponentSlotArray = []; // We don't need to worry about leftovers, because we always use indexes within the calculated counts // primeSlots.Clear(); @@ -1866,15 +1887,13 @@ internal static partial class PrimeOrderCalculator uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit; ulong remaining = value; - List pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2); + List pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(PrimeOrderConstants.GpuSmallPrimeFactorSlots); PopulateSmallPrimeFactorsCpu( value, limit, - ref primeSlots, - ref exponentSlots, - ref primeSlotArray, - ref exponentSlotArray, + primeSlots, + exponentSlots, out int factorCount, out remaining); @@ -1884,16 +1903,16 @@ internal static partial class PrimeOrderCalculator if (config.PollardRhoMilliseconds > 0) { long deadlineTimestamp = CreateDeadlineTimestamp(config.PollardRhoMilliseconds); - Stack compositeStack = ThreadStaticPools.RentUlongStack(4); + Stack compositeStack = ThreadStaticPools.RentUlongStack(PrimeOrderConstants.GpuSmallPrimeFactorSlots); compositeStack.Push(remaining); - CollectFactorsCpu(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); + CollectFactorsCpu(primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); if (limitReached) { while (compositeStack.Count > 0) { - pending.Add(new PendingEntry(compositeStack.Pop(), knownComposite: false)); + pending.Add(PendingEntry.Rent(compositeStack.Pop(), knownComposite: false)); } } @@ -1902,7 +1921,7 @@ internal static partial class PrimeOrderCalculator } else { - pending.Add(new PendingEntry(remaining, knownComposite: false)); + pending.Add(PendingEntry.Rent(remaining, knownComposite: false)); } } @@ -1919,6 +1938,7 @@ internal static partial class PrimeOrderCalculator { cofactor = checked(cofactor * composite); cofactorContainsComposite = true; + PendingEntry.Return(entry); continue; } @@ -1926,16 +1946,12 @@ internal static partial class PrimeOrderCalculator { bool isPrime = PrimeTesterByLastDigit.IsPrimeCpu(composite); - // Atomic.Add(ref _partialFactorPendingHits, 1UL); - // Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}"); - entry.WithPrimality(isPrime); - pending[index] = entry; } if (entry.IsPrime) { - AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); + AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); } // composite is never smaller on the execution path // else if (composite > 1UL) @@ -1944,6 +1960,8 @@ internal static partial class PrimeOrderCalculator cofactor = checked(cofactor * composite); cofactorContainsComposite = true; } + + PendingEntry.Return(entry); } bool cofactorIsPrime; @@ -1958,9 +1976,6 @@ internal static partial class PrimeOrderCalculator else { cofactorIsPrime = PrimeTesterByLastDigit.IsPrimeCpu(cofactor); - - // Atomic.Add(ref _partialFactorCofactorHits, 1UL); - // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); } PartialFactorResult result; @@ -1989,11 +2004,8 @@ internal static partial class PrimeOrderCalculator // goto ReturnResult; // } - ArrayPool ulongPool = ThreadStaticPools.UlongPool; - ArrayPool intPool = ThreadStaticPools.IntPool; - - ulong[] factorsArray = ulongPool.Rent(factorCount); - int[] exponentsArray = intPool.Rent(factorCount); + ulong[] factorsArray = Pools.ExclusiveUlongArray.Rent(factorCount); + int[] exponentsArray = Pools.ExclusiveIntArray.Rent(factorCount); for (int i = 0; i < factorCount; i++) { @@ -2016,18 +2028,12 @@ internal static partial class PrimeOrderCalculator result = PartialFactorResult.Rent(factorsArray, exponentsArray, cofactor, limitReached, factorCount, cofactorIsPrime); ReturnResult: - if (primeSlotArray.Length > 0) - { - ThreadStaticPools.UlongPool.Return(primeSlotArray, clearArray: false); - ThreadStaticPools.IntPool.Return(exponentSlotArray, clearArray: false); - } - pending.Clear(); ThreadStaticPools.ReturnPrimeOrderPendingEntryList(pending); return result; } - private static PartialFactorResult PartialFactorHybrid(PrimeOrderCalculatorAccelerator gpu, ulong value, in PrimeOrderSearchConfig config) + private static PartialFactorResult PartialFactorHybrid(PrimeOrderCalculatorAccelerator gpu, ulong value, in PrimeOrderCalculatorConfig config) { if (value <= 1UL) { @@ -2051,10 +2057,8 @@ internal static partial class PrimeOrderCalculator PopulateSmallPrimeFactorsCpu( value, limit, - ref primeSlots, - ref exponentSlots, - ref primeSlotArray, - ref exponentSlotArray, + primeSlots, + exponentSlots, out int factorCount, out remaining); @@ -2067,7 +2071,7 @@ internal static partial class PrimeOrderCalculator Stack compositeStack = ThreadStaticPools.RentUlongStack(4); compositeStack.Push(remaining); - CollectFactorsHybrid(gpu, ref primeSlots, ref exponentSlots, primeSlotArray: ref primeSlotArray, ref exponentSlotArray, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); + CollectFactorsHybrid(gpu, primeSlots, exponentSlots, primeSlotArray: ref primeSlotArray, ref exponentSlotArray, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); if (limitReached) { @@ -2106,16 +2110,13 @@ internal static partial class PrimeOrderCalculator { bool isPrime = RunOnCpu() ? PrimeTesterByLastDigit.IsPrimeCpu(composite) : HeuristicCombinedPrimeTester.IsPrimeGpu(gpu, composite); - // Atomic.Add(ref _partialFactorPendingHits, 1UL); - // Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}"); - entry.WithPrimality(isPrime); pending[index] = entry; } if (entry.IsPrime) { - AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); + AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); } // composite is never smaller on the execution path // else if (composite > 1UL) @@ -2204,7 +2205,7 @@ internal static partial class PrimeOrderCalculator return result; } - private static PartialFactorResult PartialFactorGpu(PrimeOrderCalculatorAccelerator gpu, ulong value, in PrimeOrderSearchConfig config) + private static PartialFactorResult PartialFactorGpu(PrimeOrderCalculatorAccelerator gpu, ulong value, in PrimeOrderCalculatorConfig config) { if (value <= 1UL) { @@ -2232,10 +2233,8 @@ internal static partial class PrimeOrderCalculator PopulateSmallPrimeFactorsCpu( value, limit, - ref primeSlots, - ref exponentSlots, - ref primeSlotArray, - ref exponentSlotArray, + primeSlots, + exponentSlots, out factorCount, out remaining); } @@ -2249,7 +2248,7 @@ internal static partial class PrimeOrderCalculator Stack compositeStack = ThreadStaticPools.RentUlongStack(4); compositeStack.Push(remaining); - CollectFactorsCpu(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); + CollectFactorsCpu(primeSlots, exponentSlots, ref factorCount, pending, compositeStack, deadlineTimestamp, out limitReached); if (limitReached) { @@ -2288,16 +2287,13 @@ internal static partial class PrimeOrderCalculator { bool isPrime = PrimeTesterByLastDigit.IsPrimeCpu(composite); - // Atomic.Add(ref _partialFactorPendingHits, 1UL); - // Console.WriteLine($"Partial factor pending hits {Volatile.Read(ref _partialFactorPendingHits)}"); - entry.WithPrimality(isPrime); pending[index] = entry; } if (entry.IsPrime) { - AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); + AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); } // composite is never smaller on the execution path // else if (composite > 1UL) @@ -2320,9 +2316,6 @@ internal static partial class PrimeOrderCalculator else { cofactorIsPrime = PrimeTesterByLastDigit.IsPrimeCpu(cofactor); - - // Atomic.Add(ref _partialFactorCofactorHits, 1UL); - // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); } PartialFactorResult result; @@ -2390,7 +2383,7 @@ internal static partial class PrimeOrderCalculator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CollectFactorsHybrid(PrimeOrderCalculatorAccelerator gpu, ref Span primeSlots, ref Span exponentSlots, ref ulong[] primeSlotArray, ref int[] exponentSlotArray, ref int factorCount, List pending, Stack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) + private static void CollectFactorsHybrid(PrimeOrderCalculatorAccelerator gpu, Span primeSlots, Span exponentSlots, ref ulong[] primeSlotArray, ref int[] exponentSlotArray, ref int factorCount, List pending, Stack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) { while (compositeStack.Count > 0) { @@ -2399,7 +2392,7 @@ internal static partial class PrimeOrderCalculator if (isPrime) { - AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); + AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); continue; } @@ -2425,7 +2418,7 @@ internal static partial class PrimeOrderCalculator } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static void CollectFactorsCpu(ref Span primeSlots, ref Span exponentSlots, ref ulong[] primeSlotArray, ref int[] exponentSlotArray, ref int factorCount, List pending, Stack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) + private static void CollectFactorsCpu(Span primeSlots, Span exponentSlots, ref int factorCount, List pending, Stack compositeStack, long deadlineTimestamp, out bool pollardRhoDeadlineReached) { while (compositeStack.Count > 0) { @@ -2434,20 +2427,20 @@ internal static partial class PrimeOrderCalculator if (isPrime) { - AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); + AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); continue; } pollardRhoDeadlineReached = Stopwatch.GetTimestamp() > deadlineTimestamp; if (pollardRhoDeadlineReached) { - pending.Add(new PendingEntry(composite, knownComposite: true)); + pending.Add(PendingEntry.Rent(composite, knownComposite: true)); continue; } if (!TryPollardRhoCpu(composite, deadlineTimestamp, out ulong factor)) { - pending.Add(new PendingEntry(composite, knownComposite: true)); + pending.Add(PendingEntry.Rent(composite, knownComposite: true)); continue; } @@ -2469,7 +2462,7 @@ internal static partial class PrimeOrderCalculator if (isPrime) { - AddFactorToCollector(ref primeSlots, ref exponentSlots, ref primeSlotArray, ref exponentSlotArray, ref factorCount, composite, 1); + AddFactorToCollector(primeSlots, exponentSlots, ref factorCount, composite, 1); continue; } @@ -2535,10 +2528,8 @@ internal static partial class PrimeOrderCalculator private static void PopulateSmallPrimeFactorsCpu( ulong value, uint limit, - ref Span primeTargets, - ref Span exponentTargets, - ref ulong[] primeArray, - ref int[] exponentArray, + Span primeTargets, + Span exponentTargets, out int factorCount, out ulong remaining) { @@ -2591,11 +2582,7 @@ internal static partial class PrimeOrderCalculator } EnsureCollectorCapacity( - ref primeTargets, - ref exponentTargets, - ref primeArray, - ref exponentArray, - factorCount, + primeTargets, factorCount + 1); primeTargets[factorCount] = primeValue; @@ -2797,10 +2784,8 @@ internal static partial class PrimeOrderCalculator } private static void AddFactorToCollector( - ref Span primes, - ref Span exponents, - ref ulong[] primeArray, - ref int[] exponentArray, + Span primes, + Span exponents, ref int count, ulong prime, int exponent) @@ -2820,11 +2805,7 @@ internal static partial class PrimeOrderCalculator } EnsureCollectorCapacity( - ref primes, - ref exponents, - ref primeArray, - ref exponentArray, - count, + primes, count + 1); primes[count] = prime; @@ -2832,43 +2813,12 @@ internal static partial class PrimeOrderCalculator count++; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void EnsureCollectorCapacity( - ref Span primes, - ref Span exponents, - ref ulong[] primeArray, - ref int[] exponentArray, - int count, + Span primes, int requiredCapacity) { - if (requiredCapacity <= primes.Length) - { - return; - } - - int newCapacity = primes.Length << 1; - if (newCapacity < requiredCapacity) - { - newCapacity = requiredCapacity; - } - - ulong[] newPrimeArray = ThreadStaticPools.UlongPool.Rent(newCapacity); - int[] newExponentArray = ThreadStaticPools.IntPool.Rent(newCapacity); - Span newPrimeSpan = newPrimeArray.AsSpan(); - Span newExponentSpan = newExponentArray.AsSpan(); - - primes[..count].CopyTo(newPrimeSpan); - exponents[..count].CopyTo(newExponentSpan); - - if (primeArray.Length > 0) - { - ThreadStaticPools.UlongPool.Return(primeArray, clearArray: false); - ThreadStaticPools.IntPool.Return(exponentArray!, clearArray: false); - } - - primeArray = newPrimeArray; - exponentArray = newExponentArray; - primes = newPrimeSpan; - exponents = newExponentSpan; + ArgumentOutOfRangeException.ThrowIfGreaterThan(requiredCapacity, primes.Length); } private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData) @@ -2918,9 +2868,6 @@ internal static partial class PrimeOrderCalculator return order; } - // private static ulong _factorCompletelyHits; - // private static ulong _factorCompletelyPollardRhoHits; - private static void FactorCompletelyCpu(ulong value, Dictionary counts, bool knownComposite) { if (value <= 1UL) @@ -2928,9 +2875,6 @@ internal static partial class PrimeOrderCalculator return; } - // Atomic.Add(ref _factorCompletelyHits, 1UL); - // Console.WriteLine($"Factor completely hits {Volatile.Read(ref _factorCompletelyHits)}"); - bool isPrime = PrimeTesterByLastDigit.IsPrimeCpu(value); if (!knownComposite && isPrime) { @@ -2941,9 +2885,6 @@ internal static partial class PrimeOrderCalculator ulong factor = PollardRhoStrict(value); ulong quotient = value / factor; - // Atomic.Add(ref _factorCompletelyPollardRhoHits, 1UL); - // Console.WriteLine($"Factor completely after PollardRho hits {Volatile.Read(ref _factorCompletelyPollardRhoHits)}"); - isPrime = PrimeTesterByLastDigit.IsPrimeCpu(factor); if (isPrime) { @@ -2973,9 +2914,6 @@ internal static partial class PrimeOrderCalculator } else { - // Atomic.Add(ref _partialFactorCofactorHits, 1UL); - // Console.WriteLine($"Partial factor cofactor hits {Volatile.Read(ref _partialFactorCofactorHits)}"); - isPrime = PrimeTesterByLastDigit.IsPrimeCpu(quotient); } @@ -3029,7 +2967,7 @@ internal static partial class PrimeOrderCalculator return exponent.Pow2MontgomeryModWindowed(modulus); } - private static bool TryConfirmCandidateWideCpu(in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData? divisorData) + private static bool TryConfirmCandidateWideCpu(in UInt128 prime, in UInt128 candidate, in PrimeOrderCalculatorConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData? divisorData) { PartialFactorResult128 factorization = PartialFactorWide(candidate, config); if (factorization.Factors is null) @@ -3084,6 +3022,8 @@ internal static partial class PrimeOrderCalculator private readonly struct CandidateKey(int primary, long secondary, long tertiary) { + public static readonly CandidateKey Max = new(int.MaxValue, long.MaxValue, long.MaxValue); + public readonly int Primary = primary; public readonly long Secondary = secondary; public readonly long Tertiary = tertiary; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs index 53c0d8c..5d82202 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs @@ -15,7 +15,7 @@ internal static partial class PrimeOrderCalculator ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config) + in PrimeOrderCalculatorConfig config) { // TODO: Is this condition ever met on EvenPerfectBitScanner's execution path? If not, we can add a clarification comment and comment out the entire block. We want to support p candidates at least greater or equal to 31. if (prime <= 3UL) @@ -50,7 +50,7 @@ internal static partial class PrimeOrderCalculator PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, - in PrimeOrderSearchConfig config) + in PrimeOrderCalculatorConfig config) { MontgomeryDivisorData divisorData; UInt128 result; @@ -86,7 +86,7 @@ internal static partial class PrimeOrderCalculator return result; } - private static UInt128 CalculateWideInternalGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config) + private static UInt128 CalculateWideInternalGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, in PrimeOrderCalculatorConfig config) { if (prime <= UInt128.One) { @@ -118,7 +118,7 @@ internal static partial class PrimeOrderCalculator in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, in UInt128 phi, in PartialFactorResult128 phiFactors) { @@ -135,7 +135,7 @@ internal static partial class PrimeOrderCalculator return candidateOrder; } - if (config.Mode == PrimeOrderMode.Strict) + if (config.StrictMode) { return FinishStrictlyWideGpu(gpu, prime, divisorData); } @@ -268,7 +268,7 @@ internal static partial class PrimeOrderCalculator return order; } - private static bool TryConfirmOrderWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in MontgomeryDivisorData? divisorData, in PrimeOrderSearchConfig config) + private static bool TryConfirmOrderWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 order, in MontgomeryDivisorData? divisorData, in PrimeOrderCalculatorConfig config) { if (order == UInt128.Zero) { @@ -331,7 +331,7 @@ internal static partial class PrimeOrderCalculator in UInt128 order, in UInt128? previousOrder, in MontgomeryDivisorData? divisorData, - in PrimeOrderSearchConfig config, + in PrimeOrderCalculatorConfig config, out UInt128 result) { result = UInt128.Zero; @@ -575,7 +575,7 @@ internal static partial class PrimeOrderCalculator return exponent.Pow2MontgomeryModWindowed(modulus); } - private static bool TryConfirmCandidateWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData? divisorData) + private static bool TryConfirmCandidateWideGpu(PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128 candidate, in PrimeOrderCalculatorConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData? divisorData) { PartialFactorResult128 factorization = PartialFactorWide(candidate, config); if (factorization.Factors is null) diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs index d628aef..26c3528 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Hybrid.cs @@ -9,7 +9,7 @@ internal static partial class PrimeOrderCalculator ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, - in PrimeOrderSearchConfig config) + in PrimeOrderCalculatorConfig config) { // TODO: Is this condition ever met on EvenPerfectBitScanner's execution path? If not, we can add a clarification comment and comment out the entire block. We want to support p candidates at least greater or equal to 31. if (prime <= 3UL) @@ -40,7 +40,7 @@ internal static partial class PrimeOrderCalculator PrimeOrderCalculatorAccelerator gpu, in UInt128 prime, in UInt128? previousOrder, - in PrimeOrderSearchConfig config) + in PrimeOrderCalculatorConfig config) { MontgomeryDivisorData divisorData; UInt128 result; diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs index b4e98e4..2c262fd 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs @@ -180,7 +180,7 @@ internal static partial class PrimeOrderCalculator } } - private static PartialFactorResult128 PartialFactorWide(in UInt128 value, in PrimeOrderSearchConfig config) + private static PartialFactorResult128 PartialFactorWide(in UInt128 value, in PrimeOrderCalculatorConfig config) { if (value <= UInt128.One) { diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index e8140ea..10ea057 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -4,39 +4,33 @@ namespace PerfectNumbers.Core; internal static partial class PrimeOrderCalculator { - internal enum PrimeOrderMode + internal readonly struct PrimeOrderCalculatorConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, bool strictMode) { - Heuristic, - Strict, - } + private static PrimeOrderCalculatorConfig _heuristicDefault = CreateHeuristicDefault(UnboundedTaskScheduler.ConfiguredThreadCount); - internal readonly struct PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode) - { - private static PrimeOrderSearchConfig _heuristicDefault = CreateHeuristicDefault(UnboundedTaskScheduler.ConfiguredThreadCount); - - public static PrimeOrderSearchConfig HeuristicDefault + public static PrimeOrderCalculatorConfig HeuristicDefault { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _heuristicDefault; } - public static readonly PrimeOrderSearchConfig StrictDefault = new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict); + public static readonly PrimeOrderCalculatorConfig StrictDefault = new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, true); public static void ConfigureHeuristicDefault(int threadCount) { _heuristicDefault = CreateHeuristicDefault(threadCount); } - public static PrimeOrderSearchConfig CreateHeuristicDefault(int threadCount) + public static PrimeOrderCalculatorConfig CreateHeuristicDefault(int threadCount) { int normalizedThreadCount = threadCount < 1 ? 1 : threadCount; - return new(smallFactorLimit: 100_000, pollardRhoMilliseconds: normalizedThreadCount * 24, maxPowChecks: 24, PrimeOrderMode.Heuristic); + return new(smallFactorLimit: 100_000, pollardRhoMilliseconds: normalizedThreadCount * 24, maxPowChecks: 24, false); } public readonly uint SmallFactorLimit = smallFactorLimit; public readonly int PollardRhoMilliseconds = pollardRhoMilliseconds; public readonly int MaxPowChecks = maxPowChecks; - public readonly PrimeOrderMode Mode = mode; + public readonly bool StrictMode = strictMode; } // TODO: Remove branching on the CPU / GPU paths diff --git a/PerfectNumbers.Core/UnboundedTaskScheduler.cs b/PerfectNumbers.Core/UnboundedTaskScheduler.cs index 3597d88..2d32739 100644 --- a/PerfectNumbers.Core/UnboundedTaskScheduler.cs +++ b/PerfectNumbers.Core/UnboundedTaskScheduler.cs @@ -19,7 +19,7 @@ public sealed class UnboundedTaskScheduler : TaskScheduler public static void ConfigureThreadCount(int threadCount) { _configuredThreadCount = threadCount; - PrimeOrderCalculator.PrimeOrderSearchConfig.ConfigureHeuristicDefault(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.