Small optimizations. Branches which we don't visit in runtime disabled. TODOs added.
This commit is contained in:
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);
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user