diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index c5866e8..77eb824 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -55,6 +55,7 @@ internal static class Program ulong remainder = currentP % 6UL; bool startPrimeProvided = _cliArguments.StartPrimeProvided; int threadCount = Math.Max(1, _cliArguments.ThreadCount); + UnboundedTaskScheduler.ConfigureThreadCount(threadCount); int blockSize = Math.Max(1, _cliArguments.BlockSize); int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads); int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch); diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs index 536eebd..5148dc0 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Cpu.cs @@ -10,10 +10,35 @@ namespace PerfectNumbers.Core; internal static partial class PrimeOrderCalculator { - internal readonly struct PendingEntry(ulong value, bool knownComposite) + internal readonly struct PendingEntry { - public readonly ulong Value = value; - public readonly bool KnownComposite = knownComposite; + public readonly ulong Value; + public readonly bool KnownComposite; + public readonly bool HasKnownPrimality; + public readonly bool IsPrime; + + public PendingEntry(ulong value, bool knownComposite) + { + Value = value; + KnownComposite = knownComposite; + HasKnownPrimality = knownComposite; + IsPrime = false; + } + + private PendingEntry(ulong value, bool knownComposite, bool hasKnownPrimality, bool isPrime) + { + Value = value; + KnownComposite = knownComposite; + HasKnownPrimality = hasKnownPrimality; + IsPrime = isPrime; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public PendingEntry WithPrimality(bool isPrime) + { + bool knownComposite = KnownComposite || !isPrime; + return new PendingEntry(Value, knownComposite, true, isPrime); + } } private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) @@ -720,6 +745,7 @@ internal static partial class PrimeOrderCalculator return isPrime; } + private static PartialFactorResult PartialFactor(ulong value, in PrimeOrderSearchConfig config) { if (value <= 1UL) @@ -756,7 +782,6 @@ internal static partial class PrimeOrderCalculator List pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2); Stack? compositeStack = null; PartialFactorResult result; - if (!gpuFactored) { // We don't need to worry about leftovers, because we always use indexes within the calculated counts @@ -829,6 +854,14 @@ internal static partial class PrimeOrderCalculator continue; } + long currentTimestamp = Stopwatch.GetTimestamp(); + if (currentTimestamp > deadlineTimestamp) + { + pollardRhoDeadlineReached = true; + pending.Add(new PendingEntry(composite, knownComposite: true)); + continue; + } + if (!TryPollardRho(composite, deadlineTimestamp, out ulong factor)) { pending.Add(new PendingEntry(composite, knownComposite: true)); @@ -840,7 +873,10 @@ internal static partial class PrimeOrderCalculator compositeStack.Push(quotient); } - pollardRhoDeadlineReached = Stopwatch.GetTimestamp() > deadlineTimestamp; + if (!pollardRhoDeadlineReached) + { + pollardRhoDeadlineReached = Stopwatch.GetTimestamp() > deadlineTimestamp; + } } if (pollardRhoDeadlineReached) @@ -868,9 +904,14 @@ internal static partial class PrimeOrderCalculator continue; } - bool isPrime = GetOrComputePrimality(composite); + if (!entry.HasKnownPrimality) + { + bool isPrime = GetOrComputePrimality(composite); + entry = entry.WithPrimality(isPrime); + pending[index] = entry; + } - if (isPrime) + if (entry.IsPrime) { AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); } @@ -968,6 +1009,7 @@ internal static partial class PrimeOrderCalculator // ThreadStaticPools.UlongPool.Return(primeSlotsArray); // ThreadStaticPools.IntPool.Return(exponentSlotsArray); + if (counts is not null) { ThreadStaticPools.ReturnUlongIntDictionary(counts); diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.cs b/PerfectNumbers.Core/PrimeOrderCalculator.cs index 34b4c1b..d3fd7e6 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.cs @@ -1,3 +1,5 @@ +using System.Runtime.CompilerServices; + namespace PerfectNumbers.Core; internal static partial class PrimeOrderCalculator @@ -10,9 +12,27 @@ internal static partial class PrimeOrderCalculator internal readonly struct PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode) { - public static readonly PrimeOrderSearchConfig HeuristicDefault = new(smallFactorLimit: 100_000, pollardRhoMilliseconds: 24, maxPowChecks: 24, PrimeOrderMode.Heuristic); + private static PrimeOrderSearchConfig _heuristicDefault = CreateHeuristicDefault(UnboundedTaskScheduler.ConfiguredThreadCount); + + public static PrimeOrderSearchConfig HeuristicDefault + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => _heuristicDefault; + } + public static readonly PrimeOrderSearchConfig StrictDefault = new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict); + public static void ConfigureHeuristicDefault(int threadCount) + { + _heuristicDefault = CreateHeuristicDefault(threadCount); + } + + public static PrimeOrderSearchConfig CreateHeuristicDefault(int threadCount) + { + int normalizedThreadCount = threadCount < 1 ? 1 : threadCount; + return new(smallFactorLimit: 100_000, pollardRhoMilliseconds: normalizedThreadCount * 24, maxPowChecks: 24, PrimeOrderMode.Heuristic); + } + public readonly uint SmallFactorLimit = smallFactorLimit; public readonly int PollardRhoMilliseconds = pollardRhoMilliseconds; public readonly int MaxPowChecks = maxPowChecks; diff --git a/PerfectNumbers.Core/UnboundedTaskScheduler.cs b/PerfectNumbers.Core/UnboundedTaskScheduler.cs index ec9209f..b552fa9 100644 --- a/PerfectNumbers.Core/UnboundedTaskScheduler.cs +++ b/PerfectNumbers.Core/UnboundedTaskScheduler.cs @@ -6,13 +6,31 @@ namespace PerfectNumbers.Core; public sealed class UnboundedTaskScheduler : TaskScheduler { - public static UnboundedTaskScheduler Instance { get; } = new(); + private static UnboundedTaskScheduler? _instance; + private static int _configuredThreadCount = Environment.ProcessorCount; - private readonly TaskThreadPool _threadPool; + public static UnboundedTaskScheduler Instance => _instance ??= new UnboundedTaskScheduler(_configuredThreadCount); - private UnboundedTaskScheduler() + public static int ConfiguredThreadCount => _configuredThreadCount; + + private TaskThreadPool _threadPool; + + private UnboundedTaskScheduler(int threadCount) { - _threadPool = new TaskThreadPool(10_340, ExecuteTask); + _threadPool = new TaskThreadPool(threadCount, ExecuteTask); + } + + public static void ConfigureThreadCount(int threadCount) + { + _configuredThreadCount = threadCount; + PrimeOrderCalculator.PrimeOrderSearchConfig.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); + // } } public override int MaximumConcurrencyLevel => int.MaxValue; @@ -22,6 +40,19 @@ public sealed class UnboundedTaskScheduler : TaskScheduler _threadPool.Queue(task); } + private void UpdateThreadPool(int threadCount) + { + if (_threadPool.ThreadCount == threadCount) + { + return; + } + + TaskThreadPool newPool = new(threadCount, ExecuteTask); + TaskThreadPool oldPool = _threadPool; + _threadPool = newPool; + oldPool.Dispose(); + } + private void ExecuteTask(Task task) { TryExecuteTask(task); @@ -36,4 +67,5 @@ public sealed class UnboundedTaskScheduler : TaskScheduler { return _threadPool.GetScheduledTasks(); } + }