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
+84 -89

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);
+23 -24
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,16 +10,10 @@ 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");
@@ -755,7 +752,7 @@ 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)
@@ -887,23 +884,25 @@ internal static partial class PrimeOrderCalculator
{ {
cofactorIsPrime = GetOrComputePrimality(cofactor); 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;
+3 -1
View File
@@ -10,7 +10,7 @@ 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: 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;
@@ -25,6 +25,8 @@ internal static partial class PrimeOrderCalculator
Gpu, Gpu,
} }
// TODO: Remove branching on the CPU / GPU paths
// TODO: Split big non-static functions into smaller / extract static code to limit JIT time
public static ulong Calculate( public static ulong Calculate(
ulong prime, ulong prime,
ulong? previousOrder, ulong? previousOrder,
+50 -48
View File
@@ -337,10 +337,12 @@ public static class ULongExtensions
// return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus; // return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
// } // }
if (exponent <= Pow2WindowFallbackThreshold) // There is no point in having extra branch, because in real use-cases it'll barely ever hit.
{ // Out of all 3_358_944_545 Pow2MontgomeryModWindowedCpu calls, we hit Pow2MontgomeryModSingleBit only 772 times.
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery); // if (exponent <= Pow2WindowFallbackThreshold)
} // {
// return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
// }
int bitLength = GetPortableBitLength(exponent); int bitLength = GetPortableBitLength(exponent);
int windowSize = GetWindowSize(bitLength); int windowSize = GetWindowSize(bitLength);
@@ -402,16 +404,18 @@ public static class ULongExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong Pow2MontgomeryModWindowedGpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery) public static ulong Pow2MontgomeryModWindowedGpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
{ {
ulong modulus = divisor.Modulus; // We shouldn't hit it in production code. If we do, we're doing something wrong.
if (exponent == 0UL) // ulong modulus = divisor.Modulus;
{ // if (exponent == 0UL)
return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus; // {
} // return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
// }
if (exponent <= Pow2WindowFallbackThreshold) // We barely ever hit it in production code. It's e.g. 772 calls out of billions
{ // if (exponent <= Pow2WindowFallbackThreshold)
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery); // {
} // return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
// }
if (Pow2MontgomeryGpuExecutor.TryExecute(exponent, divisor, keepMontgomery, out ulong gpuResult)) if (Pow2MontgomeryGpuExecutor.TryExecute(exponent, divisor, keepMontgomery, out ulong gpuResult))
{ {
@@ -425,15 +429,17 @@ public static class ULongExtensions
internal static ulong Pow2MontgomeryModWindowedKernel(ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery) internal static ulong Pow2MontgomeryModWindowedKernel(ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
{ {
ulong modulus = divisor.Modulus; ulong modulus = divisor.Modulus;
if (exponent == 0UL) // This should never happen in production code
{ // if (exponent == 0UL)
return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus; // {
} // return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
// }
if (exponent <= Pow2WindowFallbackThreshold) // We barely every hit it from production code. It's only 772 calls out of billions.
{ // if (exponent <= Pow2WindowFallbackThreshold)
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery); // {
} // return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
// }
int bitLength = GetPortableBitLength(exponent); int bitLength = GetPortableBitLength(exponent);
int windowSize = GetWindowSize(bitLength); int windowSize = GetWindowSize(bitLength);
@@ -517,24 +523,16 @@ public static class ULongExtensions
{ {
result = 0UL; result = 0UL;
GpuKernelLease lease; GpuKernelLease lease = GpuKernelPool.GetKernel(useGpuOrder: true);
try
{
lease = GpuKernelPool.GetKernel(useGpuOrder: true);
}
catch (Exception)
{
return false;
}
var execution = lease.EnterExecutionScope(); var execution = lease.EnterExecutionScope();
try try
{ {
Accelerator accelerator = lease.Accelerator; Accelerator accelerator = lease.Accelerator;
if (accelerator.AcceleratorType == AcceleratorType.CPU) // Keep this commented out. It should never happen in production code.
{ // if (accelerator.AcceleratorType == AcceleratorType.CPU)
return false; // {
} // return false;
// }
var kernel = KernelCache.GetOrAdd(accelerator, static accel => var kernel = KernelCache.GetOrAdd(accelerator, static accel =>
{ {
@@ -547,8 +545,10 @@ public static class ULongExtensions
using var resultBuffer = accelerator.Allocate1D<ulong>(1); using var resultBuffer = accelerator.Allocate1D<ulong>(1);
exponentBuffer.View.CopyFromCPU(ref exponent, 1); exponentBuffer.View.CopyFromCPU(ref exponent, 1);
resultBuffer.MemSetToZero(); // We don't need to worry about any left-overs here.
// resultBuffer.MemSetToZero();
// TODO: Have 2 kernels for both cases to have branch-less solution.
byte keepFlag = keepMontgomery ? (byte)1 : (byte)0; byte keepFlag = keepMontgomery ? (byte)1 : (byte)0;
AcceleratorStream stream = lease.Stream; AcceleratorStream stream = lease.Stream;
kernel(stream, 1, exponentBuffer.View, divisor, keepFlag, resultBuffer.View); kernel(stream, 1, exponentBuffer.View, divisor, keepFlag, resultBuffer.View);
@@ -557,14 +557,15 @@ public static class ULongExtensions
resultBuffer.View.CopyToCPU(ref result, 1); resultBuffer.View.CopyToCPU(ref result, 1);
return true; return true;
} }
catch (AcceleratorException) // Keep this commented. We don't want to catch any exceptions. All should crash the scanner.
{ // catch (AcceleratorException)
return false; // {
} // return false;
catch (NotSupportedException) // }
{ // catch (NotSupportedException)
return false; // {
} // return false;
// }
finally finally
{ {
execution.Dispose(); execution.Dispose();
@@ -609,10 +610,11 @@ public static class ULongExtensions
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int GetPortableBitLength(ulong value) private static int GetPortableBitLength(ulong value)
{ {
if (value == 0UL) // Keep this commented out. It will never happen in production code.
{ // if (value == 0UL)
return 0; // {
} // return 0;
// }
return 64 - XMath.LeadingZeroCount(value); return 64 - XMath.LeadingZeroCount(value);
} }
+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;
}
} }