Small optimizations. Branches which we don't visit in runtime disabled. TODOs added.

This commit is contained in:
mlemanczyk committed 2025-10-18 14:20:46 +02:00
1 parent 7a22fbbba7
commit 5fbd5bc20c
5 files changed
+1171 -1176

No files matched your search

@@ -257,10 +257,11 @@ public static class MersenneNumberDivisorByDivisorTester
} }
else else
{ {
var scheduler = UnboundedTaskScheduler.Instance;
ParallelOptions options = new() ParallelOptions options = new()
{ {
MaxDegreeOfParallelism = workerCount, MaxDegreeOfParallelism = workerCount,
TaskScheduler = UnboundedTaskScheduler.Instance TaskScheduler = scheduler
}; };
Parallel.ForEach(filteredPrimes, options, ProcessPrime); Parallel.ForEach(filteredPrimes, options, ProcessPrime);
+120 -121
View File
@@ -1,10 +1,8 @@
using System.Buffers; using System.Buffers;
using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
@@ -12,17 +10,11 @@ namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator internal static partial class PrimeOrderCalculator
{ {
internal readonly struct PendingEntry internal readonly struct PendingEntry(ulong value, bool knownComposite)
{ {
public readonly ulong Value; public readonly ulong Value = value;
public readonly bool KnownComposite; public readonly bool KnownComposite = knownComposite;
}
public PendingEntry(ulong value, bool knownComposite)
{
Value = value;
KnownComposite = knownComposite;
}
}
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)
{ {
@@ -70,8 +62,7 @@ internal static partial class PrimeOrderCalculator
ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData); ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData);
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData); candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData);
PartialFactorResult? orderFactors; if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, config, out PartialFactorResult? orderFactors))
if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, config, out orderFactors))
{ {
return candidateOrder; return candidateOrder;
} }
@@ -104,6 +95,10 @@ internal static partial class PrimeOrderCalculator
// continue; // continue;
// } // }
// TODO: Can we optimize it thanks to knowing the factors, differences between factors, factors and / or phi
// being monotonically increasing etc. For example, if phi increases and factor increases, what does it tell us
// about the value of reduced? Or, maybe wee can use residue stepping to calculate the residue instead of calling
// Pow2equalsOneCpu? Maybe we don't need to calculate it from scratch?
ulong reduced = phi / factor; ulong reduced = phi / factor;
if (Pow2EqualsOneCpu(reduced, prime, divisorData)) if (Pow2EqualsOneCpu(reduced, prime, divisorData))
{ {
@@ -237,8 +232,10 @@ internal static partial class PrimeOrderCalculator
{ {
ulong primeFactor = span[i].Value; ulong primeFactor = span[i].Value;
ulong reduced = order; ulong reduced = order;
for (int iteration = 0; iteration < span[i].Exponent; iteration++) int exponent = span[i].Exponent;
for (int iteration = 0; iteration < exponent; iteration++)
{ {
// TODO: Benchmark and / or implement DivRem like solution or residue stepper?
if ((reduced % primeFactor) != 0UL) if ((reduced % primeFactor) != 0UL)
{ {
break; break;
@@ -299,7 +296,7 @@ internal static partial class PrimeOrderCalculator
orderFactors = extended; orderFactors = extended;
} }
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4; int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks << 2;
List<ulong> candidates = ThreadStaticPools.RentUlongList(capacity); List<ulong> candidates = ThreadStaticPools.RentUlongList(capacity);
FactorEntry[] factorArray = orderFactors.Factors!; FactorEntry[] factorArray = orderFactors.Factors!;
// DebugLog("Building candidates list"); // DebugLog("Building candidates list");
@@ -669,7 +666,7 @@ internal static partial class PrimeOrderCalculator
// GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData); // GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData);
// if (status == GpuPow2ModStatus.Success) // if (status == GpuPow2ModStatus.Success)
// { // {
return remainder == 1UL; return remainder == 1UL;
// } // }
} }
@@ -755,14 +752,14 @@ internal static partial class PrimeOrderCalculator
// exponentSlots.Clear(); // exponentSlots.Clear();
counts = ThreadStaticPools.RentUlongIntDictionary(Math.Max(FactorSlotCount, 8)); counts = ThreadStaticPools.RentUlongIntDictionary(Math.Max(FactorSlotCount, 8));
counts.Clear(); // counts.Clear();
// bool gpuPopulated = TryPopulateSmallPrimeFactorsGpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining); // bool gpuPopulated = TryPopulateSmallPrimeFactorsGpu(value, limit, primeSlots, exponentSlots, out factorCount, out remaining);
// if (!gpuPopulated) // if (!gpuPopulated)
// { // {
// throw new InvalidOperationException($"GPU didn't populate factors for {value}"); // throw new InvalidOperationException($"GPU didn't populate factors for {value}");
// counts.Clear(); // counts.Clear();
remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts); remaining = PopulateSmallPrimeFactorsCpu(value, limit, counts);
// } // }
int dictionaryCount = counts.Count; int dictionaryCount = counts.Count;
@@ -844,66 +841,68 @@ internal static partial class PrimeOrderCalculator
} }
ulong cofactor = 1UL; ulong cofactor = 1UL;
bool cofactorContainsComposite = false; bool cofactorContainsComposite = false;
int pendingCount = pending.Count; int pendingCount = pending.Count;
int index = 0; int index = 0;
for (; index < pendingCount; index++) for (; index < pendingCount; index++)
{ {
PendingEntry entry = pending[index]; PendingEntry entry = pending[index];
ulong composite = entry.Value; ulong composite = entry.Value;
if (entry.KnownComposite) if (entry.KnownComposite)
{ {
cofactor = checked(cofactor * composite); cofactor = checked(cofactor * composite);
cofactorContainsComposite = true; cofactorContainsComposite = true;
continue; continue;
} }
bool isPrime = GetOrComputePrimality(composite); bool isPrime = GetOrComputePrimality(composite);
if (isPrime) if (isPrime)
{ {
AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1); AddFactorToCollector(ref useDictionary, ref counts, primeSlots, exponentSlots, ref factorCount, composite, 1);
} }
// composite is never smaller on the execution path // composite is never smaller on the execution path
// else if (composite > 1UL) // else if (composite > 1UL)
else else
{ {
cofactor = checked(cofactor * composite); cofactor = checked(cofactor * composite);
cofactorContainsComposite = true; cofactorContainsComposite = true;
} }
} }
bool cofactorIsPrime;
if (cofactor <= 1UL)
{
cofactorIsPrime = false;
}
else if (cofactorContainsComposite)
{
cofactorIsPrime = false;
}
else
{
cofactorIsPrime = GetOrComputePrimality(cofactor);
}
bool cofactorIsPrime;
if (cofactor <= 1UL)
{
cofactorIsPrime = false;
}
else if (cofactorContainsComposite)
{
cofactorIsPrime = false;
}
else
{
cofactorIsPrime = GetOrComputePrimality(cofactor);
}
ArrayPool<FactorEntry> pool = ThreadStaticPools.FactorEntryPool; ArrayPool<FactorEntry> pool = ThreadStaticPools.FactorEntryPool;
bool temp; bool temp;
if (useDictionary) if (useDictionary)
{ {
if ((counts is null || counts.Count == 0) && cofactor == value) temp = counts is null || counts.Count == 0;
if (temp && cofactor == value)
{ {
result = PartialFactorResult.Rent(null, cofactor, false, 0, cofactorIsPrime); result = PartialFactorResult.Rent(null, cofactor, false, 0, cofactorIsPrime);
goto ReturnResult; goto ReturnResult;
} }
if (counts is null || counts.Count == 0) if (temp)
{ {
result = PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0, cofactorIsPrime); result = PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0, cofactorIsPrime);
goto ReturnResult; goto ReturnResult;
} }
FactorEntry[] factors = pool.Rent(counts.Count); FactorEntry[] factors = pool.Rent(counts!.Count);
index = 0; index = 0;
foreach (KeyValuePair<ulong, int> entry in counts) foreach (KeyValuePair<ulong, int> entry in counts)
{ {
@@ -1137,8 +1136,7 @@ internal static partial class PrimeOrderCalculator
break; break;
} }
ulong primeValue = primeCandidate; if ((remaining % primeCandidate) != 0UL)
if ((remaining % primeValue) != 0UL)
{ {
continue; continue;
} }
@@ -1146,12 +1144,13 @@ internal static partial class PrimeOrderCalculator
int exponent = 0; int exponent = 0;
do do
{ {
remaining /= primeValue; // TODO: Implement DivRem like solution or residue stepper to re-use previous iteration results in new calculation. Add / use method, likely to ULongExtensions
remaining /= primeCandidate;
exponent++; exponent++;
} }
while ((remaining % primeValue) == 0UL); while ((remaining % primeCandidate) == 0UL);
counts[primeValue] = exponent; counts[primeCandidate] = exponent;
} }
return remaining; return remaining;
@@ -1398,70 +1397,70 @@ internal static partial class PrimeOrderCalculator
} }
private static void FactorCompletely(ulong value, Dictionary<ulong, int> counts) private static void FactorCompletely(ulong value, Dictionary<ulong, int> counts)
{ {
FactorCompletely(value, counts, knownComposite: false); FactorCompletely(value, counts, knownComposite: false);
} }
private static void FactorCompletely(ulong value, Dictionary<ulong, int> counts, bool knownComposite) private static void FactorCompletely(ulong value, Dictionary<ulong, int> counts, bool knownComposite)
{ {
if (value <= 1UL) if (value <= 1UL)
{ {
return; return;
} }
if (!knownComposite && GetOrComputePrimality(value)) if (!knownComposite && GetOrComputePrimality(value))
{ {
AddFactor(counts, value, 1); AddFactor(counts, value, 1);
return; return;
} }
ulong factor = PollardRhoStrict(value); ulong factor = PollardRhoStrict(value);
ulong quotient = value / factor; ulong quotient = value / factor;
bool factorIsPrime = GetOrComputePrimality(factor); bool factorIsPrime = GetOrComputePrimality(factor);
if (factorIsPrime) if (factorIsPrime)
{ {
int exponent = 1; int exponent = 1;
ulong remaining = quotient; ulong remaining = quotient;
while ((remaining % factor) == 0UL) while ((remaining % factor) == 0UL)
{ {
remaining /= factor; remaining /= factor;
exponent++; exponent++;
} }
AddFactor(counts, factor, exponent); AddFactor(counts, factor, exponent);
if (remaining > 1UL) if (remaining > 1UL)
{ {
FactorCompletely(remaining, counts, knownComposite: false); FactorCompletely(remaining, counts, knownComposite: false);
} }
return; return;
} }
FactorCompletely(factor, counts, knownComposite: true); FactorCompletely(factor, counts, knownComposite: true);
bool quotientIsPrime; bool quotientIsPrime;
if (quotient == factor) if (quotient == factor)
{ {
quotientIsPrime = false; quotientIsPrime = false;
} }
else else
{ {
quotientIsPrime = GetOrComputePrimality(quotient); quotientIsPrime = GetOrComputePrimality(quotient);
} }
if (quotientIsPrime) if (quotientIsPrime)
{ {
AddFactor(counts, quotient, 1); AddFactor(counts, quotient, 1);
} }
else else
{ {
FactorCompletely(quotient, counts, knownComposite: true); FactorCompletely(quotient, counts, knownComposite: true);
} }
} }
private static ulong PollardRhoStrict(ulong n) private static ulong PollardRhoStrict(ulong n)
{ {
if ((n & 1UL) == 0UL) if ((n & 1UL) == 0UL)
{ {
+70 -68
View File
@@ -2,79 +2,81 @@ namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator internal static partial class PrimeOrderCalculator
{ {
internal enum PrimeOrderMode internal enum PrimeOrderMode
{ {
Heuristic, Heuristic,
Strict, Strict,
} }
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: 4_000_000, pollardRhoMilliseconds: 24, maxPowChecks: 24, PrimeOrderMode.Heuristic); public static readonly PrimeOrderSearchConfig HeuristicDefault = new(smallFactorLimit: 4_000_000, pollardRhoMilliseconds: 24 * 40960, maxPowChecks: 24, PrimeOrderMode.Heuristic);
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 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;
public readonly PrimeOrderMode Mode = mode; public readonly PrimeOrderMode Mode = mode;
} }
internal enum PrimeOrderHeuristicDevice internal enum PrimeOrderHeuristicDevice
{ {
Cpu, Cpu,
Gpu, Gpu,
} }
public static ulong Calculate( // TODO: Remove branching on the CPU / GPU paths
ulong prime, // TODO: Split big non-static functions into smaller / extract static code to limit JIT time
ulong? previousOrder, public static ulong Calculate(
in MontgomeryDivisorData divisorData, ulong prime,
in PrimeOrderSearchConfig config, ulong? previousOrder,
PrimeOrderHeuristicDevice device) in MontgomeryDivisorData divisorData,
{ in PrimeOrderSearchConfig config,
var scope = UsePow2Mode(device); PrimeOrderHeuristicDevice device)
ulong order = CalculateInternal(prime, previousOrder, divisorData, config); {
scope.Dispose(); var scope = UsePow2Mode(device);
return order; ulong order = CalculateInternal(prime, previousOrder, divisorData, config);
} scope.Dispose();
return order;
}
public static UInt128 Calculate( public static UInt128 Calculate(
in UInt128 prime, in UInt128 prime,
in UInt128? previousOrder, in UInt128? previousOrder,
in PrimeOrderSearchConfig config, in PrimeOrderSearchConfig config,
in PrimeOrderHeuristicDevice device) in PrimeOrderHeuristicDevice device)
{ {
var scope = UsePow2Mode(device); var scope = UsePow2Mode(device);
MontgomeryDivisorData divisorData; MontgomeryDivisorData divisorData;
UInt128 result; UInt128 result;
if (prime <= ulong.MaxValue) if (prime <= ulong.MaxValue)
{ {
ulong? previous = null; ulong? previous = null;
if (previousOrder.HasValue) if (previousOrder.HasValue)
{ {
UInt128 previousValue = previousOrder.Value; UInt128 previousValue = previousOrder.Value;
if (previousValue <= ulong.MaxValue) if (previousValue <= ulong.MaxValue)
{ {
previous = (ulong)previousValue; previous = (ulong)previousValue;
} }
else else
{ {
previous = ulong.MaxValue; previous = ulong.MaxValue;
} }
} }
ulong prime64 = (ulong)prime; ulong prime64 = (ulong)prime;
divisorData = MontgomeryDivisorData.FromModulus(prime64); divisorData = MontgomeryDivisorData.FromModulus(prime64);
ulong order64 = Calculate(prime64, previous, divisorData, config, device); ulong order64 = Calculate(prime64, previous, divisorData, config, device);
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64; result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
} }
else else
{ {
divisorData = default; divisorData = default;
result = CalculateWideInternal(prime, previousOrder, divisorData, config); result = CalculateWideInternal(prime, previousOrder, divisorData, config);
} }
scope.Dispose(); scope.Dispose();
return result; return result;
} }
} }
File diff suppressed because it is too large. Load diff
+6 -15
View File
@@ -1,6 +1,3 @@
using System.Threading;
using System.Threading.Tasks;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
public sealed class UnboundedTaskScheduler : TaskScheduler public sealed class UnboundedTaskScheduler : TaskScheduler
@@ -10,27 +7,21 @@ public sealed class UnboundedTaskScheduler : TaskScheduler
private UnboundedTaskScheduler() private UnboundedTaskScheduler()
{ {
ThreadPool.SetMaxThreads(100_000, 100_000); ThreadPool.SetMaxThreads(100_000, 100_000);
ThreadPool.SetMinThreads(10_300, 100); ThreadPool.SetMinThreads(10_340, 100);
} }
public override int MaximumConcurrencyLevel => int.MaxValue; public override int MaximumConcurrencyLevel => int.MaxValue;
protected override void QueueTask(Task task) protected override void QueueTask(Task task)
{ {
ThreadPool.UnsafeQueueUserWorkItem(static state => ThreadPool.UnsafeQueueUserWorkItem( (state) =>
{ {
(UnboundedTaskScheduler Scheduler, Task Task) tuple = ((UnboundedTaskScheduler, Task))state!; var (scheduler, task) = ((UnboundedTaskScheduler, Task))state!;
tuple.Scheduler.TryExecuteTask(tuple.Task); scheduler.TryExecuteTask(task);
}, (this, task)); }, (this, task));
} }
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) => TryExecuteTask(task);
{
return TryExecuteTask(task);
}
protected override IEnumerable<Task>? GetScheduledTasks() protected override IEnumerable<Task>? GetScheduledTasks() => null;
{
return null;
}
} }