Merge pull request #210 from mlemanczyk/codex/optimize-partialfactor-method-for-isprime-caching

Cache composite primality results during partial factorization
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-10-20 01:07:24 +02:00
commit bfc6605e97
4 files changed
+105 -10

No files matched your search

+1
View File
@@ -55,6 +55,7 @@ internal static class Program
ulong remainder = currentP % 6UL; ulong remainder = currentP % 6UL;
bool startPrimeProvided = _cliArguments.StartPrimeProvided; bool startPrimeProvided = _cliArguments.StartPrimeProvided;
int threadCount = Math.Max(1, _cliArguments.ThreadCount); int threadCount = Math.Max(1, _cliArguments.ThreadCount);
UnboundedTaskScheduler.ConfigureThreadCount(threadCount);
int blockSize = Math.Max(1, _cliArguments.BlockSize); int blockSize = Math.Max(1, _cliArguments.BlockSize);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads); int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch); int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
@@ -10,10 +10,35 @@ namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator internal static partial class PrimeOrderCalculator
{ {
internal readonly struct PendingEntry(ulong value, bool knownComposite) internal readonly struct PendingEntry
{ {
public readonly ulong Value = value; public readonly ulong Value;
public readonly bool KnownComposite = knownComposite; 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) 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; return isPrime;
} }
private static PartialFactorResult PartialFactor(ulong value, in PrimeOrderSearchConfig config) private static PartialFactorResult PartialFactor(ulong value, in PrimeOrderSearchConfig config)
{ {
if (value <= 1UL) if (value <= 1UL)
@@ -756,7 +782,6 @@ internal static partial class PrimeOrderCalculator
List<PendingEntry> pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2); List<PendingEntry> pending = ThreadStaticPools.RentPrimeOrderPendingEntryList(2);
Stack<ulong>? compositeStack = null; Stack<ulong>? compositeStack = null;
PartialFactorResult result; PartialFactorResult result;
if (!gpuFactored) if (!gpuFactored)
{ {
// We don't need to worry about leftovers, because we always use indexes within the calculated counts // 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; 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)) if (!TryPollardRho(composite, deadlineTimestamp, out ulong factor))
{ {
pending.Add(new PendingEntry(composite, knownComposite: true)); pending.Add(new PendingEntry(composite, knownComposite: true));
@@ -840,8 +873,11 @@ internal static partial class PrimeOrderCalculator
compositeStack.Push(quotient); compositeStack.Push(quotient);
} }
if (!pollardRhoDeadlineReached)
{
pollardRhoDeadlineReached = Stopwatch.GetTimestamp() > deadlineTimestamp; pollardRhoDeadlineReached = Stopwatch.GetTimestamp() > deadlineTimestamp;
} }
}
if (pollardRhoDeadlineReached) if (pollardRhoDeadlineReached)
{ {
@@ -868,9 +904,14 @@ internal static partial class PrimeOrderCalculator
continue; continue;
} }
if (!entry.HasKnownPrimality)
{
bool isPrime = GetOrComputePrimality(composite); 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); 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.UlongPool.Return(primeSlotsArray);
// ThreadStaticPools.IntPool.Return(exponentSlotsArray); // ThreadStaticPools.IntPool.Return(exponentSlotsArray);
if (counts is not null) if (counts is not null)
{ {
ThreadStaticPools.ReturnUlongIntDictionary(counts); ThreadStaticPools.ReturnUlongIntDictionary(counts);
+21 -1
View File
@@ -1,3 +1,5 @@
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator 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) 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 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 uint SmallFactorLimit = smallFactorLimit;
public readonly int PollardRhoMilliseconds = pollardRhoMilliseconds; public readonly int PollardRhoMilliseconds = pollardRhoMilliseconds;
public readonly int MaxPowChecks = maxPowChecks; public readonly int MaxPowChecks = maxPowChecks;
+36 -4
View File
@@ -6,13 +6,31 @@ namespace PerfectNumbers.Core;
public sealed class UnboundedTaskScheduler : TaskScheduler 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; public override int MaximumConcurrencyLevel => int.MaxValue;
@@ -22,6 +40,19 @@ public sealed class UnboundedTaskScheduler : TaskScheduler
_threadPool.Queue(task); _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) private void ExecuteTask(Task task)
{ {
TryExecuteTask(task); TryExecuteTask(task);
@@ -36,4 +67,5 @@ public sealed class UnboundedTaskScheduler : TaskScheduler
{ {
return _threadPool.GetScheduledTasks(); return _threadPool.GetScheduledTasks();
} }
} }