Compare commits
38
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0433924770 | ||
|
|
80d81e9d2c | ||
|
|
ace5cacff9 | ||
|
|
615970dff1 | ||
|
|
64c08f9b49 | ||
|
|
79d821c475 | ||
|
|
adac9ff5f9 | ||
|
|
4ab70ec4a8 | ||
|
|
9a81f5f685 | ||
|
|
686d482417 | ||
|
|
85a98eb009 | ||
|
|
5a66abe672 | ||
|
|
fbe9a98b42 | ||
|
|
4afe0e356e | ||
|
|
c45b3951c3 | ||
|
|
632957e4f4 | ||
|
|
6dc26336c6 | ||
|
|
6a4ee0e2c5 | ||
|
|
85634da6eb | ||
|
|
26abc2dfa8 | ||
|
|
6918bd28ff | ||
|
|
cd1debe9d6 | ||
|
|
2eb622030e | ||
|
|
762b9bd9f3 | ||
|
|
b27c8bffa7 | ||
|
|
c87f88fd88 | ||
|
|
2dd5a679e2 | ||
|
|
e0a5eea62d | ||
|
|
48127845b5 | ||
|
|
31e95e5a8b | ||
|
|
16181903a1 | ||
|
|
bd8686b86b | ||
|
|
894a3d8af2 | ||
|
|
ac846df7d9 | ||
|
|
581da24b2f | ||
|
|
0543306f31 | ||
|
|
20120f5bc0 | ||
|
|
ce1355c749 |
No files matched your search
Vendored
+16
-3
@@ -20,9 +20,22 @@
|
||||
"request": "launch",
|
||||
"preLaunchTask": "Build Debug",
|
||||
"program": "${workspaceFolder}/${input:projectName}/bin/Debug/net8.0/${input:projectName}.dll",
|
||||
"args": [
|
||||
"--increment=add --mersenne=residue --threads=128 --gpu-scan-batch=512000 --primes-device=gpu --mersenne-device=gpu --mod-reduction=barrett128 --block-size=10000000 --gpu-prime-threads=32 --write-batch-size=10000 --divisor-cycles-device=gpu"
|
||||
],
|
||||
"args": [
|
||||
"--increment=add",
|
||||
"--mersenne=bydivisor",
|
||||
"--threads=1",
|
||||
"--gpu-scan-batch=256000",
|
||||
"--primes-device=gpu",
|
||||
"--mersenne-device=gpu",
|
||||
"--mod-reduction=barrett128",
|
||||
"--block-size=1000000",
|
||||
"--gpu-prime-threads=64",
|
||||
"--write-batch-size=10000",
|
||||
"--divisor-cycles-device=gpu",
|
||||
"--prime=31",
|
||||
"--results-prefix=test-",
|
||||
"--filter-p=sorted-primes-passed-residue-_even_perfect_bit_scan_inc-add_thr-16_blk-10000000_mers-incremental_mersdev-gpu_ntt-reference_red-barrett128_primesdev-gpu_order-off_orderdev-gpu_gcd-off_modfix-off_gputh-64_llslice-32_scanb-512000_warm-5000000.csv"
|
||||
],
|
||||
"cwd": "${workspaceFolder}",
|
||||
"stopAtEntry": false,
|
||||
"console": "internalConsole"
|
||||
|
||||
Vendored
+2
@@ -3,8 +3,10 @@
|
||||
"chatgpt.openOnStartup": true,
|
||||
"cSpell.words": [
|
||||
"CCCCCCCDUL",
|
||||
"cofactor",
|
||||
"ILGPU",
|
||||
"mers",
|
||||
"Mersenne",
|
||||
"primality",
|
||||
"suff"
|
||||
]
|
||||
|
||||
+3124
File diff suppressed because it is too large.
Load diff
@@ -39,7 +39,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus);
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLength(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
_largeDivisors[i] = CreateMontgomeryDivisorData(largeModulus);
|
||||
|
||||
@@ -261,9 +261,10 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
||||
|
||||
private ulong CalculateCycleLengthWithHeuristics(ulong modulus, ulong fallbackOrder = 0UL)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
// When the below is uncommented, the benchmark never completes due to the primality test cost on very large random set.
|
||||
@@ -281,6 +282,7 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
||||
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
||||
modulus,
|
||||
_previousPrimeOrder,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
if (orderResult.Order != 0UL)
|
||||
@@ -292,7 +294,7 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
||||
_previousPrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
private UInt128 CalculateCycleLengthWithHeuristics(UInt128 modulus)
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -1,5 +1,6 @@
|
||||
using BenchmarkDotNet.Configs;
|
||||
using BenchmarkDotNet.Running;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace EvenPerfectBitScanner.Benchmarks;
|
||||
|
||||
@@ -7,15 +8,16 @@ public static class Program
|
||||
{
|
||||
public static void Main(string[] args)
|
||||
{
|
||||
var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary);
|
||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||
// var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
||||
// BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||
|
||||
// Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
||||
// {
|
||||
// Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large
|
||||
// };
|
||||
Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
||||
{
|
||||
Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large
|
||||
};
|
||||
|
||||
// benchmarks.Setup();
|
||||
GpuPrimeWorkLimiter.SetLimit(1);
|
||||
benchmarks.Setup();
|
||||
|
||||
// Console.WriteLine("Press ENTER to run");
|
||||
// Console.ReadLine();
|
||||
|
||||
@@ -458,7 +458,6 @@ internal static class Program
|
||||
|
||||
if (useByDivisor)
|
||||
{
|
||||
threadCount = 1;
|
||||
blockSize = 1;
|
||||
}
|
||||
|
||||
@@ -499,6 +498,7 @@ internal static class Program
|
||||
maxK: residueKMax);
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders");
|
||||
tester.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
|
||||
@@ -673,12 +673,11 @@ internal static class Program
|
||||
|
||||
if (!useDivisor && !useByDivisor)
|
||||
{
|
||||
Console.WriteLine("Warming up orders...");
|
||||
|
||||
threadCount = Math.Max(1, threadCount);
|
||||
_ = MersenneTesters.Value;
|
||||
if (!useLucas)
|
||||
{
|
||||
Console.WriteLine("Warming up orders...");
|
||||
MersenneTesters.Value!.WarmUpOrders(currentP, _orderWarmupLimitOverride ?? 5_000_000UL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,418 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
using PerfectNumbers.Core;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests.Gpu;
|
||||
|
||||
[Collection("GpuNtt")]
|
||||
public class GpuKernelCompilationTests
|
||||
{
|
||||
private static readonly MethodInfo DivisorCycleCacheLoadKernelMethod =
|
||||
typeof(DivisorCycleCache).GetMethod("LoadKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("DivisorCycleCache.LoadKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetBitReverseKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetBitReverseKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetBitReverseKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetMulKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetMulKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetMulKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetStageKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetStageKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetStageKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetScaleKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetScaleKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetScaleKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetStageMontKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetStageMontKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetStageMontKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetStageBarrettKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetStageBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetStageBarrett128Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetScaleBarrettKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetScaleBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetScaleBarrett128Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetSquareBarrettKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetSquareBarrett128Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetSquareBarrett128Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetToMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetToMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetToMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetFromMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetFromMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetFromMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetSquareMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetSquareMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetSquareMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetScaleMont64KernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetScaleMont64Kernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetScaleMont64Kernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetForwardKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetForwardKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetForwardKernel not found.");
|
||||
|
||||
private static readonly MethodInfo NttGetInverseKernelMethod =
|
||||
typeof(NttGpuMath).GetMethod("GetInverseKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("NttGpuMath.GetInverseKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetExponentKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetKernelByPrimeExponent", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetKernelByPrimeExponent not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetRemainderDeltaKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetRemainderDeltaKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetRemainderDeltaKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorByDivisorGetRemainderScanKernelMethod =
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester).GetMethod("GetRemainderScanKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorByDivisorGpuTester.GetRemainderScanKernel not found.");
|
||||
|
||||
private static readonly MethodInfo DivisorTesterGetKernelMethod =
|
||||
typeof(MersenneNumberDivisorGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberDivisorGpuTester.GetKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetAddSmallKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetAddSmallKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetAddSmallKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetSubSmallKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetSubSmallKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetSubSmallKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetReduceKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetReduceKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetReduceKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetIsZeroKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetIsZeroKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetIsZeroKernel not found.");
|
||||
|
||||
private static readonly MethodInfo LucasLehmerGetBatchKernelMethod =
|
||||
typeof(MersenneNumberLucasLehmerGpuTester).GetMethod("GetBatchKernel", BindingFlags.NonPublic | BindingFlags.Instance)
|
||||
?? throw new InvalidOperationException("MersenneNumberLucasLehmerGpuTester.GetBatchKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetPartialFactorKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPartialFactorKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPartialFactorKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetOrderKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetOrderKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetOrderKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetPow2ModKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
|
||||
|
||||
private static readonly MethodInfo PrimeOrderGetPow2ModWideKernelMethod =
|
||||
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModWideKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModWideKernel not found.");
|
||||
|
||||
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
|
||||
(Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>)Delegate.CreateDelegate(
|
||||
typeof(Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>),
|
||||
typeof(MersenneDivisorCycles).GetMethod("GpuDivisorCycleKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("MersenneDivisorCycles.GpuDivisorCycleKernel not found."));
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
||||
(Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>)Delegate.CreateDelegate(
|
||||
typeof(Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>),
|
||||
typeof(PrimeTester).GetMethod("SmallPrimeSieveKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeTester.SmallPrimeSieveKernel not found."));
|
||||
|
||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
||||
(Action<Index1D, ArrayView<ulong>, ArrayView<byte>>)Delegate.CreateDelegate(
|
||||
typeof(Action<Index1D, ArrayView<ulong>, ArrayView<byte>>),
|
||||
typeof(PrimeTester).GetMethod("SharesFactorKernel", BindingFlags.NonPublic | BindingFlags.Static)
|
||||
?? throw new InvalidOperationException("PrimeTester.SharesFactorKernel not found."));
|
||||
|
||||
public static IEnumerable<object[]> KernelLoaders()
|
||||
{
|
||||
yield return Loader("DivisorCycleCache.GpuAdvanceDivisorCyclesKernel", CompileDivisorCycleCacheKernel);
|
||||
yield return Loader("NttGpuMath.BitReverseKernel", CompileNttBitReverseKernel);
|
||||
yield return Loader("NttGpuMath.MulKernel", CompileNttMulKernel);
|
||||
yield return Loader("NttGpuMath.StageKernel", CompileNttStageKernel);
|
||||
yield return Loader("NttGpuMath.ScaleKernel", CompileNttScaleKernel);
|
||||
yield return Loader("NttGpuMath.StageMontKernel", CompileNttStageMontKernel);
|
||||
yield return Loader("NttGpuMath.StageBarrett128Kernel", CompileNttStageBarrettKernel);
|
||||
yield return Loader("NttGpuMath.ScaleBarrett128Kernel", CompileNttScaleBarrettKernel);
|
||||
yield return Loader("NttGpuMath.SquareBarrett128Kernel", CompileNttSquareBarrettKernel);
|
||||
yield return Loader("NttGpuMath.ToMont64Kernel", CompileNttToMont64Kernel);
|
||||
yield return Loader("NttGpuMath.FromMont64Kernel", CompileNttFromMont64Kernel);
|
||||
yield return Loader("NttGpuMath.SquareMont64Kernel", CompileNttSquareMont64Kernel);
|
||||
yield return Loader("NttGpuMath.ScaleMont64Kernel", CompileNttScaleMont64Kernel);
|
||||
yield return Loader("NttGpuMath.ForwardKernel", CompileNttForwardKernel);
|
||||
yield return Loader("NttGpuMath.InverseKernel", CompileNttInverseKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.CheckKernel", CompileDivisorByDivisorCheckKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.ComputeMontgomeryExponentKernel", CompileDivisorByDivisorExponentKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.ComputeRemainderDeltasKernel", CompileDivisorByDivisorRemainderDeltaKernel);
|
||||
yield return Loader("MersenneNumberDivisorByDivisorGpuTester.AccumulateRemaindersKernel", CompileDivisorByDivisorRemainderScanKernel);
|
||||
yield return Loader("MersenneNumberDivisorGpuTester.Kernel", CompileDivisorTesterKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.Kernel", CompileLucasLehmerKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.AddSmallKernel", CompileLucasLehmerAddSmallKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.SubtractSmallKernel", CompileLucasLehmerSubSmallKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.ReduceModMersenneKernel", CompileLucasLehmerReduceKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.IsZeroKernel", CompileLucasLehmerIsZeroKernel);
|
||||
yield return Loader("MersenneNumberLucasLehmerGpuTester.KernelBatch", CompileLucasLehmerBatchKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.PartialFactorKernel", CompilePrimeOrderPartialFactorKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.CalculateOrderKernel", CompilePrimeOrderKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.Pow2ModKernel", CompilePrimeOrderPow2ModKernel);
|
||||
yield return Loader("PrimeOrderGpuHeuristics.Pow2ModKernelWide", CompilePrimeOrderPow2ModWideKernel);
|
||||
yield return Loader("MersenneDivisorCycles.GpuDivisorCycleKernel", CompileMersenneDivisorCyclesKernel);
|
||||
yield return Loader("PrimeTester.SmallPrimeSieveKernel", CompilePrimeTesterSmallPrimeKernel);
|
||||
yield return Loader("PrimeTester.SharesFactorKernel", CompilePrimeTesterSharesFactorKernel);
|
||||
yield return Loader("GpuKernelPool.KernelLeaseKernels", CompileGpuKernelPoolKernels);
|
||||
}
|
||||
|
||||
private static object[] Loader(string name, Action<Accelerator> loader)
|
||||
{
|
||||
return new object[] { name, loader };
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[Trait("Category", "Fast")]
|
||||
[MemberData(nameof(KernelLoaders))]
|
||||
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||
{
|
||||
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||
var accelerator = lease.Accelerator;
|
||||
Action action = () => compile(accelerator);
|
||||
action.Should().NotThrow($"kernel {name} should compile");
|
||||
}
|
||||
|
||||
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = DivisorCycleCacheLoadKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttBitReverseKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetBitReverseKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttMulKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetMulKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttStageKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetStageKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttScaleKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetScaleKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttStageMontKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetStageMontKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttStageBarrettKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetStageBarrettKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttScaleBarrettKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetScaleBarrettKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttSquareBarrettKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetSquareBarrettKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttToMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetToMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttFromMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetFromMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttSquareMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetSquareMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttScaleMont64Kernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetScaleMont64KernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttForwardKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetForwardKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileNttInverseKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = NttGetInverseKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorCheckKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorExponentKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetExponentKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorRemainderDeltaKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetRemainderDeltaKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorByDivisorRemainderScanKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
_ = DivisorByDivisorGetRemainderScanKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileDivisorTesterKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
_ = DivisorTesterGetKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerAddSmallKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetAddSmallKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerSubSmallKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetSubSmallKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerReduceKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetReduceKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerIsZeroKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetIsZeroKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileLucasLehmerBatchKernel(Accelerator accelerator)
|
||||
{
|
||||
var tester = new MersenneNumberLucasLehmerGpuTester();
|
||||
_ = LucasLehmerGetBatchKernelMethod.Invoke(tester, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderPartialFactorKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetPartialFactorKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetOrderKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderPow2ModKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetPow2ModKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
|
||||
{
|
||||
_ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
|
||||
}
|
||||
|
||||
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
|
||||
{
|
||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
|
||||
_ = kernel;
|
||||
}
|
||||
|
||||
private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
|
||||
{
|
||||
_ = accelerator;
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: false);
|
||||
try
|
||||
{
|
||||
_ = lease.OrderKernel;
|
||||
_ = lease.IncrementalKernel;
|
||||
_ = lease.Pow2ModKernel;
|
||||
_ = lease.IncrementalOrderKernel;
|
||||
_ = lease.Pow2ModOrderKernel;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
|
||||
var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
_ = orderLease.OrderKernel;
|
||||
_ = orderLease.IncrementalKernel;
|
||||
_ = orderLease.Pow2ModKernel;
|
||||
_ = orderLease.IncrementalOrderKernel;
|
||||
_ = orderLease.Pow2ModOrderKernel;
|
||||
}
|
||||
finally
|
||||
{
|
||||
orderLease.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -25,8 +25,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
try
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
@@ -40,7 +40,9 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
[Fact]
|
||||
public void TryPow2Mod_returns_success_for_supported_prime()
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, 193UL, out ulong remainder);
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
remainder.Should().Be(2UL);
|
||||
@@ -54,7 +56,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(1UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
@@ -73,12 +76,14 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
PrimeOrderGpuHeuristics.OverrideCapabilitiesForTesting(capability);
|
||||
try
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, 193UL, out ulong remainder);
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(16UL, prime, out ulong remainder, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainder.Should().Be(0UL);
|
||||
|
||||
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, 193UL, out ulong followUpRemainder);
|
||||
GpuPow2ModStatus followUp = PrimeOrderGpuHeuristics.TryPow2Mod(4UL, prime, out ulong followUpRemainder, divisorData);
|
||||
followUp.Should().Be(GpuPow2ModStatus.Success);
|
||||
followUpRemainder.Should().Be(16UL);
|
||||
}
|
||||
@@ -98,7 +103,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
|
||||
ulong[] remainders = new ulong[2];
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
@@ -115,7 +121,9 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
{
|
||||
ulong[] remainders = { 123UL, 456UL };
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, 257UL, remainders);
|
||||
const ulong prime = 257UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Success);
|
||||
remainders[0].Should().Be(2UL);
|
||||
@@ -133,7 +141,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
ulong[] remainders = { 5UL, 6UL };
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
@@ -156,7 +165,9 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
ulong[] exponents = { 2UL, 16UL };
|
||||
ulong[] remainders = { 9UL, 11UL };
|
||||
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, 193UL, remainders);
|
||||
const ulong prime = 193UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
|
||||
status.Should().Be(GpuPow2ModStatus.Overflow);
|
||||
remainders[0].Should().Be(0UL);
|
||||
@@ -322,16 +333,22 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
||||
{
|
||||
ulong[] remainders = new ulong[1];
|
||||
ulong[] exponents = { 1UL, 2UL };
|
||||
// This will never happen in production code because the caller ensures the remainder span is large enough.
|
||||
// [Fact]
|
||||
// public void TryPow2ModBatch_throws_when_remainder_span_is_too_small()
|
||||
// {
|
||||
// ulong[] remainders = new ulong[1];
|
||||
// ulong[] exponents = { 1UL, 2UL };
|
||||
|
||||
Action act = () => PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, 5UL, remainders);
|
||||
// Action act = () =>
|
||||
// {
|
||||
// const ulong prime = 5UL;
|
||||
// MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
// PrimeOrderGpuHeuristics.TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
// };
|
||||
|
||||
act.Should().Throw<ArgumentException>();
|
||||
}
|
||||
// act.Should().Throw<ArgumentException>();
|
||||
// }
|
||||
|
||||
[Fact]
|
||||
public void Calculate_falls_back_to_cpu_when_gpu_overflow_is_marked()
|
||||
@@ -341,7 +358,8 @@ public class PrimeOrderGpuHeuristicsTests
|
||||
{
|
||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(prime, null, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
result.Order.Should().Be(3UL);
|
||||
|
||||
@@ -38,7 +38,8 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
expected.Should().Contain(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor));
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -86,7 +87,8 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
foreach (var pair in pairs)
|
||||
{
|
||||
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor));
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
|
||||
uniqueDivisors.Should().BeEquivalentTo(expected);
|
||||
|
||||
@@ -18,7 +18,8 @@ public class MersenneDivisorCyclesTests
|
||||
[MemberData(nameof(Divisors))]
|
||||
public void CalculateCycleLength_returns_expected_value(ulong divisor, ulong expected)
|
||||
{
|
||||
MersenneDivisorCycles.CalculateCycleLength(divisor).Should().Be(expected);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData).Should().Be(expected);
|
||||
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace PerfectNumbers.Core.Tests;
|
||||
|
||||
public class MersenneNumberDivisorByDivisorTesterTests
|
||||
{
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void Run_marks_candidates_divisible_by_seven_or_eleven_as_composite()
|
||||
{
|
||||
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
|
||||
var tester = new FakeTester();
|
||||
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>();
|
||||
int compositeMarks = 0;
|
||||
int clearedMarks = 0;
|
||||
|
||||
MersenneNumberDivisorByDivisorTester.Run(
|
||||
candidates,
|
||||
tester,
|
||||
previousResults: null,
|
||||
startPrime: 0UL,
|
||||
markComposite: () => compositeMarks++,
|
||||
clearComposite: () => clearedMarks++,
|
||||
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
|
||||
threadCount: 1);
|
||||
|
||||
tester.ConfiguredMaxPrime.Should().Be(13UL);
|
||||
compositeMarks.Should().Be(2);
|
||||
clearedMarks.Should().Be(2);
|
||||
results.Should().Equal(
|
||||
new[]
|
||||
{
|
||||
(5UL, true, true, true),
|
||||
(7UL, true, true, false),
|
||||
(11UL, true, true, false),
|
||||
(13UL, true, true, true),
|
||||
});
|
||||
}
|
||||
|
||||
private sealed class FakeTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
public ulong ConfiguredMaxPrime { get; private set; }
|
||||
|
||||
public int BatchSize { get; set; }
|
||||
|
||||
public ulong DivisorLimit => ConfiguredMaxPrime + 100UL;
|
||||
|
||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||
{
|
||||
ConfiguredMaxPrime = maxPrime;
|
||||
}
|
||||
|
||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||
{
|
||||
return DivisorLimit;
|
||||
}
|
||||
|
||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
||||
}
|
||||
|
||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||
{
|
||||
return new DummySession();
|
||||
}
|
||||
|
||||
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||
{
|
||||
for (int i = 0; i < primes.Length; i++)
|
||||
{
|
||||
allowedMaxValues[i] = 100UL;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||
{
|
||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,14 +35,50 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL);
|
||||
session.CheckDivisor(31UL, cycle31, primes, hits);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using FluentAssertions;
|
||||
using PerfectNumbers.Core;
|
||||
@@ -11,15 +12,18 @@ namespace PerfectNumbers.Core.Tests;
|
||||
public class MersenneNumberDivisorGpuTesterTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(3UL, 7UL, true)]
|
||||
[InlineData(11UL, 23UL, true)]
|
||||
[InlineData(7UL, 35UL, false)]
|
||||
[InlineData(13UL, 23UL, false)]
|
||||
[InlineData(33UL, 7UL, true)]
|
||||
[InlineData(35UL, 31UL, true)]
|
||||
[InlineData(37UL, 223UL, true)]
|
||||
[InlineData(33UL, 13UL, false)]
|
||||
[InlineData(35UL, 73UL, false)]
|
||||
[InlineData(37UL, 227UL, false)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsDivisible(exponent, divisor).Should().Be(expected);
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(exponent, in divisorValue).Should().Be(expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -28,8 +32,10 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
UInt128 divisor = (UInt128.One << 65) - UInt128.One;
|
||||
tester.IsDivisible(65UL, divisor).Should().BeTrue();
|
||||
tester.IsDivisible(65UL, divisor + 2).Should().BeFalse();
|
||||
ReadOnlyGpuUInt128 divisorValue = new ReadOnlyGpuUInt128(divisor);
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeTrue();
|
||||
divisorValue = new ReadOnlyGpuUInt128(divisor + 2); // Reusing divisorValue for the shifted candidate.
|
||||
tester.IsDivisible(65UL, in divisorValue).Should().BeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -41,7 +47,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
.GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!
|
||||
.SetValue(null, Array.Empty<(ulong, uint)>());
|
||||
|
||||
tester.IsPrime(11UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
tester.IsPrime(31UL, UInt128.Zero, 0UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeFalse();
|
||||
}
|
||||
|
||||
@@ -50,7 +56,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void IsPrime_sets_divisorsExhausted_true_when_divisible_by_specified_divisor()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsPrime(3UL, 7UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
tester.IsPrime(35UL, 31UL, 0UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -59,7 +65,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void IsPrime_accepts_large_search_limits()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorGpuTester();
|
||||
tester.IsPrime(3UL, 7UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
||||
tester.IsPrime(35UL, 31UL, ulong.MaxValue, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -68,15 +74,15 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(11UL);
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
tester.IsPrime(5UL, out bool exhausted).Should().BeTrue();
|
||||
tester.IsPrime(31UL, out bool exhausted).Should().BeTrue();
|
||||
exhausted.Should().BeTrue();
|
||||
|
||||
tester.IsPrime(7UL, out exhausted).Should().BeTrue();
|
||||
tester.IsPrime(37UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
|
||||
tester.IsPrime(11UL, out exhausted).Should().BeFalse();
|
||||
tester.IsPrime(43UL, out exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -85,7 +91,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_tester_requires_configuration()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
Action act = () => tester.IsPrime(5UL, out _);
|
||||
Action act = () => tester.IsPrime(31UL, out _);
|
||||
act.Should().Throw<InvalidOperationException>();
|
||||
}
|
||||
|
||||
@@ -94,16 +100,30 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_tester_respects_filter_based_limit()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(5UL);
|
||||
tester.ConfigureFromMaxPrime(41UL);
|
||||
|
||||
tester.IsPrime(5UL, out _).Should().BeTrue();
|
||||
tester.IsPrime(7UL, out bool exhausted).Should().BeTrue();
|
||||
tester.IsPrime(31UL, out _).Should().BeTrue();
|
||||
tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
|
||||
exhausted.Should().BeTrue();
|
||||
|
||||
tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
|
||||
[Theory]
|
||||
[InlineData(71UL, false)]
|
||||
[InlineData(89UL, true)]
|
||||
[InlineData(127UL, true)]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_tester_matches_incremental_expectations(ulong exponent, bool expectedPrime)
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(exponent);
|
||||
|
||||
tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime);
|
||||
exhausted.Should().BeTrue();
|
||||
}
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_tester_uses_cycle_remainders_per_divisor()
|
||||
@@ -113,9 +133,9 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
GpuBatchSize = 8,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(13UL);
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
tester.IsPrime(11UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
|
||||
@@ -124,20 +144,20 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
public void ByDivisor_session_checks_divisors_across_primes()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(13UL);
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||
|
||||
Array.Fill(hits, (byte)0);
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL);
|
||||
session.CheckDivisor(31UL, cycle31, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits);
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -149,16 +169,122 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
GpuBatchSize = 2,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(17UL);
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
GpuBatchSize = 5,
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(43UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
||||
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
||||
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
||||
byte[] computedHits = new byte[exponents.Length];
|
||||
for (int i = 0; i < residues.Length; i++)
|
||||
{
|
||||
computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||
{
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||
{
|
||||
GpuBatchSize = 5,
|
||||
};
|
||||
tester.ConfigureFromMaxPrime(61UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||
byte[] hits = new byte[exponents.Length];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||
|
||||
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
||||
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
||||
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
||||
byte[] computedHits = new byte[exponents.Length];
|
||||
for (int i = 0; i < residues.Length; i++)
|
||||
{
|
||||
computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||
}
|
||||
|
||||
[Fact]
|
||||
[Trait("Category", "Fast")]
|
||||
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
|
||||
{
|
||||
var cycles = MersenneDivisorCycles.Shared;
|
||||
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
|
||||
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||
|
||||
try
|
||||
{
|
||||
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
|
||||
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
||||
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(191UL, divisorData);
|
||||
|
||||
tableField.SetValue(cycles, patchedTable);
|
||||
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
|
||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||
tester.ConfigureFromMaxPrime(19UL);
|
||||
|
||||
typeof(MersenneNumberDivisorByDivisorGpuTester)
|
||||
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||
.SetValue(tester, 200UL);
|
||||
|
||||
tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue();
|
||||
divisorsExhausted.Should().BeTrue();
|
||||
}
|
||||
finally
|
||||
{
|
||||
tableField.SetValue(cycles, originalTable);
|
||||
smallCyclesField.SetValue(cycles, originalSmall);
|
||||
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||
GpuContextPool.DisposeAll();
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -170,16 +296,16 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
BatchSize = 2,
|
||||
};
|
||||
|
||||
tester.ConfigureFromMaxPrime(17UL);
|
||||
tester.ConfigureFromMaxPrime(47UL);
|
||||
|
||||
using var session = tester.CreateDivisorSession();
|
||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL, 17UL };
|
||||
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||
byte[] hits = new byte[primes.Length];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL);
|
||||
session.CheckDivisor(23UL, cycle23, primes, hits);
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0, 0 });
|
||||
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ public class MersenneNumberIncrementalGpuTesterTests
|
||||
{
|
||||
private static bool LastDigitIsSeven(ulong exponent) => (exponent & 3UL) == 3UL;
|
||||
|
||||
[Theory]
|
||||
[Theory(Skip = "Disabled until the GPU incremental cycle heuristics are ported.")]
|
||||
[InlineData(GpuKernelType.Pow2Mod, false)]
|
||||
[InlineData(GpuKernelType.Incremental, false)]
|
||||
[InlineData(GpuKernelType.Pow2Mod, true)]
|
||||
@@ -20,7 +20,8 @@ public class MersenneNumberIncrementalGpuTesterTests
|
||||
|
||||
if (type == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
RunCase(tester, 11UL, 1UL, expectedPrime: false);
|
||||
// Mirrored by the divisor GPU coverage to ensure the failing cases stay validated.
|
||||
RunCase(tester, 37UL, 1UL, expectedPrime: false);
|
||||
RunCase(tester, 71UL, 341_860UL, expectedPrime: false);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ public class PrimeOrderCalculatorTests
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
@@ -64,11 +65,13 @@ public class PrimeOrderCalculatorTests
|
||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault);
|
||||
|
||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
@@ -85,6 +88,7 @@ public class PrimeOrderCalculatorTests
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||
prime,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(prime),
|
||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
@@ -104,6 +108,7 @@ public class PrimeOrderCalculatorTests
|
||||
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||
prime: 13UL,
|
||||
previousOrder: null,
|
||||
MontgomeryDivisorData.FromModulus(13UL),
|
||||
config);
|
||||
|
||||
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
||||
@@ -120,9 +125,11 @@ public class PrimeOrderCalculatorTests
|
||||
maxPowChecks: 1,
|
||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
heuristicConfig);
|
||||
|
||||
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
||||
@@ -137,6 +144,7 @@ public class PrimeOrderCalculatorTests
|
||||
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
||||
prime: 239UL,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
strictConfig);
|
||||
|
||||
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||
|
||||
@@ -14,6 +14,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
private ulong _lastStatusDivisor;
|
||||
private bool _isConfigured;
|
||||
private int _batchSize = 1_024;
|
||||
private const int PollardBrentMaxRestarts = 4;
|
||||
private const int PollardBrentIterationBudget = 262_144;
|
||||
|
||||
|
||||
public int BatchSize
|
||||
@@ -80,9 +82,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return true;
|
||||
}
|
||||
|
||||
ulong processedCount = 0UL;
|
||||
ulong lastProcessed = 0UL;
|
||||
bool processedAll = false;
|
||||
ulong processedCount;
|
||||
ulong lastProcessed;
|
||||
bool processedAll;
|
||||
|
||||
bool composite = CheckDivisors(
|
||||
prime,
|
||||
@@ -95,7 +97,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
{
|
||||
lock (_sync)
|
||||
{
|
||||
UpdateStatusUnsafe(lastProcessed, processedCount);
|
||||
UpdateStatusUnsafe(processedCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,61 +176,121 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
|
||||
ulong divisor = 3UL;
|
||||
while (divisor <= allowedMax)
|
||||
ulong pollardIterationCount = 0UL;
|
||||
if (prime > 3UL)
|
||||
{
|
||||
processedCount++;
|
||||
lastProcessed = divisor;
|
||||
var pollardBrent = new MersennePollardBrent(prime, _batchSize);
|
||||
if (pollardBrent.IsSupported)
|
||||
{
|
||||
bool pollardFound = pollardBrent.TryFindFactor(
|
||||
allowedMax,
|
||||
PollardBrentMaxRestarts,
|
||||
PollardBrentIterationBudget,
|
||||
out ulong pollardFactor,
|
||||
out pollardIterationCount);
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
// TODO: Hoist MontgomeryDivisorData acquisition into the divisor-cycle cache so we reuse the
|
||||
// staged ProcessEightBitWindows-ready metadata instead of reloading the slower Montgomery
|
||||
// structs for every divisor.
|
||||
ulong divisorCycle;
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
||||
}
|
||||
else
|
||||
{
|
||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(divisor, prime, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
||||
}
|
||||
}
|
||||
if (divisorCycle != 0UL)
|
||||
{
|
||||
byte hit = CheckDivisor(prime, divisorCycle, divisorData);
|
||||
if (hit != 0)
|
||||
if (pollardFound)
|
||||
{
|
||||
processedCount = pollardIterationCount;
|
||||
lastProcessed = pollardFactor;
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (divisor >= allowedMax - 1UL)
|
||||
{
|
||||
processedAll = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (divisor > ulong.MaxValue - 2UL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
divisor += 2UL;
|
||||
// TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||
// CPU by-divisor benchmarks so we advance directly to the next cached divisor candidate
|
||||
// instead of testing every odd integer.
|
||||
}
|
||||
|
||||
if (!processedAll)
|
||||
processedCount = pollardIterationCount;
|
||||
|
||||
UInt128 step = (UInt128)prime << 1;
|
||||
if (step == UInt128.Zero)
|
||||
{
|
||||
processedAll = divisor > allowedMax;
|
||||
processedAll = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
UInt128 limit = allowedMax;
|
||||
UInt128 divisor = step + UInt128.One;
|
||||
if (divisor > limit)
|
||||
{
|
||||
processedAll = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
|
||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||
|
||||
byte step10 = (byte)(step % 10UL);
|
||||
byte step8 = (byte)(step % 8UL);
|
||||
byte step5 = (byte)(step % 5UL);
|
||||
byte step3 = (byte)(step % 3UL);
|
||||
byte step7 = (byte)(step % 7UL);
|
||||
byte step11 = (byte)(step % 11UL);
|
||||
|
||||
byte remainder10 = (byte)(divisor % 10UL);
|
||||
byte remainder8 = (byte)(divisor % 8UL);
|
||||
byte remainder5 = (byte)(divisor % 5UL);
|
||||
byte remainder3 = (byte)(divisor % 3UL);
|
||||
byte remainder7 = (byte)(divisor % 7UL);
|
||||
byte remainder11 = (byte)(divisor % 11UL);
|
||||
|
||||
// Keep the divisibility filters aligned with the divisor-cycle generator so the
|
||||
// CPU path never requests cycles that were skipped during cache creation.
|
||||
bool lastIsSeven = (prime & 3UL) == 3UL;
|
||||
|
||||
while (divisor <= limit)
|
||||
{
|
||||
ulong candidate = (ulong)divisor;
|
||||
processedCount++;
|
||||
lastProcessed = candidate;
|
||||
|
||||
bool admissible = lastIsSeven
|
||||
? (remainder10 == 3 || remainder10 == 7 || remainder10 == 9)
|
||||
: (remainder10 == 1 || remainder10 == 3 || remainder10 == 9);
|
||||
|
||||
if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
|
||||
ulong divisorCycle;
|
||||
|
||||
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||
}
|
||||
else
|
||||
{
|
||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||
}
|
||||
}
|
||||
|
||||
if (divisorCycle != 0 && divisorCycle == prime)
|
||||
{
|
||||
processedAll = true;
|
||||
return true;
|
||||
}
|
||||
else if (divisorCycle == 0)
|
||||
{
|
||||
Console.WriteLine($"Divisor cycle wasn't calculated for ${prime}");
|
||||
}
|
||||
// if (divisorCycle != 0UL && CheckDivisor(prime, divisorCycle, divisorData) != 0)
|
||||
// {
|
||||
// processedAll = true;
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
|
||||
divisor += step;
|
||||
remainder10 = AddMod(remainder10, step10, (byte)10);
|
||||
remainder8 = AddMod(remainder8, step8, (byte)8);
|
||||
remainder5 = AddMod(remainder5, step5, (byte)5);
|
||||
remainder3 = AddMod(remainder3, step3, (byte)3);
|
||||
remainder7 = AddMod(remainder7, step7, (byte)7);
|
||||
remainder11 = AddMod(remainder11, step11, (byte)11);
|
||||
}
|
||||
|
||||
processedAll = divisor > limit;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -238,7 +300,18 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return residue == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
private void UpdateStatusUnsafe(ulong lastProcessed, ulong processedCount)
|
||||
private static byte AddMod(byte value, byte delta, byte modulus)
|
||||
{
|
||||
int sum = value + delta;
|
||||
if (sum >= modulus)
|
||||
{
|
||||
sum -= modulus;
|
||||
}
|
||||
|
||||
return (byte)sum;
|
||||
}
|
||||
|
||||
private void UpdateStatusUnsafe(ulong processedCount)
|
||||
{
|
||||
if (processedCount == 0UL)
|
||||
{
|
||||
@@ -303,7 +376,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
_disposed = false;
|
||||
}
|
||||
|
||||
public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
@@ -316,6 +389,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
return;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData cachedData = divisorData;
|
||||
if (cachedData.Modulus != divisor)
|
||||
{
|
||||
cachedData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
||||
@@ -326,25 +405,25 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
}
|
||||
}
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
|
||||
var exponentStepper = new ExponentRemainderStepper(divisorData);
|
||||
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
|
||||
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
|
||||
var exponentStepper = new ExponentRemainderStepper(cachedData);
|
||||
if (!exponentStepper.IsValidModulus)
|
||||
{
|
||||
hits.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var stepper = new CycleRemainderStepper(divisorCycle);
|
||||
var cycleStepper = new CycleRemainderStepper(divisorCycle);
|
||||
|
||||
ulong remainder = stepper.Initialize(primes[0]);
|
||||
ulong remainder = cycleStepper.Initialize(primes[0]);
|
||||
hits[0] = remainder == 0UL
|
||||
? (exponentStepper.ComputeNextIsUnity(primes[0]) ? (byte)1 : (byte)0)
|
||||
: (byte)0;
|
||||
|
||||
for (int i = 1; i < length; i++)
|
||||
{
|
||||
remainder = stepper.ComputeNext(primes[i]);
|
||||
remainder = cycleStepper.ComputeNext(primes[i]);
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
hits[i] = 0;
|
||||
@@ -354,6 +433,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
||||
hits[i] = exponentStepper.ComputeNextIsUnity(primes[i]) ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
|
||||
@@ -0,0 +1,926 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Numerics;
|
||||
using System.Security.Cryptography;
|
||||
using PerfectNumbers.Core;
|
||||
using UInt128 = System.UInt128;
|
||||
|
||||
namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
internal sealed class MersennePollardBrent
|
||||
{
|
||||
private enum ArithmeticMode
|
||||
{
|
||||
Unsupported,
|
||||
UInt64,
|
||||
UInt128,
|
||||
BigInteger,
|
||||
}
|
||||
|
||||
private readonly ArithmeticMode _mode;
|
||||
private readonly int _bitLength;
|
||||
private readonly int _byteLength;
|
||||
private readonly int _bitRemainder;
|
||||
private readonly byte _maskByte;
|
||||
private readonly BigInteger _modulus;
|
||||
private readonly BigInteger _mask;
|
||||
private readonly ulong _modulus64;
|
||||
private readonly ulong _mask64;
|
||||
private readonly UInt128 _modulus128;
|
||||
private readonly UInt128 _mask128;
|
||||
private readonly int _batchSize;
|
||||
private readonly bool _isSupported;
|
||||
|
||||
public MersennePollardBrent(ulong exponent, int batchSize)
|
||||
{
|
||||
if (exponent < 3UL || exponent > int.MaxValue)
|
||||
{
|
||||
_mode = ArithmeticMode.Unsupported;
|
||||
_isSupported = false;
|
||||
_bitLength = 0;
|
||||
_byteLength = 0;
|
||||
_bitRemainder = 0;
|
||||
_maskByte = 0;
|
||||
_modulus = BigInteger.Zero;
|
||||
_mask = BigInteger.Zero;
|
||||
_modulus64 = 0UL;
|
||||
_mask64 = 0UL;
|
||||
_modulus128 = UInt128.Zero;
|
||||
_mask128 = UInt128.Zero;
|
||||
_batchSize = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
_isSupported = true;
|
||||
_bitLength = (int)exponent;
|
||||
_byteLength = (_bitLength + 7) / 8;
|
||||
_bitRemainder = _bitLength & 7;
|
||||
_maskByte = _bitRemainder == 0 ? (byte)0xFF : (byte)((1 << _bitRemainder) - 1);
|
||||
_batchSize = Math.Clamp(batchSize, 8, 1024);
|
||||
|
||||
if (_bitLength <= 64)
|
||||
{
|
||||
_mode = ArithmeticMode.UInt64;
|
||||
if (_bitLength == 64)
|
||||
{
|
||||
_modulus64 = ulong.MaxValue;
|
||||
_mask64 = ulong.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_modulus64 = (1UL << _bitLength) - 1UL;
|
||||
_mask64 = _modulus64;
|
||||
}
|
||||
|
||||
_modulus128 = UInt128.Zero;
|
||||
_mask128 = UInt128.Zero;
|
||||
_modulus = BigInteger.Zero;
|
||||
_mask = BigInteger.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
if (_bitLength <= 128)
|
||||
{
|
||||
_mode = ArithmeticMode.UInt128;
|
||||
if (_bitLength == 128)
|
||||
{
|
||||
_modulus128 = UInt128.MaxValue;
|
||||
_mask128 = UInt128.MaxValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
_modulus128 = (UInt128.One << _bitLength) - UInt128.One;
|
||||
_mask128 = _modulus128;
|
||||
}
|
||||
|
||||
_modulus64 = 0UL;
|
||||
_mask64 = 0UL;
|
||||
_modulus = BigInteger.Zero;
|
||||
_mask = BigInteger.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
_mode = ArithmeticMode.BigInteger;
|
||||
_modulus = (BigInteger.One << _bitLength) - BigInteger.One;
|
||||
_mask = _modulus;
|
||||
_modulus64 = 0UL;
|
||||
_mask64 = 0UL;
|
||||
_modulus128 = UInt128.Zero;
|
||||
_mask128 = UInt128.Zero;
|
||||
}
|
||||
|
||||
public bool IsSupported => _isSupported;
|
||||
|
||||
public bool TryFindFactor(
|
||||
ulong allowedMax,
|
||||
int maxRestarts,
|
||||
int maxIterationsPerRestart,
|
||||
out ulong factor,
|
||||
out ulong iterationCount)
|
||||
{
|
||||
factor = 0UL;
|
||||
iterationCount = 0UL;
|
||||
|
||||
if (!_isSupported)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (allowedMax <= 1UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return _mode switch
|
||||
{
|
||||
ArithmeticMode.UInt64 => TryFindFactorUInt64(allowedMax, maxRestarts, maxIterationsPerRestart, out factor, out iterationCount),
|
||||
ArithmeticMode.UInt128 => TryFindFactorUInt128(allowedMax, maxRestarts, maxIterationsPerRestart, out factor, out iterationCount),
|
||||
ArithmeticMode.BigInteger => TryFindFactorBigInteger(allowedMax, maxRestarts, maxIterationsPerRestart, out factor, out iterationCount),
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
private bool TryFindFactorUInt64(
|
||||
ulong allowedMax,
|
||||
int maxRestarts,
|
||||
int maxIterationsPerRestart,
|
||||
out ulong factor,
|
||||
out ulong iterationCount)
|
||||
{
|
||||
factor = 0UL;
|
||||
iterationCount = 0UL;
|
||||
|
||||
int restarts = Math.Max(1, maxRestarts);
|
||||
int iterationBudget = Math.Max(_batchSize, maxIterationsPerRestart);
|
||||
|
||||
for (int restart = 0; restart < restarts; restart++)
|
||||
{
|
||||
ulong x = NextRandomSeed64();
|
||||
ulong y = x;
|
||||
ulong c = NextRandomConstant64();
|
||||
ulong q = 1UL;
|
||||
ulong g = 1UL;
|
||||
ulong ys = 0UL;
|
||||
int r = 1;
|
||||
ulong localIterations = 0UL;
|
||||
|
||||
while (g == 1UL && localIterations < (ulong)iterationBudget)
|
||||
{
|
||||
x = y;
|
||||
for (int i = 0; i < r && localIterations < (ulong)iterationBudget; i++)
|
||||
{
|
||||
y = Iterate64(y, c, ref localIterations);
|
||||
}
|
||||
|
||||
if (localIterations >= (ulong)iterationBudget)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
while (k < r && g == 1UL && localIterations < (ulong)iterationBudget)
|
||||
{
|
||||
ys = y;
|
||||
int t = Math.Min(_batchSize, r - k);
|
||||
for (int i = 0; i < t; i++)
|
||||
{
|
||||
y = Iterate64(y, c, ref localIterations);
|
||||
ulong diff = AbsSub64(y, x);
|
||||
if (diff == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
q = MulMod64(q, diff);
|
||||
}
|
||||
|
||||
g = BinaryGcd64(q, _modulus64);
|
||||
k += t;
|
||||
}
|
||||
|
||||
r <<= 1;
|
||||
}
|
||||
|
||||
if (g == _modulus64)
|
||||
{
|
||||
do
|
||||
{
|
||||
ys = Iterate64(ys, c, ref localIterations);
|
||||
ulong diff = AbsSub64(ys, x);
|
||||
if (diff == 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
g = BinaryGcd64(diff, _modulus64);
|
||||
}
|
||||
while (g == 1UL);
|
||||
}
|
||||
|
||||
iterationCount += localIterations;
|
||||
|
||||
if (g == 1UL || g == _modulus64)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g > 1UL && g <= allowedMax)
|
||||
{
|
||||
factor = g;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryFindFactorUInt128(
|
||||
ulong allowedMax,
|
||||
int maxRestarts,
|
||||
int maxIterationsPerRestart,
|
||||
out ulong factor,
|
||||
out ulong iterationCount)
|
||||
{
|
||||
factor = 0UL;
|
||||
iterationCount = 0UL;
|
||||
|
||||
int restarts = Math.Max(1, maxRestarts);
|
||||
int iterationBudget = Math.Max(_batchSize, maxIterationsPerRestart);
|
||||
UInt128 allowedMaxWide = (UInt128)allowedMax;
|
||||
|
||||
for (int restart = 0; restart < restarts; restart++)
|
||||
{
|
||||
UInt128 x = NextRandomSeed128();
|
||||
UInt128 y = x;
|
||||
UInt128 c = NextRandomConstant128();
|
||||
UInt128 q = UInt128.One;
|
||||
UInt128 g = UInt128.One;
|
||||
UInt128 ys = UInt128.Zero;
|
||||
int r = 1;
|
||||
ulong localIterations = 0UL;
|
||||
|
||||
while (g == UInt128.One && localIterations < (ulong)iterationBudget)
|
||||
{
|
||||
x = y;
|
||||
for (int i = 0; i < r && localIterations < (ulong)iterationBudget; i++)
|
||||
{
|
||||
y = Iterate128(y, c, ref localIterations);
|
||||
}
|
||||
|
||||
if (localIterations >= (ulong)iterationBudget)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
while (k < r && g == UInt128.One && localIterations < (ulong)iterationBudget)
|
||||
{
|
||||
ys = y;
|
||||
int t = Math.Min(_batchSize, r - k);
|
||||
for (int i = 0; i < t; i++)
|
||||
{
|
||||
y = Iterate128(y, c, ref localIterations);
|
||||
UInt128 diff = AbsSub128(y, x);
|
||||
if (diff == UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
q = MulMod128(q, diff);
|
||||
}
|
||||
|
||||
g = q.BinaryGcd(_modulus128);
|
||||
k += t;
|
||||
}
|
||||
|
||||
r <<= 1;
|
||||
}
|
||||
|
||||
if (g == _modulus128)
|
||||
{
|
||||
do
|
||||
{
|
||||
ys = Iterate128(ys, c, ref localIterations);
|
||||
UInt128 diff = AbsSub128(ys, x);
|
||||
if (diff == UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
g = diff.BinaryGcd(_modulus128);
|
||||
}
|
||||
while (g == UInt128.One);
|
||||
}
|
||||
|
||||
iterationCount += localIterations;
|
||||
|
||||
if (g == UInt128.One || g == _modulus128)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g > UInt128.One && g <= allowedMaxWide)
|
||||
{
|
||||
factor = (ulong)g;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool TryFindFactorBigInteger(
|
||||
ulong allowedMax,
|
||||
int maxRestarts,
|
||||
int maxIterationsPerRestart,
|
||||
out ulong factor,
|
||||
out ulong iterationCount)
|
||||
{
|
||||
factor = 0UL;
|
||||
iterationCount = 0UL;
|
||||
|
||||
int restarts = Math.Max(1, maxRestarts);
|
||||
int iterationBudget = Math.Max(_batchSize, maxIterationsPerRestart);
|
||||
|
||||
for (int restart = 0; restart < restarts; restart++)
|
||||
{
|
||||
BigInteger x = NextRandomSeedBigInteger();
|
||||
BigInteger y = x;
|
||||
BigInteger c = NextRandomConstantBigInteger();
|
||||
BigInteger q = BigInteger.One;
|
||||
BigInteger g = BigInteger.One;
|
||||
BigInteger ys = BigInteger.Zero;
|
||||
int r = 1;
|
||||
ulong localIterations = 0UL;
|
||||
|
||||
while (g.IsOne && localIterations < (ulong)iterationBudget)
|
||||
{
|
||||
x = y;
|
||||
for (int i = 0; i < r && localIterations < (ulong)iterationBudget; i++)
|
||||
{
|
||||
y = IterateBigInteger(y, c, ref localIterations);
|
||||
}
|
||||
|
||||
if (localIterations >= (ulong)iterationBudget)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
int k = 0;
|
||||
while (k < r && g.IsOne && localIterations < (ulong)iterationBudget)
|
||||
{
|
||||
ys = y;
|
||||
int t = Math.Min(_batchSize, r - k);
|
||||
for (int i = 0; i < t; i++)
|
||||
{
|
||||
y = IterateBigInteger(y, c, ref localIterations);
|
||||
BigInteger diff = AbsSubBigInteger(y, x);
|
||||
if (diff.IsZero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
q = MulModBigInteger(q, diff);
|
||||
}
|
||||
|
||||
g = BigInteger.GreatestCommonDivisor(q, _modulus);
|
||||
k += t;
|
||||
}
|
||||
|
||||
r <<= 1;
|
||||
}
|
||||
|
||||
if (g == _modulus)
|
||||
{
|
||||
do
|
||||
{
|
||||
ys = IterateBigInteger(ys, c, ref localIterations);
|
||||
BigInteger diff = AbsSubBigInteger(ys, x);
|
||||
if (diff.IsZero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
g = BigInteger.GreatestCommonDivisor(diff, _modulus);
|
||||
}
|
||||
while (g.IsOne);
|
||||
}
|
||||
|
||||
iterationCount += localIterations;
|
||||
|
||||
if (g.IsOne || g == _modulus)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (g <= ulong.MaxValue)
|
||||
{
|
||||
ulong candidate = (ulong)g;
|
||||
if (candidate > 1UL && candidate <= allowedMax)
|
||||
{
|
||||
factor = candidate;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private ulong Iterate64(ulong value, ulong c, ref ulong iterationCounter)
|
||||
{
|
||||
iterationCounter++;
|
||||
value = SquareMod64(value);
|
||||
value = AddMod64(value, c);
|
||||
return value;
|
||||
}
|
||||
|
||||
private UInt128 Iterate128(UInt128 value, UInt128 c, ref ulong iterationCounter)
|
||||
{
|
||||
iterationCounter++;
|
||||
value = SquareMod128(value);
|
||||
value = AddMod128(value, c);
|
||||
return value;
|
||||
}
|
||||
|
||||
private BigInteger IterateBigInteger(BigInteger value, BigInteger c, ref ulong iterationCounter)
|
||||
{
|
||||
iterationCounter++;
|
||||
value = SquareModBigInteger(value);
|
||||
value = AddModBigInteger(value, c);
|
||||
return value;
|
||||
}
|
||||
|
||||
private ulong AddMod64(ulong a, ulong b)
|
||||
{
|
||||
UInt128 sum = (UInt128)a + b;
|
||||
return Reduce64(sum);
|
||||
}
|
||||
|
||||
private UInt128 AddMod128(UInt128 a, UInt128 b)
|
||||
{
|
||||
UInt128 sum = a + b;
|
||||
bool carryOut = sum < a;
|
||||
if (!carryOut && sum < _modulus128)
|
||||
{
|
||||
return sum;
|
||||
}
|
||||
|
||||
ulong p3 = 0UL;
|
||||
ulong p2 = carryOut ? 1UL : 0UL;
|
||||
ulong p1 = (ulong)(sum >> 64);
|
||||
ulong p0 = (ulong)sum;
|
||||
return ReduceProductBitwise(p3, p2, p1, p0, _modulus128);
|
||||
}
|
||||
|
||||
private BigInteger AddModBigInteger(BigInteger a, BigInteger b)
|
||||
{
|
||||
return ReduceBigInteger(a + b);
|
||||
}
|
||||
|
||||
private ulong SquareMod64(ulong value)
|
||||
{
|
||||
UInt128 square = (UInt128)value * value;
|
||||
return Reduce64(square);
|
||||
}
|
||||
|
||||
private UInt128 SquareMod128(UInt128 value)
|
||||
{
|
||||
return MulMod128(value, value);
|
||||
}
|
||||
|
||||
private BigInteger SquareModBigInteger(BigInteger value)
|
||||
{
|
||||
return ReduceBigInteger(value * value);
|
||||
}
|
||||
|
||||
private ulong MulMod64(ulong a, ulong b)
|
||||
{
|
||||
UInt128 product = (UInt128)a * b;
|
||||
return Reduce64(product);
|
||||
}
|
||||
|
||||
private UInt128 MulMod128(UInt128 a, UInt128 b)
|
||||
{
|
||||
MultiplyFull(a, b, out ulong p3, out ulong p2, out ulong p1, out ulong p0);
|
||||
return ReduceProductBitwise(p3, p2, p1, p0, _modulus128);
|
||||
}
|
||||
|
||||
private BigInteger MulModBigInteger(BigInteger a, BigInteger b)
|
||||
{
|
||||
if (b == _modulus)
|
||||
{
|
||||
return a;
|
||||
}
|
||||
|
||||
if (a == _modulus)
|
||||
{
|
||||
a = BigInteger.Zero;
|
||||
}
|
||||
|
||||
return ReduceBigInteger(a * b);
|
||||
}
|
||||
|
||||
private ulong Reduce64(UInt128 value)
|
||||
{
|
||||
if (_bitLength == 64)
|
||||
{
|
||||
UInt128 combined = (UInt128)(ulong)value + (value >> 64);
|
||||
UInt128 modulus = _modulus64;
|
||||
if (combined >= modulus)
|
||||
{
|
||||
combined -= modulus;
|
||||
if (combined >= modulus)
|
||||
{
|
||||
combined -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
return (ulong)combined;
|
||||
}
|
||||
|
||||
UInt128 mask = _mask64;
|
||||
UInt128 modulusValue = _modulus64;
|
||||
UInt128 working = value;
|
||||
while (true)
|
||||
{
|
||||
UInt128 high = working >> _bitLength;
|
||||
if (high == UInt128.Zero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
working = (working & mask) + high;
|
||||
}
|
||||
|
||||
if (working >= modulusValue)
|
||||
{
|
||||
working -= modulusValue;
|
||||
if (working >= modulusValue)
|
||||
{
|
||||
working %= modulusValue;
|
||||
if (working >= modulusValue)
|
||||
{
|
||||
working -= modulusValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (ulong)working;
|
||||
}
|
||||
|
||||
private BigInteger ReduceBigInteger(BigInteger value)
|
||||
{
|
||||
if (value.Sign < 0)
|
||||
{
|
||||
value %= _modulus;
|
||||
if (value.Sign < 0)
|
||||
{
|
||||
value += _modulus;
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
BigInteger high = value >> _bitLength;
|
||||
if (high.IsZero)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
value &= _mask;
|
||||
value += high;
|
||||
}
|
||||
|
||||
if (value >= _modulus)
|
||||
{
|
||||
value -= _modulus;
|
||||
if (value >= _modulus)
|
||||
{
|
||||
value %= _modulus;
|
||||
if (value.Sign < 0)
|
||||
{
|
||||
value += _modulus;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private ulong NextRandomSeed64()
|
||||
{
|
||||
ulong value;
|
||||
do
|
||||
{
|
||||
value = NextRandomBelowModulus64();
|
||||
}
|
||||
while (value <= 1UL);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private UInt128 NextRandomSeed128()
|
||||
{
|
||||
UInt128 value;
|
||||
do
|
||||
{
|
||||
value = NextRandomBelowModulus128();
|
||||
}
|
||||
while (value <= UInt128.One);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private BigInteger NextRandomSeedBigInteger()
|
||||
{
|
||||
BigInteger value;
|
||||
do
|
||||
{
|
||||
value = NextRandomBelowModulusBigInteger();
|
||||
}
|
||||
while (value <= BigInteger.One);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private ulong NextRandomConstant64()
|
||||
{
|
||||
ulong value;
|
||||
do
|
||||
{
|
||||
value = NextRandomBelowModulus64();
|
||||
}
|
||||
while (value == 0UL);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private UInt128 NextRandomConstant128()
|
||||
{
|
||||
UInt128 value;
|
||||
do
|
||||
{
|
||||
value = NextRandomBelowModulus128();
|
||||
}
|
||||
while (value == UInt128.Zero);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private BigInteger NextRandomConstantBigInteger()
|
||||
{
|
||||
BigInteger value;
|
||||
do
|
||||
{
|
||||
value = NextRandomBelowModulusBigInteger();
|
||||
}
|
||||
while (value.IsZero);
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private ulong NextRandomBelowModulus64()
|
||||
{
|
||||
if (_byteLength == 0)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[_byteLength];
|
||||
ulong candidate;
|
||||
do
|
||||
{
|
||||
RandomNumberGenerator.Fill(buffer);
|
||||
if (_bitRemainder != 0)
|
||||
{
|
||||
buffer[_byteLength - 1] &= _maskByte;
|
||||
}
|
||||
|
||||
candidate = (ulong)ReadUInt128LittleEndian(buffer);
|
||||
}
|
||||
while (candidate >= _modulus64);
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private UInt128 NextRandomBelowModulus128()
|
||||
{
|
||||
if (_byteLength == 0)
|
||||
{
|
||||
return UInt128.Zero;
|
||||
}
|
||||
|
||||
Span<byte> buffer = stackalloc byte[_byteLength];
|
||||
UInt128 candidate;
|
||||
do
|
||||
{
|
||||
RandomNumberGenerator.Fill(buffer);
|
||||
if (_bitRemainder != 0)
|
||||
{
|
||||
buffer[_byteLength - 1] &= _maskByte;
|
||||
}
|
||||
|
||||
candidate = ReadUInt128LittleEndian(buffer);
|
||||
}
|
||||
while (candidate >= _modulus128);
|
||||
|
||||
return candidate;
|
||||
}
|
||||
|
||||
private BigInteger NextRandomBelowModulusBigInteger()
|
||||
{
|
||||
if (_byteLength == 0)
|
||||
{
|
||||
return BigInteger.Zero;
|
||||
}
|
||||
|
||||
byte[] buffer = ArrayPool<byte>.Shared.Rent(_byteLength);
|
||||
try
|
||||
{
|
||||
BigInteger value;
|
||||
do
|
||||
{
|
||||
RandomNumberGenerator.Fill(buffer.AsSpan(0, _byteLength));
|
||||
if (_bitRemainder != 0)
|
||||
{
|
||||
buffer[_byteLength - 1] &= _maskByte;
|
||||
}
|
||||
|
||||
value = new BigInteger(buffer, isUnsigned: true, isBigEndian: false);
|
||||
}
|
||||
while (value >= _modulus);
|
||||
|
||||
return value;
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<byte>.Shared.Return(buffer, clearArray: true);
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong AbsSub64(ulong a, ulong b)
|
||||
{
|
||||
return a >= b ? a - b : b - a;
|
||||
}
|
||||
|
||||
private static UInt128 AbsSub128(UInt128 a, UInt128 b)
|
||||
{
|
||||
return a >= b ? a - b : b - a;
|
||||
}
|
||||
|
||||
private static BigInteger AbsSubBigInteger(BigInteger a, BigInteger b)
|
||||
{
|
||||
BigInteger diff = a - b;
|
||||
return diff.Sign < 0 ? -diff : diff;
|
||||
}
|
||||
|
||||
private static ulong BinaryGcd64(ulong u, ulong v)
|
||||
{
|
||||
if (u == 0UL)
|
||||
{
|
||||
return v;
|
||||
}
|
||||
|
||||
if (v == 0UL)
|
||||
{
|
||||
return u;
|
||||
}
|
||||
|
||||
int shift = BitOperations.TrailingZeroCount(u | v);
|
||||
u >>= BitOperations.TrailingZeroCount(u);
|
||||
do
|
||||
{
|
||||
v >>= BitOperations.TrailingZeroCount(v);
|
||||
if (u > v)
|
||||
{
|
||||
(u, v) = (v, u);
|
||||
}
|
||||
|
||||
v -= u;
|
||||
}
|
||||
while (v != 0UL);
|
||||
|
||||
return u << shift;
|
||||
}
|
||||
|
||||
private static void MultiplyFull(UInt128 left, UInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
ulong leftLow = (ulong)left;
|
||||
ulong leftHigh = (ulong)(left >> 64);
|
||||
ulong rightLow = (ulong)right;
|
||||
ulong rightHigh = (ulong)(right >> 64);
|
||||
|
||||
(ulong High, ulong Low) partial0 = Multiply64(leftLow, rightLow);
|
||||
(ulong High, ulong Low) partial1 = Multiply64(leftLow, rightHigh);
|
||||
(ulong High, ulong Low) partial2 = Multiply64(leftHigh, rightLow);
|
||||
(ulong High, ulong Low) partial3 = Multiply64(leftHigh, rightHigh);
|
||||
|
||||
p0 = partial0.Low;
|
||||
ulong carry = partial0.High;
|
||||
ulong sum = carry + partial1.Low;
|
||||
ulong carryMid = sum < partial1.Low ? 1UL : 0UL;
|
||||
sum += partial2.Low;
|
||||
if (sum < partial2.Low)
|
||||
{
|
||||
carryMid++;
|
||||
}
|
||||
|
||||
p1 = sum;
|
||||
sum = partial1.High + partial2.High;
|
||||
ulong carryHigh = sum < partial2.High ? 1UL : 0UL;
|
||||
sum += partial3.Low;
|
||||
if (sum < partial3.Low)
|
||||
{
|
||||
carryHigh++;
|
||||
}
|
||||
|
||||
sum += carryMid;
|
||||
if (sum < carryMid)
|
||||
{
|
||||
carryHigh++;
|
||||
}
|
||||
|
||||
p2 = sum;
|
||||
p3 = partial3.High + carryHigh;
|
||||
}
|
||||
|
||||
private static (ulong High, ulong Low) Multiply64(ulong left, ulong right)
|
||||
{
|
||||
ulong a0 = (uint)left;
|
||||
ulong a1 = left >> 32;
|
||||
ulong b0 = (uint)right;
|
||||
ulong b1 = right >> 32;
|
||||
|
||||
ulong lo = a0 * b0;
|
||||
ulong mid1 = a1 * b0;
|
||||
b0 = a0 * b1;
|
||||
b1 *= a1;
|
||||
|
||||
a0 = (lo >> 32) + (uint)mid1 + (uint)b0;
|
||||
a1 = (lo & 0xFFFFFFFFUL) | (a0 << 32);
|
||||
b1 += (mid1 >> 32) + (b0 >> 32) + (a0 >> 32);
|
||||
|
||||
return (b1, a1);
|
||||
}
|
||||
|
||||
private static UInt128 ReduceProductBitwise(ulong p3, ulong p2, ulong p1, ulong p0, UInt128 modulus)
|
||||
{
|
||||
UInt128 remainder = UInt128.Zero;
|
||||
for (int bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder |= (UInt128)((p3 >> bit) & 1UL);
|
||||
if (remainder >= modulus)
|
||||
{
|
||||
remainder -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
for (int bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder |= (UInt128)((p2 >> bit) & 1UL);
|
||||
if (remainder >= modulus)
|
||||
{
|
||||
remainder -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
for (int bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder |= (UInt128)((p1 >> bit) & 1UL);
|
||||
if (remainder >= modulus)
|
||||
{
|
||||
remainder -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
for (int bit = 63; bit >= 0; bit--)
|
||||
{
|
||||
remainder <<= 1;
|
||||
remainder |= (UInt128)((p0 >> bit) & 1UL);
|
||||
if (remainder >= modulus)
|
||||
{
|
||||
remainder -= modulus;
|
||||
}
|
||||
}
|
||||
|
||||
return remainder;
|
||||
}
|
||||
|
||||
private static UInt128 ReadUInt128LittleEndian(ReadOnlySpan<byte> buffer)
|
||||
{
|
||||
UInt128 value = UInt128.Zero;
|
||||
for (int i = buffer.Length - 1; i >= 0; i--)
|
||||
{
|
||||
value <<= 8;
|
||||
value |= buffer[i];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -109,14 +109,16 @@ public sealed class DivisorCycleCache
|
||||
ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
|
||||
{
|
||||
for (int i = 0; i < indices.Length; i++)
|
||||
{
|
||||
int targetIndex = indices[i];
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisors[targetIndex]);
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,16 +25,16 @@ public readonly struct ResidueAutomatonArgs
|
||||
|
||||
public sealed class KernelContainer
|
||||
{
|
||||
// Serializes first-time initialization of kernels/buffers per accelerator.
|
||||
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
|
||||
// Serializes first-time initialization of kernels/buffers per accelerator.
|
||||
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
|
||||
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
|
||||
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
|
||||
|
||||
@@ -45,22 +45,22 @@ public sealed class KernelContainer
|
||||
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
|
||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
|
||||
|
||||
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
|
||||
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
|
||||
{
|
||||
var current = Volatile.Read(ref slot);
|
||||
if (current is not null)
|
||||
{
|
||||
var current = Volatile.Read(ref slot);
|
||||
if (current is not null)
|
||||
{
|
||||
return current;
|
||||
}
|
||||
|
||||
current = Volatile.Read(ref slot);
|
||||
if (current is null)
|
||||
{
|
||||
current = factory();
|
||||
Volatile.Write(ref slot, current);
|
||||
}
|
||||
return current;
|
||||
return current;
|
||||
}
|
||||
|
||||
current = Volatile.Read(ref slot);
|
||||
if (current is null)
|
||||
{
|
||||
current = factory();
|
||||
Volatile.Write(ref slot, current);
|
||||
}
|
||||
return current;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResiduePrimeViews(
|
||||
@@ -77,122 +77,120 @@ public readonly struct ResiduePrimeViews(
|
||||
|
||||
public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels)
|
||||
{
|
||||
private bool disposedValue;
|
||||
private IDisposable? _limiter = limiter;
|
||||
private GpuContextLease? _gpu = gpu;
|
||||
private KernelContainer _kernels = kernels;
|
||||
private AcceleratorStream? _stream;
|
||||
private object? _executionLock = gpu.ExecutionLock;
|
||||
private bool disposedValue;
|
||||
private IDisposable? _limiter = limiter;
|
||||
private GpuContextLease? _gpu = gpu;
|
||||
private KernelContainer _kernels = kernels;
|
||||
private AcceleratorStream? _stream;
|
||||
private object? _executionLock = gpu.ExecutionLock;
|
||||
|
||||
public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
|
||||
public readonly Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
|
||||
|
||||
public AcceleratorStream Stream
|
||||
public AcceleratorStream Stream
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
if (_stream is { } stream)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
// It feels that we can combine these into 1 return statement, but that increases the max stack frame size to 3 from 2
|
||||
stream = Accelerator.CreateStream();
|
||||
_stream = stream;
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ExecutionScope EnterExecutionScope() => new ExecutionScope(_executionLock);
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator; // avoid capturing 'this' in lambda
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Order, () =>
|
||||
{
|
||||
if (_stream is { } stream)
|
||||
{
|
||||
return stream;
|
||||
}
|
||||
|
||||
stream = Accelerator.CreateStream();
|
||||
_stream = stream;
|
||||
return stream;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ExecutionScope EnterExecutionScope()
|
||||
{
|
||||
return new ExecutionScope(_executionLock);
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
var accel = Accelerator; // avoid capturing 'this' in lambda
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Order, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Incremental, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Incremental, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
|
||||
{
|
||||
get
|
||||
{
|
||||
get
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () =>
|
||||
{
|
||||
var accel = Accelerator;
|
||||
lock (_gpu!.Value.KernelInitLock)
|
||||
{
|
||||
return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () =>
|
||||
{
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModOrderKernelScan);
|
||||
var kernel = KernelUtil.GetKernel(loaded);
|
||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||
// (some already accept it). Consider a compact type (byte/ushort) for memory footprint.
|
||||
// TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||
// (some already accept it). Consider a compact type (byte/ushort) for memory footprint.
|
||||
|
||||
private static void IncrementalKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong divMul,
|
||||
ulong q0m10, ulong q0m8, ulong q0m3, ulong q0m5, ArrayView<ulong> orders,
|
||||
@@ -206,21 +204,21 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
// residue automaton
|
||||
ulong step10 = (exponent.Mod10() << 1).Mod10();
|
||||
ulong r10 = q0m10 + (step10 * idx).Mod10();
|
||||
r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL; // skip q ≡ 5 (mod 10); other residues handled by r8/r3/r5
|
||||
if (shouldCheck)
|
||||
{
|
||||
r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL; // skip q ≡ 5 (mod 10); other residues handled by r8/r3/r5
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong r8 = (q0m8 + ((step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Swap these `%` filters to the shared Mod helpers so the residue automaton matches the benchmarked
|
||||
// bitmask-based implementation for GPU workloads.
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
// TODO: Swap these `%` filters to the shared Mod helpers so the residue automaton matches the benchmarked
|
||||
// bitmask-based implementation for GPU workloads.
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong r3 = q0m3 + (step3 * idxMod3); r3 -= (r3 >= 3UL) ? 3UL : 0UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
@@ -236,19 +234,21 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!shouldCheck)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Is this expected that the value of kStart is modified?
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Add(GpuUInt128.One);
|
||||
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
{
|
||||
@@ -260,7 +260,9 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: q will be lowered by GpuUInt128.One here. Is this expected? Maybe that's why we have issues with the incremental kernel? Review other places where we use GpuUInt128 operators.
|
||||
GpuUInt128 phi = q - GpuUInt128.One;
|
||||
// TODO: Do we have the check here because we don't support high values or this is expected result?
|
||||
if (phi.High != 0UL)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -270,13 +272,13 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong phi64 = phi.Low;
|
||||
// TODO: Replace these Pow2Mod calls with the ProcessEightBitWindows helper when the shared windowed
|
||||
// scalar implementation lands; benchmarks showed the windowed kernel trimming per-divisor runtime by ~2.4×.
|
||||
if (GpuUInt128.Pow2Mod(phi64, q) != GpuUInt128.One)
|
||||
if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -284,7 +286,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
|
||||
ulong div = FastDiv64Gpu(phi64, exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -297,6 +299,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
private static void OrderKernelScan(Index1D index, ulong exponent, ulong divMul, ArrayView<GpuUInt128> qs, ArrayView<ulong> orders)
|
||||
{
|
||||
GpuUInt128 q = qs[index];
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
GpuUInt128 phi = q - GpuUInt128.One;
|
||||
if (phi.High != 0UL)
|
||||
{
|
||||
@@ -307,14 +310,14 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong phi64 = phi.Low;
|
||||
// TODO: Once the ProcessEightBitWindows helper is available, switch this order kernel to that faster
|
||||
// Pow2Mod variant so cycle checks inherit the same gains observed in GpuPow2ModBenchmarks.
|
||||
GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, q);
|
||||
GpuUInt128 pow = GpuUInt128.Pow2Mod(phi64, in readOnlyQ);
|
||||
if (pow != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -322,7 +325,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
|
||||
ulong div = FastDiv64Gpu(phi64, exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -332,29 +335,29 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
orders[index] = exponent;
|
||||
}
|
||||
|
||||
private void Dispose(bool disposing)
|
||||
private void Dispose(bool disposing)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (!disposedValue)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
_stream = null;
|
||||
if (disposing)
|
||||
{
|
||||
_stream?.Dispose();
|
||||
_stream = null;
|
||||
|
||||
_gpu?.Dispose();
|
||||
_gpu = null;
|
||||
_gpu?.Dispose();
|
||||
_gpu = null;
|
||||
|
||||
_executionLock = null;
|
||||
_executionLock = null;
|
||||
|
||||
_limiter?.Dispose();
|
||||
_limiter = null;
|
||||
}
|
||||
_limiter?.Dispose();
|
||||
_limiter = null;
|
||||
}
|
||||
|
||||
// free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
// free unmanaged resources (unmanaged objects) and override finalizer
|
||||
// set large fields to null
|
||||
disposedValue = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void Pow2ModKernelScan(Index1D index, ulong exponent, GpuUInt128 twoP, GpuUInt128 kStart, byte lastIsSeven, ulong _,
|
||||
ResidueAutomatonArgs ra, ArrayView<ulong> orders,
|
||||
@@ -404,13 +407,16 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
// TODO: kStart is modified after this. Is this expected?
|
||||
kStart.Add(idx);
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Mul64(kStart);
|
||||
q.Add(GpuUInt128.One);
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
{
|
||||
ulong cycle = smallCycles[(int)q.Low];
|
||||
// cycle should always be initialized if we're within array limit in production code
|
||||
if (cycle != 0UL && cycle <= exponent && (exponent % cycle) != 0UL)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
@@ -419,7 +425,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
// TODO: Swap Pow2Minus1Mod for the eight-bit window helper once the scalar version switches;
|
||||
// benchmarks show the windowed variant cuts large-divisor scans from ~51 µs to ~21 µs.
|
||||
if (GpuUInt128.Pow2Minus1Mod(exponent, q) != GpuUInt128.Zero)
|
||||
if (GpuUInt128.Pow2Minus1Mod(exponent, in readOnlyQ) != GpuUInt128.Zero)
|
||||
{
|
||||
orders[index] = 0UL;
|
||||
return;
|
||||
@@ -517,45 +523,46 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong idxMod3 = idx % 3UL;
|
||||
ulong idxMod5 = idx % 5UL;
|
||||
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Add(GpuUInt128.One);
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
@@ -575,19 +582,19 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong phi64 = phi.Low;
|
||||
// TODO: Upgrade this pow2mod order kernel to the ProcessEightBitWindows helper once available so GPU residue
|
||||
// scans avoid the single-bit ladder that benchmarks found to be 2.3× slower on large exponents.
|
||||
if (GpuUInt128.Pow2Mod(phi64, q) != GpuUInt128.One)
|
||||
if (GpuUInt128.Pow2Mod(phi64, in readOnlyQ) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, q) - GpuUInt128.One;
|
||||
GpuUInt128 halfPow = GpuUInt128.Pow2Mod(phi64 >> 1, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(halfPow, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ulong div = FastDiv64Gpu(phi64, exponent, divMul);
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, q) - GpuUInt128.One;
|
||||
GpuUInt128 divPow = GpuUInt128.Pow2Mod(div, in readOnlyQ) - GpuUInt128.One;
|
||||
if (GpuUInt128.BinaryGcd(divPow, q) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
@@ -603,45 +610,46 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
ulong idxMod3 = idx % 3UL;
|
||||
ulong idxMod5 = idx % 5UL;
|
||||
ulong r10 = ra.Q0M10 + (ra.Step10 * idx).Mod10(); r10 -= (r10 >= 10UL) ? 10UL : 0UL;
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
bool shouldCheck = r10 != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
ulong r8 = (ra.Q0M8 + ((ra.Step8 * idx) & 7UL)) & 7UL;
|
||||
if (r8 != 1UL && r8 != 7UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r3 = ra.Q0M3 + (ra.Step3 * idxMod3);
|
||||
if (r3 >= 6UL) r3 -= 6UL;
|
||||
if (r3 >= 3UL) r3 -= 3UL;
|
||||
if (r3 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
ulong r5 = ra.Q0M5 + (ra.Step5 * idxMod5);
|
||||
if (r5 >= 15UL) r5 -= 15UL;
|
||||
if (r5 >= 10UL) r5 -= 10UL;
|
||||
if (r5 >= 5UL) r5 -= 5UL;
|
||||
if (r5 == 0UL)
|
||||
{
|
||||
shouldCheck = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!shouldCheck)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 k = kStart + (GpuUInt128)idx;
|
||||
GpuUInt128 q = twoP;
|
||||
q.Mul64(k);
|
||||
q.Add(GpuUInt128.One);
|
||||
ReadOnlyGpuUInt128 readOnlyQ = q.AsReadOnly();
|
||||
// Small-cycles in-kernel early rejection from device table
|
||||
if (q.High == 0UL && q.Low < (ulong)smallCycles.Length)
|
||||
{
|
||||
@@ -653,7 +661,7 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
}
|
||||
// TODO: Replace this Pow2Mod check with the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
// residue order scans will then benefit from the same 2× speedup the benchmarked windowed kernel delivered.
|
||||
if (GpuUInt128.Pow2Mod(exponent, q) != GpuUInt128.One)
|
||||
if (GpuUInt128.Pow2Mod(exponent, in readOnlyQ) != GpuUInt128.One)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@@ -661,35 +669,35 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
||||
Atomic.Or(ref found[0], 1);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
// Enabled this when the struct is converted to a class
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public readonly struct ExecutionScope : IDisposable
|
||||
{
|
||||
private readonly object? _lock;
|
||||
|
||||
public ExecutionScope(object? sync)
|
||||
{
|
||||
_lock = sync;
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Enter(_lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
|
||||
Dispose(disposing: true);
|
||||
// Enabled this when the struct is converted to a class
|
||||
// GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public readonly struct ExecutionScope : IDisposable
|
||||
{
|
||||
private readonly object? _lock;
|
||||
|
||||
public ExecutionScope(object? sync)
|
||||
{
|
||||
_lock = sync;
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Enter(_lock);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Exit(_lock);
|
||||
}
|
||||
}
|
||||
if (_lock is not null)
|
||||
{
|
||||
Monitor.Exit(_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class GpuKernelPool
|
||||
@@ -771,19 +779,19 @@ public class GpuKernelPool
|
||||
}
|
||||
}
|
||||
|
||||
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
||||
{
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
var gpu = RentPreferred(preferCpu: !useGpuOrder);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernels = GetKernels(accelerator);
|
||||
return new(limiter, gpu, kernels);
|
||||
}
|
||||
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
||||
{
|
||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||
var gpu = RentPreferred(preferCpu: !useGpuOrder);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernels = GetKernels(accelerator);
|
||||
return new(limiter, gpu, kernels);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs a GPU action with an acquired accelerator and stream.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to run with (Accelerator, Stream).</param>
|
||||
/// <summary>
|
||||
/// Runs a GPU action with an acquired accelerator and stream.
|
||||
/// </summary>
|
||||
/// <param name="action">Action to run with (Accelerator, Stream).</param>
|
||||
public static void Run(Action<Accelerator, AcceleratorStream> action)
|
||||
{
|
||||
var lease = GetKernel(useGpuOrder: true);
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU.Algorithms;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
{
|
||||
public ulong High;
|
||||
|
||||
public ulong Low;
|
||||
|
||||
private const int NativeModuloChunkBits = 8;
|
||||
@@ -20,6 +20,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSizeBits - 1);
|
||||
private const int Pow2WindowMaxExponent = (1 << Pow2WindowSizeBits) - 1;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetScalarBitLength(ulong value) => value == 0UL ? 0 : 64 - XMath.LeadingZeroCount(value);
|
||||
|
||||
public static readonly GpuUInt128 Zero = new();
|
||||
public static readonly GpuUInt128 One = new(1UL);
|
||||
|
||||
@@ -57,45 +60,52 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int GetBitLength()
|
||||
{
|
||||
if (High != 0UL)
|
||||
ulong temp = High;
|
||||
if (temp != 0UL)
|
||||
{
|
||||
return 64 + (64 - BitOperations.LeadingZeroCount(High));
|
||||
return 64 + (64 - BitOperations.LeadingZeroCount(temp));
|
||||
}
|
||||
|
||||
ulong low = Low;
|
||||
if (low == 0UL)
|
||||
temp = Low;
|
||||
if (temp == 0UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 64 - BitOperations.LeadingZeroCount(low);
|
||||
return 64 - BitOperations.LeadingZeroCount(temp);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int GetBitLength(GpuUInt128 value) => value.GetBitLength();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(UInt128 value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(ulong value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator GpuUInt128(ReadOnlyGpuUInt128 value) => new(value.High, value.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator UInt128(GpuUInt128 value) =>
|
||||
((UInt128)value.High << 64) | value.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ReadOnlyGpuUInt128 AsReadOnly() => new(this);
|
||||
|
||||
// Operators must return fresh instances so callers never rely on accidental in-place mutation. Prefer the mutating helpers when state updates are required.
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator +(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
left.Add(right);
|
||||
return left;
|
||||
GpuUInt128 result = new(left.High, left.Low);
|
||||
result.Add(right);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator -(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
left.Sub(right);
|
||||
return left;
|
||||
GpuUInt128 result = new(left.High, left.Low);
|
||||
result.Sub(right);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -106,61 +116,63 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
public static GpuUInt128 operator *(GpuUInt128 left, GpuUInt128 right)
|
||||
{
|
||||
Multiply(left, right, out var high, out var low);
|
||||
left.High = high;
|
||||
left.Low = low;
|
||||
return left;
|
||||
return new GpuUInt128(high, low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator <<(GpuUInt128 value, int shift)
|
||||
{
|
||||
shift &= 127;
|
||||
GpuUInt128 result = new(value.High, value.Low);
|
||||
if (shift == 0)
|
||||
{
|
||||
return value;
|
||||
return result;
|
||||
}
|
||||
|
||||
ulong high = value.High;
|
||||
ulong low = value.Low;
|
||||
ulong high = result.High;
|
||||
ulong low = result.Low;
|
||||
|
||||
int shiftedValue;
|
||||
if (shift >= 64)
|
||||
{
|
||||
int longShift = shift - 64;
|
||||
value.High = low << longShift;
|
||||
value.Low = 0UL;
|
||||
return value;
|
||||
shiftedValue = shift - 64;
|
||||
result.High = low << shiftedValue;
|
||||
result.Low = 0UL;
|
||||
return result;
|
||||
}
|
||||
|
||||
int inverseShift = 64 - shift;
|
||||
value.High = (high << shift) | (low >> inverseShift);
|
||||
value.Low = low << shift;
|
||||
return value;
|
||||
shiftedValue = 64 - shift;
|
||||
result.High = (high << shift) | (low >> shiftedValue);
|
||||
result.Low = low << shift;
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator >>(GpuUInt128 value, int shift)
|
||||
{
|
||||
shift &= 127;
|
||||
GpuUInt128 result = new(value.High, value.Low);
|
||||
if (shift == 0)
|
||||
{
|
||||
return value;
|
||||
return result;
|
||||
}
|
||||
|
||||
ulong high = value.High;
|
||||
ulong low = value.Low;
|
||||
ulong high = result.High;
|
||||
ulong low = result.Low;
|
||||
|
||||
int shiftedValue;
|
||||
if (shift >= 64)
|
||||
{
|
||||
int longShift = shift - 64;
|
||||
value.Low = longShift == 0 ? high : high >> longShift;
|
||||
value.High = 0UL;
|
||||
return value;
|
||||
shiftedValue = shift - 64;
|
||||
result.Low = shiftedValue == 0 ? high : high >> shiftedValue;
|
||||
result.High = 0UL;
|
||||
return result;
|
||||
}
|
||||
|
||||
int inverseShift = 64 - shift;
|
||||
value.Low = (low >> shift) | (high << inverseShift);
|
||||
value.High = high >> shift;
|
||||
return value;
|
||||
shiftedValue = 64 - shift;
|
||||
result.Low = (low >> shift) | (high << shiftedValue);
|
||||
result.High = high >> shift;
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -184,8 +196,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 operator ++(GpuUInt128 value)
|
||||
{
|
||||
value.Add(1UL);
|
||||
return value;
|
||||
GpuUInt128 result = new(value.High, value.Low);
|
||||
result.Add(1UL);
|
||||
return result;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -214,6 +227,33 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly int CompareTo(in ReadOnlyGpuUInt128 other)
|
||||
{
|
||||
if (High < other.High)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (High > other.High)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
ulong otherLow = other.Low;
|
||||
if (Low < otherLow)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Low > otherLow)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly bool Equals(GpuUInt128 other) => High == other.High && Low == other.Low;
|
||||
|
||||
@@ -224,25 +264,70 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
public override readonly int GetHashCode() => HashCode.Combine(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(GpuUInt128 other)
|
||||
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(in GpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddInternal(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
ulong originalLow = Low;
|
||||
ulong low = originalLow + other.Low;
|
||||
ulong carry = low < originalLow ? 1UL : 0UL;
|
||||
High = High + other.High + carry;
|
||||
// We're reusing originalLow for carry to avoid an extra local.
|
||||
ulong low = originalLow + otherLow;
|
||||
originalLow = low < originalLow ? 1UL : 0UL;
|
||||
Low = low;
|
||||
// We're reusing low for the final sum to avoid an extra local and to keep max stack frame at 3 instead of 4 with one statement.
|
||||
low = High + otherHigh;
|
||||
High = low + originalLow;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Add(ulong value)
|
||||
{
|
||||
ulong low = Low + value;
|
||||
High += (low < Low ? 1UL : 0UL);
|
||||
High += low < Low ? 1UL : 0UL;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void AddModInternal(ulong valueHigh, ulong valueLow, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
AddMod(valueLow, modulus.Low);
|
||||
return;
|
||||
}
|
||||
|
||||
AddInternal(valueHigh, valueLow);
|
||||
if (CompareTo(in modulus) >= 0)
|
||||
{
|
||||
Sub(in modulus);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(ulong value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -251,35 +336,20 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
Add(value);
|
||||
if (CompareTo(modulus) >= 0)
|
||||
if (CompareTo(in modulus) >= 0)
|
||||
{
|
||||
Sub(modulus);
|
||||
Sub(in modulus);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(ulong value, GpuUInt128 modulus)
|
||||
public static GpuUInt128 Pow2Minus1Mod(ulong exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
AddMod(value, modulus.Low);
|
||||
return;
|
||||
}
|
||||
|
||||
Add(value);
|
||||
if (CompareTo(modulus) >= 0)
|
||||
{
|
||||
Sub(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 Pow2Minus1Mod(ulong exponent, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.IsZero)
|
||||
{
|
||||
return Zero;
|
||||
}
|
||||
// This should never happen in production code
|
||||
// if (modulus.IsZero)
|
||||
// {
|
||||
// return Zero;
|
||||
// }
|
||||
|
||||
GpuUInt128 pow = Pow2Mod(exponent, modulus);
|
||||
if (pow.IsZero)
|
||||
@@ -292,7 +362,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void Pow2Minus1ModBatch(GpuUInt128 modulus, ReadOnlySpan<ulong> exponents, Span<GpuUInt128> results)
|
||||
public static void Pow2Minus1ModBatch(in ReadOnlyGpuUInt128 modulus, ReadOnlySpan<ulong> exponents, Span<GpuUInt128> results)
|
||||
{
|
||||
int count = exponents.Length;
|
||||
for (int i = 0; i < count; i++)
|
||||
@@ -302,7 +372,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(GpuUInt128 value, ulong modulus)
|
||||
public void AddMod(in ReadOnlyGpuUInt128 value, ulong modulus)
|
||||
{
|
||||
// TODO: Pre-reduce the operands via the Montgomery ladder used in MulMod64Benchmarks so the GPU
|
||||
// compatible shim stops paying for `%` on every call; the InlineUInt128 helper ran 6–82× faster on
|
||||
@@ -319,6 +389,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
Low = sum;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(in GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddMod(ulong value, ulong modulus)
|
||||
{
|
||||
@@ -338,57 +411,74 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(GpuUInt128 other)
|
||||
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Sub(in GpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SubInternal(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
ulong borrow = Low < other.Low ? 1UL : 0UL;
|
||||
High = High - other.High - borrow;
|
||||
Low -= other.Low;
|
||||
ulong borrow = Low < otherLow ? 1UL : 0UL;
|
||||
High = High - otherHigh - borrow;
|
||||
Low -= otherLow;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static ulong MulHigh(ulong x, ulong y)
|
||||
{
|
||||
// TODO: Review if we can reuse any other existing variables to reduce registry pressure.
|
||||
ulong xLow = (uint)x;
|
||||
ulong yLow = (uint)y;
|
||||
ulong lowProduct = xLow * yLow;
|
||||
|
||||
ulong yHigh = y >> 32;
|
||||
ulong cross = xLow * yHigh;
|
||||
// We're reusing xLow for the cross product to avoid an extra local.
|
||||
xLow *= yHigh;
|
||||
|
||||
ulong xHigh = x >> 32;
|
||||
ulong result = xHigh * yHigh;
|
||||
ulong temp = xHigh * yLow;
|
||||
// We're reusing yHigh for the result to void an extra local.
|
||||
yHigh *= xHigh;
|
||||
// We're reusing yLow for the other cross product to avoid an extra local.
|
||||
yLow *= xHigh;
|
||||
|
||||
// Keeping the wide partial sum in a dedicated local prevents the JIT from
|
||||
// materialising it on the stack before the final carry propagation. The
|
||||
// additional store looks redundant in C#, but it shortens the generated
|
||||
// instruction sequence by avoiding an extra temporary and results in a
|
||||
// measurable throughput win in the MulHigh benchmarks.
|
||||
result += cross >> 32;
|
||||
result += temp >> 32;
|
||||
yHigh += (xLow >> 32);
|
||||
// We've more instructions with this but the max method stack frame is 3 instead of 4.
|
||||
yHigh += (yLow >> 32);
|
||||
|
||||
lowProduct >>= 32;
|
||||
lowProduct += (uint)cross;
|
||||
lowProduct += (uint)temp;
|
||||
result += lowProduct >> 32;
|
||||
return result;
|
||||
lowProduct += (uint)xLow + (uint)yLow;
|
||||
yHigh += lowProduct >> 32;
|
||||
return yHigh;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul64(GpuUInt128 other)
|
||||
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul64(in GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
// Multiply this.Low (assumed 64-bit value) by full 128-bit other
|
||||
ulong operand = Low;
|
||||
Low = operand * other.Low;
|
||||
ulong highProduct = operand * other.High;
|
||||
High = highProduct + MulHigh(operand, other.Low);
|
||||
Low = operand * otherLow;
|
||||
ulong highProduct = operand * otherHigh;
|
||||
High = highProduct + MulHigh(operand, otherLow);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void MulMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
||||
// Kept by-value to coexist with the ref readonly overload below while still signaling read-only intent.
|
||||
public void MulMod(ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 multiplicand = this;
|
||||
GpuUInt128 multiplier = value;
|
||||
GpuUInt128 multiplier = value.ToMutable();
|
||||
|
||||
High = 0UL;
|
||||
Low = 0UL;
|
||||
@@ -411,25 +501,27 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 Pow2Mod(ulong exponent, GpuUInt128 modulus)
|
||||
public static GpuUInt128 Pow2Mod(ulong exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.IsZero || modulus == One)
|
||||
{
|
||||
return new();
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (modulus.IsZero || modulus == One)
|
||||
// {
|
||||
// return new();
|
||||
// }
|
||||
|
||||
if (exponent == 0UL)
|
||||
{
|
||||
return new GpuUInt128(1UL);
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (exponent == 0UL)
|
||||
// {
|
||||
// return new GpuUInt128(1UL);
|
||||
// }
|
||||
|
||||
Span<GpuUInt128> oddPowers = stackalloc GpuUInt128[Pow2WindowOddPowerCount];
|
||||
GpuUInt128 power = new GpuUInt128(1UL);
|
||||
GpuUInt128[] oddPowers = new GpuUInt128[PerfectNumberConstants.MaxOddPowersCount];
|
||||
GpuUInt128 power = new(1UL);
|
||||
int oddIndex = 0;
|
||||
|
||||
for (int bit = 1; bit <= Pow2WindowMaxExponent; bit++)
|
||||
{
|
||||
power += power;
|
||||
power.Add(power);
|
||||
if (power.CompareTo(modulus) >= 0)
|
||||
{
|
||||
power.Sub(modulus);
|
||||
@@ -437,12 +529,13 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
|
||||
if ((bit & 1) != 0)
|
||||
{
|
||||
oddPowers[oddIndex++] = power;
|
||||
oddPowers[oddIndex] = power;
|
||||
oddIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
GpuUInt128 result = new GpuUInt128(1UL);
|
||||
int bitLength = BitOperations.Log2(exponent) + 1;
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
int bitLength = GetScalarBitLength(exponent);
|
||||
int index = bitLength - 1;
|
||||
|
||||
while (index >= 0)
|
||||
@@ -484,7 +577,18 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
return result;
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int TrailingZeroCount(GpuUInt128 value)
|
||||
public static int TrailingZeroCount(in ReadOnlyGpuUInt128 value)
|
||||
{
|
||||
if (value.Low != 0UL)
|
||||
{
|
||||
return BitOperations.TrailingZeroCount(value.Low);
|
||||
}
|
||||
|
||||
return 64 + BitOperations.TrailingZeroCount(value.High);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static int TrailingZeroCount(in GpuUInt128 value)
|
||||
{
|
||||
if (value.Low != 0UL)
|
||||
{
|
||||
@@ -539,21 +643,66 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
||||
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
||||
{
|
||||
if (CompareTo(value) >= 0)
|
||||
{
|
||||
Sub(value);
|
||||
}
|
||||
else
|
||||
{
|
||||
Add(modulus);
|
||||
Sub(value);
|
||||
}
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(ulong value, GpuUInt128 modulus)
|
||||
public void SubMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void SubModInternal(ulong valueHigh, ulong valueLow, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (CompareToRaw(valueHigh, valueLow) >= 0)
|
||||
{
|
||||
SubInternal(valueHigh, valueLow);
|
||||
return;
|
||||
}
|
||||
|
||||
Add(in modulus);
|
||||
SubInternal(valueHigh, valueLow);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private readonly int CompareToRaw(ulong otherHigh, ulong otherLow)
|
||||
{
|
||||
if (High < otherHigh)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (High > otherHigh)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Low < otherLow)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (Low > otherLow)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SubMod(ulong value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
ulong low = Low;
|
||||
ulong high = High;
|
||||
@@ -569,9 +718,20 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul(GpuUInt128 other)
|
||||
public void Mul(in ReadOnlyGpuUInt128 other)
|
||||
{
|
||||
Multiply(this, other, out var high, out var low);
|
||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||
Multiply(readOnlyThis, other, out var high, out var low);
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Mul(in GpuUInt128 other)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
||||
Multiply(readOnlyThis, readOnlyOther, out var high, out var low);
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
@@ -585,14 +745,15 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static GpuUInt128 DivideExact(GpuUInt128 dividend, GpuUInt128 divisor)
|
||||
public static GpuUInt128 DivideExact(in ReadOnlyGpuUInt128 dividend, in ReadOnlyGpuUInt128 divisor)
|
||||
{
|
||||
int divisorBits = divisor.GetBitLength();
|
||||
int shift = dividend.GetBitLength() - divisorBits;
|
||||
|
||||
GpuUInt128 quotient = Zero;
|
||||
GpuUInt128 remainder = dividend;
|
||||
GpuUInt128 shiftedDivisor = divisor << shift;
|
||||
GpuUInt128 shiftedDivisor = divisor;
|
||||
shiftedDivisor.ShiftLeft(shift);
|
||||
GpuUInt128 quotientBit = One << shift;
|
||||
|
||||
while (true)
|
||||
@@ -622,7 +783,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void Xor(GpuUInt128 other)
|
||||
public void Xor(in ReadOnlyGpuUInt128 other)
|
||||
{
|
||||
High ^= other.High;
|
||||
Low ^= other.Low;
|
||||
@@ -682,7 +843,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
High = high >> shift;
|
||||
}
|
||||
|
||||
public void MulMod(GpuUInt128 other, GpuUInt128 modulus)
|
||||
public void MulMod(in ReadOnlyGpuUInt128 other, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -713,7 +874,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
/// method is intended for validation only and should not be used inside
|
||||
/// GPU kernels.
|
||||
/// </summary>
|
||||
internal void MulModBigInteger(GpuUInt128 other, GpuUInt128 modulus)
|
||||
internal void MulModBigInteger(in ReadOnlyGpuUInt128 other, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
var left = (BigInteger)(UInt128)this;
|
||||
var right = (BigInteger)(UInt128)other;
|
||||
@@ -728,7 +889,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
/// repeated subtractions and becomes extremely slow for large remainders.
|
||||
/// Kept for future optimization work.
|
||||
/// </summary>
|
||||
internal void MulModByLimb(GpuUInt128 other, GpuUInt128 modulus)
|
||||
internal void MulModByLimb(in ReadOnlyGpuUInt128 other, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
// TODO: Relocate this limb-based reducer to the benchmark project once the production
|
||||
// pipeline switches to the faster allocating legacy path demonstrated in the benchmarks.
|
||||
@@ -757,7 +918,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void MulMod(ulong value, GpuUInt128 modulus)
|
||||
public void MulMod(ulong value, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -771,7 +932,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ulong MulMod(GpuUInt128 other, ulong modulus)
|
||||
public readonly ulong MulMod(in ReadOnlyGpuUInt128 other, ulong modulus)
|
||||
{
|
||||
ulong modulusLocal = modulus;
|
||||
ulong a = Low % modulusLocal;
|
||||
@@ -855,7 +1016,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void ReduceProductBitwise(ulong p3, ulong p2, ulong p1, ulong p0, GpuUInt128 modulus)
|
||||
private void ReduceProductBitwise(ulong p3, ulong p2, ulong p1, ulong p0, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
GpuUInt128 remainder = new();
|
||||
int bit;
|
||||
@@ -945,7 +1106,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public readonly ulong MulModMontgomery64(GpuUInt128 other, ulong modulus, ulong nPrime, ulong r2)
|
||||
public readonly ulong MulModMontgomery64(in ReadOnlyGpuUInt128 other, ulong modulus, ulong nPrime, ulong r2)
|
||||
{
|
||||
// TODO: Retire this struct-based Montgomery path from production after adopting the extension
|
||||
// helper, which benchmarks 6-7× faster across dense and near-modulus operands.
|
||||
@@ -984,7 +1145,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SquareMod(GpuUInt128 modulus)
|
||||
public void SquareMod(in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -1043,7 +1204,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
Low = remainder.Low;
|
||||
}
|
||||
|
||||
public void ModPow(GpuUInt128 exponent, GpuUInt128 modulus)
|
||||
public void ModPow(GpuUInt128 exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
// TODO: Swap this copy-heavy path for the pooled base/exponent ladder used in
|
||||
// GpuUInt128MulModBenchmarks so the GPU shim keeps reusing buffers instead of
|
||||
@@ -1069,7 +1230,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
Low = result.Low;
|
||||
}
|
||||
|
||||
public void ModPow(ulong exponent, GpuUInt128 modulus)
|
||||
public void ModPow(ulong exponent, in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
// TODO: Share the pooled ladder state from GpuUInt128MulModBenchmarks here as well so
|
||||
// Lucas–Lehmer batches avoid constructing throwaway temporaries on every exponent.
|
||||
@@ -1103,7 +1264,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void ModInv(GpuUInt128 modulus)
|
||||
public void ModInv(in ReadOnlyGpuUInt128 modulus)
|
||||
{
|
||||
if (modulus.High == 0UL)
|
||||
{
|
||||
@@ -1121,7 +1282,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
((UInt128)this).ToString();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void MultiplyFull(GpuUInt128 left, GpuUInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
private static void MultiplyFull(in ReadOnlyGpuUInt128 left, in ReadOnlyGpuUInt128 right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
// TODO: Reuse variables to reduce register pressure following the fused-limb layout
|
||||
// from GpuUInt128MulModByLimbBenchmarks so the multiply helper matches the fastest
|
||||
@@ -1167,7 +1328,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void MultiplyFull(GpuUInt128 left, ulong right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
private static void MultiplyFull(in ReadOnlyGpuUInt128 left, ulong right, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
var (highLow, lowLow) = Mul64(left.Low, right);
|
||||
var (highHigh, lowHigh) = Mul64(left.High, right);
|
||||
@@ -1184,7 +1345,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Multiply(GpuUInt128 left, GpuUInt128 right, out ulong high, out ulong low)
|
||||
private static void Multiply(in ReadOnlyGpuUInt128 left, in ReadOnlyGpuUInt128 right, out ulong high, out ulong low)
|
||||
{
|
||||
// TODO: Reuse variables to reduce register pressure.
|
||||
var (h0, l0) = Mul64(left.Low, right.Low);
|
||||
@@ -1219,7 +1380,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void SquareFull(GpuUInt128 value, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
private static void SquareFull(in ReadOnlyGpuUInt128 value, out ulong p3, out ulong p2, out ulong p1, out ulong p0)
|
||||
{
|
||||
// TODO: Reuse variables to reduce register pressure.
|
||||
// Compute (H*2^64 + L)^2 = H^2*2^128 + 2*H*L*2^64 + L^2
|
||||
@@ -1457,14 +1618,14 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Check if the TODO below is still relevant.
|
||||
// TODO: Check if the TODO below is still relevant.
|
||||
|
||||
// TODO(MOD-OPT): Montgomery/Barrett integration plan
|
||||
// - Introduce caches of modulus-dependent constants:
|
||||
// * Montgomery: n' (-(n^{-1}) mod 2^64 or 2^128), R2 = (R^2 mod n)
|
||||
// * Barrett: mu = floor(2^k / n) for k ∈ {128, 192, 256}
|
||||
// - Add fast-path for 64-bit NTT primes (modulus.High == 0UL) using pure 64-bit ops.
|
||||
// - Expose helpers to retrieve/calc constants once per modulus and reuse in kernels.
|
||||
// - Wire these into MulMod and SquareMod hot paths under feature toggles.
|
||||
// - Ensure ILGPU compatibility (no BigInteger, no % inside kernels).
|
||||
// TODO(MOD-OPT): Montgomery/Barrett integration plan
|
||||
// - Introduce caches of modulus-dependent constants:
|
||||
// * Montgomery: n' (-(n^{-1}) mod 2^64 or 2^128), R2 = (R^2 mod n)
|
||||
// * Barrett: mu = floor(2^k / n) for k ∈ {128, 192, 256}
|
||||
// - Add fast-path for 64-bit NTT primes (modulus.High == 0UL) using pure 64-bit ops.
|
||||
// - Expose helpers to retrieve/calc constants once per modulus and reuse in kernels.
|
||||
// - Wire these into MulMod and SquareMod hot paths under feature toggles.
|
||||
// - Ensure ILGPU compatibility (no BigInteger, no % inside kernels).
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Numerics;
|
||||
@@ -13,8 +12,13 @@ using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
/// <summary>
|
||||
/// Scans Mersenne divisors on the GPU for prime exponents p >= 31 using per-divisor Montgomery data.
|
||||
/// </summary>
|
||||
public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
private const int RemainderSlotCount = 4;
|
||||
|
||||
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
||||
private readonly object _sync = new();
|
||||
private ulong _divisorLimit;
|
||||
@@ -24,6 +28,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>> _kernelExponentCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte>> _remainderDeltaKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte>> _remainderScanKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>>> _candidateMaskKernelCache = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<BatchResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
|
||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
||||
|
||||
@@ -41,6 +46,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
accelerator,
|
||||
acc => acc.LoadStreamKernel<Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte>(AccumulateRemaindersKernel));
|
||||
|
||||
private Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> GetCandidateMaskKernel(Accelerator accelerator) =>
|
||||
_candidateMaskKernelCache.GetOrAdd(
|
||||
accelerator,
|
||||
acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>>(EvaluateCandidateMaskKernel));
|
||||
|
||||
public int GpuBatchSize
|
||||
{
|
||||
get => _gpuBatchSize;
|
||||
@@ -109,10 +119,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
resources.RemainderBuffer,
|
||||
resources.RemainderDeltaKernel,
|
||||
resources.RemainderScanKernel,
|
||||
resources.CandidateMaskKernel,
|
||||
resources.Divisors,
|
||||
resources.Exponents,
|
||||
resources.Hits,
|
||||
resources.DivisorData,
|
||||
resources.CycleCandidates,
|
||||
resources.CycleLengths,
|
||||
resources.CycleCapacity,
|
||||
out lastProcessed,
|
||||
out coveredRange,
|
||||
out processedCount
|
||||
@@ -172,10 +186,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
MemoryBuffer1D<byte, Stride1D.Dense> remainderBuffer,
|
||||
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> remainderDeltaKernel,
|
||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> remainderScanKernel,
|
||||
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> candidateMaskKernel,
|
||||
in ulong[] divisors,
|
||||
ulong[] exponents,
|
||||
byte[] hits,
|
||||
MontgomeryDivisorData[] divisorData,
|
||||
ulong[] cycleCandidates,
|
||||
ulong[] cycleLengths,
|
||||
int cycleCapacity,
|
||||
out ulong lastProcessed,
|
||||
out bool coveredRange,
|
||||
out ulong processedCount)
|
||||
@@ -194,27 +212,22 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
||||
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||
|
||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||
int cycleBatchCapacity = Math.Min(batchCapacity, cycleCache.PreferredBatchSize);
|
||||
int maxKernelGroupSize = (int)accelerator.MaxNumThreadsPerGroup;
|
||||
if (cycleBatchCapacity > maxKernelGroupSize)
|
||||
cycleCapacity = Math.Max(1, cycleCapacity);
|
||||
|
||||
int chunkCapacity = Math.Min(batchCapacity, cycleCapacity);
|
||||
int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup;
|
||||
if (maxThreadsPerGroup > 0)
|
||||
{
|
||||
cycleBatchCapacity = maxKernelGroupSize;
|
||||
chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup);
|
||||
}
|
||||
|
||||
if (cycleBatchCapacity <= 0)
|
||||
if (chunkCapacity <= 0)
|
||||
{
|
||||
cycleBatchCapacity = batchCapacity;
|
||||
chunkCapacity = cycleCapacity;
|
||||
}
|
||||
|
||||
ulong[] candidateDivisors = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] filteredCycles = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder10Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder8Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder5Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
byte[] remainder3Buffer = ArrayPool<byte>.Shared.Rent(cycleBatchCapacity);
|
||||
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||
|
||||
UInt128 twoP128 = (UInt128)prime << 1;
|
||||
if (twoP128 == UInt128.Zero)
|
||||
@@ -252,12 +265,20 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
byte remainder5 = (byte)((ulong)(currentDivisor128 % 5UL));
|
||||
byte remainder3 = (byte)((ulong)(currentDivisor128 % 3UL));
|
||||
bool lastIsSeven = (prime & 3UL) == 3UL;
|
||||
byte lastIsSevenFlag = lastIsSeven ? (byte)1 : (byte)0;
|
||||
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||
ArrayView1D<byte, Stride1D.Dense> remainderBaseView = remainderBuffer.View;
|
||||
int remainderStride = (int)divisorDeltaBuffer.Length;
|
||||
if (remainderStride <= 0)
|
||||
{
|
||||
remainderStride = chunkCapacity;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
while (currentK <= maxK)
|
||||
{
|
||||
int chunkCount = Math.Min(cycleBatchCapacity, batchCapacity);
|
||||
int chunkCount = Math.Min(chunkCapacity, batchCapacity);
|
||||
ulong remainingK = maxK - currentK + 1UL;
|
||||
if ((ulong)chunkCount > remainingK)
|
||||
{
|
||||
@@ -269,12 +290,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
break;
|
||||
}
|
||||
|
||||
Span<ulong> candidateSpan = candidateDivisors.AsSpan(0, chunkCount);
|
||||
Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount);
|
||||
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
|
||||
Span<byte> rem10Span = remainder10Buffer.AsSpan(0, chunkCount);
|
||||
Span<byte> rem8Span = remainder8Buffer.AsSpan(0, chunkCount);
|
||||
Span<byte> rem5Span = remainder5Buffer.AsSpan(0, chunkCount);
|
||||
Span<byte> rem3Span = remainder3Buffer.AsSpan(0, chunkCount);
|
||||
// Reuse the hits array as temporary storage for the candidate mask before it stores final GPU hit flags.
|
||||
Span<byte> maskSpan = hits.AsSpan(0, chunkCount);
|
||||
|
||||
UInt128 localDivisor = currentDivisor128;
|
||||
|
||||
@@ -299,49 +318,37 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
ArrayView1D<ulong, Stride1D.Dense> gapView = divisorDeltaBuffer.View.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> deltaView = remainderDeltaBuffer.View.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainderView = remainderBuffer.View.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder10View = remainderBaseView.SubView(0, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder8View = remainderBaseView.SubView(remainderStride, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder5View = remainderBaseView.SubView(remainderStride * 2, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder3View = remainderBaseView.SubView(remainderStride * 3, chunkCount);
|
||||
ArrayView1D<byte, Stride1D.Dense> maskView = hitsBuffer.View.SubView(0, chunkCount);
|
||||
|
||||
gapView.CopyFromCPU(ref MemoryMarshal.GetReference(gapSpan), chunkCount);
|
||||
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder10, 10, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem10Span);
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder8, 8, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem8Span);
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder5, 5, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem5Span);
|
||||
ComputeRemaindersOnGpu(accelerator, chunkCount, remainder3, 3, gapView, deltaView, remainderView, remainderDeltaKernel, remainderScanKernel, rem3Span);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel);
|
||||
ComputeRemaindersOnGpu(chunkCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel);
|
||||
|
||||
remainder10 = AddMod(rem10Span[chunkCount - 1], step10, 10);
|
||||
remainder8 = AddMod(rem8Span[chunkCount - 1], step8, 8);
|
||||
remainder5 = AddMod(rem5Span[chunkCount - 1], step5, 5);
|
||||
remainder3 = AddMod(rem3Span[chunkCount - 1], step3, 3);
|
||||
BuildCandidateMaskOnGpu(chunkCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, maskSpan, candidateMaskKernel);
|
||||
|
||||
byte lastRemainder10 = FetchLastRemainder(remainder10View, chunkCount);
|
||||
byte lastRemainder8 = FetchLastRemainder(remainder8View, chunkCount);
|
||||
byte lastRemainder5 = FetchLastRemainder(remainder5View, chunkCount);
|
||||
byte lastRemainder3 = FetchLastRemainder(remainder3View, chunkCount);
|
||||
|
||||
remainder10 = AddMod(lastRemainder10, step10, 10);
|
||||
remainder8 = AddMod(lastRemainder8, step8, 8);
|
||||
remainder5 = AddMod(lastRemainder5, step5, 5);
|
||||
remainder3 = AddMod(lastRemainder3, step3, 3);
|
||||
|
||||
int filteredCount = 0;
|
||||
Span<ulong> filteredDivisorsSpan = filteredDivisors.AsSpan(0, chunkCount);
|
||||
Span<ulong> filteredCyclesSpan = filteredCycles.AsSpan(0, chunkCount);
|
||||
|
||||
for (int i = 0; i < chunkCount; i++)
|
||||
{
|
||||
byte r10 = rem10Span[i];
|
||||
if (lastIsSeven)
|
||||
{
|
||||
if (r10 != 3 && r10 != 7 && r10 != 9)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (r10 != 1 && r10 != 3 && r10 != 7 && r10 != 9)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
byte r8 = rem8Span[i];
|
||||
if (r8 != 1 && r8 != 7)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rem3Span[i] == 0 || rem5Span[i] == 0)
|
||||
if (maskSpan[i] == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -357,70 +364,47 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
continue;
|
||||
}
|
||||
|
||||
cycleCache.GetCycleLengths(filteredDivisorsSpan[..filteredCount], filteredCyclesSpan[..filteredCount]);
|
||||
Span<ulong> cycleSpan = cycleLengths.AsSpan(0, filteredCount);
|
||||
cycleCache.GetCycleLengths(filteredDivisorsSpan[..filteredCount], cycleSpan);
|
||||
|
||||
int batchSize = 0;
|
||||
int admissibleCount = 0;
|
||||
for (int i = 0; i < filteredCount; i++)
|
||||
{
|
||||
ulong divisorValue = filteredDivisorsSpan[i];
|
||||
ulong cycleLength = filteredCyclesSpan[i];
|
||||
|
||||
bool includeDivisor = true;
|
||||
ulong exponent = prime;
|
||||
|
||||
if (cycleLength > 0UL)
|
||||
if (cycleSpan[i] == prime)
|
||||
{
|
||||
ulong remainder = prime;
|
||||
if (remainder >= cycleLength)
|
||||
{
|
||||
remainder %= cycleLength;
|
||||
}
|
||||
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
includeDivisor = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponent = 0UL;
|
||||
}
|
||||
filteredDivisorsSpan[admissibleCount++] = filteredDivisorsSpan[i];
|
||||
}
|
||||
|
||||
if (!includeDivisor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
divisors[batchSize] = divisorValue;
|
||||
exponents[batchSize] = exponent;
|
||||
batchSize++;
|
||||
}
|
||||
|
||||
if (batchSize == 0)
|
||||
if (admissibleCount == 0)
|
||||
{
|
||||
processedAll = currentK > maxK;
|
||||
continue;
|
||||
}
|
||||
|
||||
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
||||
divisorSpan = divisors.AsSpan(0, batchSize);
|
||||
exponentSpan = exponents.AsSpan(0, batchSize);
|
||||
hitsSpan = hits.AsSpan(0, batchSize);
|
||||
divisorView = divisorsBuffer.View.SubView(0, batchSize);
|
||||
exponentView = exponentBuffer.View.SubView(0, batchSize);
|
||||
hitsView = hitsBuffer.View.SubView(0, batchSize);
|
||||
divisorDataSpan = divisorData.AsSpan(0, admissibleCount);
|
||||
divisorSpan = divisors.AsSpan(0, admissibleCount);
|
||||
exponentSpan = exponents.AsSpan(0, admissibleCount);
|
||||
// Reassign the hits span to store Montgomery kernel results after the mask data has been consumed.
|
||||
hitsSpan = hits.AsSpan(0, admissibleCount);
|
||||
divisorView = divisorsBuffer.View.SubView(0, admissibleCount);
|
||||
exponentView = exponentBuffer.View.SubView(0, admissibleCount);
|
||||
hitsView = hitsBuffer.View.SubView(0, admissibleCount);
|
||||
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
for (int i = 0; i < admissibleCount; i++)
|
||||
{
|
||||
divisorDataSpan[i] = MontgomeryDivisorDataCache.Get(divisorSpan[i]);
|
||||
ulong divisorValue = filteredDivisorsSpan[i];
|
||||
divisorSpan[i] = divisorValue;
|
||||
exponentSpan[i] = prime;
|
||||
divisorDataSpan[i] = MontgomeryDivisorData.FromModulus(divisorValue);
|
||||
}
|
||||
|
||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), batchSize);
|
||||
kernel(batchSize, divisorView, exponentView, hitsView);
|
||||
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), admissibleCount);
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), admissibleCount);
|
||||
kernel(admissibleCount, divisorView, exponentView, hitsView);
|
||||
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), admissibleCount);
|
||||
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
for (int i = 0; i < admissibleCount; i++)
|
||||
{
|
||||
if (hitsSpan[i] != 0)
|
||||
{
|
||||
@@ -432,7 +416,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
if (!composite)
|
||||
{
|
||||
lastProcessed = divisorSpan[batchSize - 1];
|
||||
lastProcessed = divisorSpan[admissibleCount - 1];
|
||||
}
|
||||
|
||||
if (composite)
|
||||
@@ -445,14 +429,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(candidateDivisors, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(filteredDivisors, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(filteredCycles, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(divisorGaps, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder10Buffer, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder8Buffer, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder5Buffer, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(remainder3Buffer, clearArray: false);
|
||||
}
|
||||
|
||||
coveredRange = composite || processedAll || (currentDivisor128 > allowedMax128);
|
||||
@@ -460,7 +438,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void ComputeRemaindersOnGpu(
|
||||
Accelerator accelerator,
|
||||
int count,
|
||||
byte baseRemainder,
|
||||
byte modulus,
|
||||
@@ -468,8 +445,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
ArrayView1D<byte, Stride1D.Dense> deltaView,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainderView,
|
||||
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
|
||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel,
|
||||
Span<byte> destination)
|
||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
@@ -483,11 +459,42 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig);
|
||||
|
||||
scanKernel(kernelConfig, new Index1D(count), deltaView, remainderView, baseRemainder, modulus);
|
||||
accelerator.Synchronize();
|
||||
remainderView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void BuildCandidateMaskOnGpu(
|
||||
int count,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder10View,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder8View,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder5View,
|
||||
ArrayView1D<byte, Stride1D.Dense> remainder3View,
|
||||
byte lastIsSevenFlag,
|
||||
ArrayView1D<byte, Stride1D.Dense> maskView,
|
||||
Span<byte> destination,
|
||||
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
destination.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView);
|
||||
maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
||||
}
|
||||
|
||||
private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
byte value = 0;
|
||||
remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1);
|
||||
return value;
|
||||
}
|
||||
|
||||
private static byte AddMod(byte value, byte delta, byte modulus)
|
||||
{
|
||||
int result = value + delta;
|
||||
@@ -556,8 +563,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer;
|
||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _resultsBuffer;
|
||||
private ulong[] _hostBuffer;
|
||||
private int[]? _positionBuffer;
|
||||
private Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? _factorCache;
|
||||
private int _capacity;
|
||||
private bool _disposed;
|
||||
|
||||
@@ -571,169 +576,54 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
_exponentsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||
_resultsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||
_positionBuffer = null;
|
||||
}
|
||||
|
||||
internal void Reset()
|
||||
{
|
||||
_disposed = false;
|
||||
_factorCache?.Clear();
|
||||
}
|
||||
|
||||
public void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
|
||||
{
|
||||
if (_disposed)
|
||||
{
|
||||
throw new ObjectDisposedException(nameof(DivisorScanSession));
|
||||
}
|
||||
|
||||
int primesLength = primes.Length;
|
||||
if (primesLength == 0)
|
||||
int length = primes.Length;
|
||||
if (length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||
var exponentStepper = new ExponentRemainderStepper(divisorData);
|
||||
if (!exponentStepper.IsValidModulus)
|
||||
if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL)
|
||||
{
|
||||
hits.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry> cache = _factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(16);
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(divisor, primes[0], cache, out divisorCycle) || divisorCycle == 0UL)
|
||||
{
|
||||
divisorCycle = DivisorCycleCache.Shared.GetCycleLength(divisor);
|
||||
if (divisorCycle == 0UL)
|
||||
{
|
||||
hits.Clear();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var cycleStepper = new CycleRemainderStepper(divisorCycle);
|
||||
ulong initialCycleRemainder = cycleStepper.Initialize(primes[0]);
|
||||
ProcessWithCycle(ref exponentStepper, ref cycleStepper, initialCycleRemainder, divisorCycle, divisorData, primes, hits);
|
||||
}
|
||||
|
||||
|
||||
private void ProcessWithCycle(
|
||||
ref ExponentRemainderStepper exponentStepper,
|
||||
ref CycleRemainderStepper cycleStepper,
|
||||
ulong initialCycleRemainder,
|
||||
ulong divisorCycle,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
ReadOnlySpan<ulong> primes,
|
||||
Span<byte> hits)
|
||||
{
|
||||
Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel;
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentsView = _exponentsBuffer.View;
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultsView = _resultsBuffer.View;
|
||||
int capacity = _capacity;
|
||||
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, capacity);
|
||||
int[]? positionBuffer = _positionBuffer;
|
||||
if (positionBuffer is null)
|
||||
{
|
||||
// Session construction already enforces the batch capacity, so we only
|
||||
// need to rent the position buffer on the first cycle-enabled invocation.
|
||||
positionBuffer = ArrayPool<int>.Shared.Rent(capacity);
|
||||
_positionBuffer = positionBuffer;
|
||||
}
|
||||
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, _capacity);
|
||||
|
||||
Span<int> positionSpan = positionBuffer.AsSpan(0, capacity);
|
||||
|
||||
int gpuBatchSize = _owner._gpuBatchSize;
|
||||
int primesLength = primes.Length;
|
||||
int offset = 0;
|
||||
bool cycleRemainderPending = true;
|
||||
|
||||
while (offset < primesLength)
|
||||
while (offset < length)
|
||||
{
|
||||
int batchSize = Math.Min(gpuBatchSize, primesLength - offset);
|
||||
ReadOnlySpan<ulong> primesSlice = primes.Slice(offset, batchSize);
|
||||
Span<byte> hitsSlice = hits.Slice(offset, batchSize);
|
||||
int batchSize = Math.Min(_capacity, length - offset);
|
||||
ReadOnlySpan<ulong> batch = primes.Slice(offset, batchSize);
|
||||
batch.CopyTo(hostSpan);
|
||||
|
||||
int computeCount = 0;
|
||||
bool hasState = exponentStepper.HasState;
|
||||
ulong previousExponent = exponentStepper.PreviousExponent;
|
||||
bool useInitialRemainder = cycleRemainderPending;
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentSlice = exponentsView.SubView(0, batchSize);
|
||||
exponentSlice.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), batchSize);
|
||||
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultSlice = resultsView.SubView(0, batchSize);
|
||||
_kernel(batchSize, divisorData, exponentSlice, resultSlice);
|
||||
resultSlice.CopyToCPU(ref MemoryMarshal.GetReference(hostSpan), batchSize);
|
||||
|
||||
Span<byte> hitSlice = hits.Slice(offset, batchSize);
|
||||
for (int i = 0; i < batchSize; i++)
|
||||
{
|
||||
hitsSlice[i] = 0;
|
||||
ulong primeValue = primesSlice[i];
|
||||
|
||||
ulong remainder = useInitialRemainder
|
||||
? initialCycleRemainder
|
||||
: cycleStepper.ComputeNext(primeValue);
|
||||
useInitialRemainder = false;
|
||||
|
||||
if (remainder != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ulong workValue;
|
||||
if (!hasState)
|
||||
{
|
||||
workValue = primeValue;
|
||||
hasState = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
workValue = primeValue - previousExponent;
|
||||
}
|
||||
|
||||
previousExponent = primeValue;
|
||||
|
||||
if (workValue >= divisorCycle)
|
||||
{
|
||||
// TODO: Replace this `%` with the divisor-cycle remainder helper so we reuse cached
|
||||
// modulo results instead of recomputing them in the GPU staging loop.
|
||||
workValue %= divisorCycle;
|
||||
}
|
||||
|
||||
hostSpan[computeCount] = workValue;
|
||||
positionSpan[computeCount] = i;
|
||||
computeCount++;
|
||||
}
|
||||
|
||||
cycleRemainderPending = useInitialRemainder;
|
||||
|
||||
if (computeCount > 0)
|
||||
{
|
||||
Span<ulong> exponentSlice = hostSpan[..computeCount];
|
||||
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentsView.SubView(0, computeCount);
|
||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||
|
||||
ArrayView1D<ulong, Stride1D.Dense> resultView = resultsView.SubView(0, computeCount);
|
||||
kernel(computeCount, divisorData, exponentView, resultView);
|
||||
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||
|
||||
bool requiresInitialization = !exponentStepper.HasState;
|
||||
for (int i = 0; i < computeCount; i++)
|
||||
{
|
||||
int position = positionSpan[i];
|
||||
ulong primeValue = primesSlice[position];
|
||||
ulong montgomeryResult = exponentSlice[i];
|
||||
bool isUnity;
|
||||
|
||||
if (requiresInitialization)
|
||||
{
|
||||
exponentStepper.TryInitializeFromMontgomeryResult(primeValue, montgomeryResult, out isUnity);
|
||||
requiresInitialization = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
exponentStepper.TryAdvanceWithMontgomeryDelta(primeValue, montgomeryResult, out isUnity);
|
||||
}
|
||||
|
||||
hitsSlice[position] = isUnity ? (byte)1 : (byte)0;
|
||||
}
|
||||
hitSlice[i] = hostSpan[i] == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
|
||||
offset += batchSize;
|
||||
@@ -748,11 +638,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
}
|
||||
|
||||
_disposed = true;
|
||||
_factorCache?.Clear();
|
||||
_owner._sessionPool.Add(this);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ComputeRemainderDeltasKernel(Index1D index, ArrayView<ulong> gaps, ArrayView<byte> deltas, byte modulus)
|
||||
{
|
||||
deltas[index] = (byte)(gaps[index] % modulus);
|
||||
@@ -822,6 +710,55 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
remainders[globalIndex] = (byte)remainder;
|
||||
}
|
||||
|
||||
private static void EvaluateCandidateMaskKernel(
|
||||
Index1D index,
|
||||
ArrayView<byte> remainder10,
|
||||
ArrayView<byte> remainder8,
|
||||
ArrayView<byte> remainder5,
|
||||
ArrayView<byte> remainder3,
|
||||
byte lastIsSevenFlag,
|
||||
ArrayView<byte> mask)
|
||||
{
|
||||
int globalIndex = index;
|
||||
int length = (int)mask.Length;
|
||||
if (globalIndex >= length)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
byte value10 = remainder10[globalIndex];
|
||||
bool accept10;
|
||||
if (lastIsSevenFlag != 0)
|
||||
{
|
||||
accept10 = value10 == 3 || value10 == 7 || value10 == 9;
|
||||
}
|
||||
else
|
||||
{
|
||||
accept10 = value10 == 1 || value10 == 3 || value10 == 7 || value10 == 9;
|
||||
}
|
||||
|
||||
if (!accept10)
|
||||
{
|
||||
mask[globalIndex] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
byte value8 = remainder8[globalIndex];
|
||||
if (value8 != 1 && value8 != 7)
|
||||
{
|
||||
mask[globalIndex] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (remainder3[globalIndex] == 0 || remainder5[globalIndex] == 0)
|
||||
{
|
||||
mask[globalIndex] = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
mask[globalIndex] = 1;
|
||||
}
|
||||
|
||||
private static void ComputeMontgomeryExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
||||
{
|
||||
ulong modulus = divisor.Modulus;
|
||||
@@ -869,19 +806,31 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
{
|
||||
internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity)
|
||||
{
|
||||
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(capacity);
|
||||
ExponentBuffer = accelerator.Allocate1D<ulong>(capacity);
|
||||
HitsBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
DivisorDeltaBuffer = accelerator.Allocate1D<ulong>(capacity);
|
||||
RemainderDeltaBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
RemainderBuffer = accelerator.Allocate1D<byte>(capacity);
|
||||
Divisors = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||
Exponents = ArrayPool<ulong>.Shared.Rent(capacity);
|
||||
Hits = ArrayPool<byte>.Shared.Rent(capacity);
|
||||
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(capacity);
|
||||
int cycleCapacity = DivisorCycleCache.Shared.PreferredBatchSize;
|
||||
if (cycleCapacity <= 0)
|
||||
{
|
||||
cycleCapacity = 1;
|
||||
}
|
||||
|
||||
int actualCapacity = Math.Max(capacity, cycleCapacity);
|
||||
|
||||
DivisorsBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(actualCapacity);
|
||||
ExponentBuffer = accelerator.Allocate1D<ulong>(actualCapacity);
|
||||
HitsBuffer = accelerator.Allocate1D<byte>(actualCapacity);
|
||||
DivisorDeltaBuffer = accelerator.Allocate1D<ulong>(actualCapacity);
|
||||
RemainderDeltaBuffer = accelerator.Allocate1D<byte>(actualCapacity);
|
||||
RemainderBuffer = accelerator.Allocate1D<byte>(actualCapacity * RemainderSlotCount);
|
||||
Divisors = ArrayPool<ulong>.Shared.Rent(actualCapacity);
|
||||
Exponents = ArrayPool<ulong>.Shared.Rent(actualCapacity);
|
||||
Hits = ArrayPool<byte>.Shared.Rent(actualCapacity);
|
||||
DivisorData = ArrayPool<MontgomeryDivisorData>.Shared.Rent(actualCapacity);
|
||||
CycleCandidates = ArrayPool<ulong>.Shared.Rent(cycleCapacity);
|
||||
CycleLengths = ArrayPool<ulong>.Shared.Rent(cycleCapacity);
|
||||
RemainderDeltaKernel = owner.GetRemainderDeltaKernel(accelerator);
|
||||
RemainderScanKernel = owner.GetRemainderScanKernel(accelerator);
|
||||
Capacity = capacity;
|
||||
CandidateMaskKernel = owner.GetCandidateMaskKernel(accelerator);
|
||||
Capacity = actualCapacity;
|
||||
CycleCapacity = cycleCapacity;
|
||||
}
|
||||
|
||||
internal readonly MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> DivisorsBuffer;
|
||||
@@ -904,12 +853,19 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
|
||||
internal readonly MontgomeryDivisorData[] DivisorData;
|
||||
|
||||
internal readonly ulong[] CycleCandidates;
|
||||
|
||||
internal readonly ulong[] CycleLengths;
|
||||
|
||||
internal readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> RemainderDeltaKernel;
|
||||
|
||||
internal readonly Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> RemainderScanKernel;
|
||||
internal readonly Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> CandidateMaskKernel;
|
||||
|
||||
internal readonly int Capacity;
|
||||
|
||||
internal readonly int CycleCapacity;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DivisorsBuffer.Dispose();
|
||||
@@ -922,6 +878,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
||||
ArrayPool<ulong>.Shared.Return(Exponents, clearArray: false);
|
||||
ArrayPool<byte>.Shared.Return(Hits, clearArray: false);
|
||||
ArrayPool<MontgomeryDivisorData>.Shared.Return(DivisorData, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(CycleCandidates, clearArray: false);
|
||||
ArrayPool<ulong>.Shared.Return(CycleLengths, clearArray: false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,13 +5,16 @@ using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
/// <summary>
|
||||
/// Provides GPU-accelerated divisibility checks for Mersenne numbers with prime exponents p >= 31.
|
||||
/// </summary>
|
||||
public sealed class MersenneNumberDivisorGpuTester
|
||||
{
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<byte>>> _kernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>> _kernelCache = new();
|
||||
private readonly ConcurrentDictionary<Accelerator, MemoryBuffer1D<byte, Stride1D.Dense>> _resultBuffers = new();
|
||||
|
||||
private Action<Index1D, ulong, GpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<byte>>(Kernel));
|
||||
private Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
|
||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(Kernel));
|
||||
|
||||
public static void BuildDivisorCandidates()
|
||||
{
|
||||
@@ -33,106 +36,102 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
_divisorCandidates = count == 0 ? [] : list[..count];
|
||||
}
|
||||
|
||||
public bool IsDivisible(ulong exponent, UInt128 divisor)
|
||||
{
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetKernel(accelerator);
|
||||
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
|
||||
resultBuffer.MemSetToZero();
|
||||
kernel(1, exponent, (GpuUInt128)divisor, resultBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
Span<byte> result = stackalloc byte[1];
|
||||
resultBuffer.View.CopyToCPU(ref result[0], 1);
|
||||
bool divisible = result[0] != 0;
|
||||
result[0] = 0;
|
||||
resultBuffer.View.CopyFromCPU(ref result[0], 1);
|
||||
accelerator.Synchronize();
|
||||
gpu.Dispose();
|
||||
return divisible;
|
||||
}
|
||||
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
|
||||
{
|
||||
var gpu = GpuContextPool.RentPreferred(preferCpu: false);
|
||||
var accelerator = gpu.Accelerator;
|
||||
var kernel = GetKernel(accelerator);
|
||||
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
|
||||
resultBuffer.MemSetToZero();
|
||||
kernel(1, exponent, divisor, resultBuffer.View);
|
||||
accelerator.Synchronize();
|
||||
Span<byte> result = stackalloc byte[1];
|
||||
resultBuffer.View.CopyToCPU(ref result[0], 1);
|
||||
bool divisible = result[0] != 0;
|
||||
result[0] = 0;
|
||||
resultBuffer.View.CopyFromCPU(ref result[0], 1);
|
||||
accelerator.Synchronize();
|
||||
gpu.Dispose();
|
||||
return divisible;
|
||||
}
|
||||
|
||||
private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>();
|
||||
|
||||
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
|
||||
{
|
||||
if (d != UInt128.Zero)
|
||||
{
|
||||
if (IsDivisible(p, d))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
|
||||
{
|
||||
ReadOnlyGpuUInt128 readOnlyDivisor;
|
||||
|
||||
divisorsExhausted = false;
|
||||
return true;
|
||||
}
|
||||
if (d != UInt128.Zero)
|
||||
{
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d);
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
divisorsExhausted = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_divisorCandidates is { Length: > 0 } candidates)
|
||||
{
|
||||
int candidateCount = candidates.Length;
|
||||
for (int index = 0; index < candidateCount; index++)
|
||||
{
|
||||
(ulong candidateDivisor, uint cycle) = candidates[index];
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
int len = candidates.Length;
|
||||
uint cycle;
|
||||
for (int k = 0; k < len; k++)
|
||||
{
|
||||
(ulong dSmall, cycle) = candidates[k];
|
||||
// TODO: Replace this `%` check with the shared divisor-cycle divisibility helper so GPU scans
|
||||
// reuse the cached cycle remainders instead of recomputing modulo for every candidate.
|
||||
if (p % cycle != 0UL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsDivisible(p, dSmall))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
UInt128 kMul2;
|
||||
var divisorCycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
|
||||
ulong limit = divisorCyclesSearchLimit;
|
||||
if ((UInt128)limit > maxK2)
|
||||
{
|
||||
limit = (ulong)maxK2;
|
||||
}
|
||||
UInt128 kMul2;
|
||||
var divisorCycles = MersenneDivisorCycles.Shared;
|
||||
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
|
||||
ulong limit = divisorCyclesSearchLimit;
|
||||
if ((UInt128)limit > maxK2)
|
||||
{
|
||||
limit = (ulong)maxK2;
|
||||
}
|
||||
|
||||
ulong k2;
|
||||
for (k2 = 1UL; k2 <= limit; k2++)
|
||||
{
|
||||
kMul2 = (UInt128)k2 << 1;
|
||||
UInt128 candidate = checked(kMul2 * p);
|
||||
d = checked(candidate + UInt128.One);
|
||||
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
for (ulong k2 = 1UL; k2 <= limit; k2++)
|
||||
{
|
||||
kMul2 = (UInt128)k2 << 1;
|
||||
UInt128 candidate = checked(kMul2 * p);
|
||||
d = checked(candidate + UInt128.One);
|
||||
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
|
||||
// TODO: When this lookup falls outside the preloaded snapshot, invoke the single-cycle GPU helper
|
||||
// instead of allowing GetCycle to allocate an entire block; the divisor-cycle benchmarks showed the
|
||||
// one-off computation keeping memory flat while still delivering the optimized stepping data.
|
||||
// TODO: Feed this check through the ProcessEightBitWindows-based residue tracker so we avoid the slow
|
||||
// UInt128 modulo on the CPU fallback highlighted by the Pow2Montgomery benchmarks.
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsDivisible(p, d))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors.
|
||||
if (IsDivisible(p, in readOnlyDivisor))
|
||||
{
|
||||
divisorsExhausted = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
|
||||
return true;
|
||||
}
|
||||
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Kernel(Index1D _, ulong exponent, GpuUInt128 divisor, ArrayView<byte> result)
|
||||
private static void Kernel(Index1D _, ulong exponent, ReadOnlyGpuUInt128 divisor, ArrayView<byte> result)
|
||||
{
|
||||
if (divisor.High == 0UL && divisor.Low <= 1UL)
|
||||
{
|
||||
@@ -140,16 +139,16 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
return;
|
||||
}
|
||||
|
||||
GpuUInt128 baseVal, mod;
|
||||
GpuUInt128 baseVal;
|
||||
int x = divisor.High == 0UL
|
||||
? 63 - XMath.LeadingZeroCount(divisor.Low)
|
||||
: 127 - XMath.LeadingZeroCount(divisor.High);
|
||||
mod = divisor;
|
||||
ReadOnlyGpuUInt128 readOnlyMod = divisor;
|
||||
if (x == 0)
|
||||
{
|
||||
// TODO: Swap Pow2Mod for the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
// GpuPow2ModBenchmarks showed the windowed kernel cutting large divisors from ~51 µs to ~21 µs.
|
||||
baseVal = GpuUInt128.Pow2Mod(exponent, mod);
|
||||
baseVal = GpuUInt128.Pow2Mod(exponent, in readOnlyMod);
|
||||
result[0] = baseVal.High == 0UL && baseVal.Low == 1UL ? (byte)1 : (byte)0;
|
||||
return;
|
||||
}
|
||||
@@ -165,12 +164,13 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
}
|
||||
|
||||
GpuUInt128 pow = baseVal;
|
||||
pow.ModPow(exponent / ux, mod);
|
||||
pow.ModPow(exponent / ux, in readOnlyMod);
|
||||
// TODO: Replace this trailing Pow2Mod call with the ProcessEightBitWindows variant once the shared
|
||||
// windowed helper lands so mixed-radix decompositions benefit from the same GPU speedup.
|
||||
var part2 = GpuUInt128.Pow2Mod(exponent % ux, mod);
|
||||
var part2 = GpuUInt128.Pow2Mod(exponent % ux, in readOnlyMod);
|
||||
GpuUInt128 product = pow;
|
||||
product.MulMod(part2, mod);
|
||||
ReadOnlyGpuUInt128 part2ReadOnly = part2.AsReadOnly();
|
||||
product.MulMod(in part2ReadOnly, in readOnlyMod);
|
||||
result[0] = product.High == 0UL && product.Low == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Threading;
|
||||
using System.Runtime.InteropServices;
|
||||
using ILGPU;
|
||||
using ILGPU.Runtime;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
/// <summary>
|
||||
/// Uses GPU kernels to incrementally scan Mersenne candidates for prime exponents p >= 31.
|
||||
/// </summary>
|
||||
public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool useGpuOrder)
|
||||
{
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
@@ -11,18 +17,23 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
|
||||
public void Scan(ulong exponent, UInt128 twoP, bool lastIsSeven, UInt128 maxK, ref bool isPrime)
|
||||
{
|
||||
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
|
||||
|
||||
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
|
||||
var execution = gpuLease.EnterExecutionScope();
|
||||
var accelerator = gpuLease.Accelerator;
|
||||
var stream = gpuLease.Stream;
|
||||
int batchSize = GpuConstants.ScanBatchSize;
|
||||
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
|
||||
UInt128 kStart = 1UL;
|
||||
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
|
||||
byte last = lastIsSeven ? (byte)1 : (byte)0;
|
||||
byte last = lastIsSeven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
|
||||
|
||||
var pow2Kernel = gpuLease.Pow2ModKernel; // TODO: Route this binding to the ProcessEightBitWindows kernel once the scalar helper lands so GPU incremental scans inherit the windowed speedup from GpuPow2ModBenchmarks.
|
||||
var pow2Kernel = gpuLease.Pow2ModKernel;
|
||||
var incKernel = gpuLease.IncrementalKernel;
|
||||
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
|
||||
ulong step10 = (exponent.Mod10() << 1).Mod10();
|
||||
ulong step8 = ((exponent & 7UL) << 1) & 7UL;
|
||||
ulong step3 = ((exponent % 3UL) << 1) % 3UL;
|
||||
ulong step5 = ((exponent % 5UL) << 1) % 5UL;
|
||||
GpuUInt128 twoPGpu = (GpuUInt128)twoP;
|
||||
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||
ResiduePrimeViews primeViews = default;
|
||||
@@ -32,12 +43,13 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
}
|
||||
|
||||
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
|
||||
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
|
||||
// Rent a reusable array from the shared pool instead.
|
||||
ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
|
||||
UInt128 remaining;
|
||||
int currentSize;
|
||||
int i;
|
||||
UInt128 q = UInt128.Zero;
|
||||
|
||||
try
|
||||
{
|
||||
while (kStart <= maxK && Volatile.Read(ref isPrime))
|
||||
@@ -45,22 +57,45 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
remaining = maxK - kStart + UInt128.One;
|
||||
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
|
||||
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
|
||||
// Precompute residue automaton bases for this batch
|
||||
UInt128 q0 = twoP * kStart + UInt128.One;
|
||||
// TODO: Swap this multiply-and-add initialization for the divisor-cycle stepping helper so the GPU incremental path reuses cached offsets instead of recomputing twoP multiples every batch.
|
||||
|
||||
if (_kernelType == GpuKernelType.Pow2Mod)
|
||||
{
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
|
||||
pow2Kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul,
|
||||
kernelArgs, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven,
|
||||
primeViews.LastOnePow2, primeViews.LastSevenPow2);
|
||||
pow2Kernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
kernelArgs,
|
||||
orderBuffer.View,
|
||||
smallCyclesView,
|
||||
primeViews.LastOne,
|
||||
primeViews.LastSeven,
|
||||
primeViews.LastOnePow2,
|
||||
primeViews.LastSevenPow2);
|
||||
}
|
||||
else
|
||||
{
|
||||
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
|
||||
incKernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, divMul,
|
||||
q0m10, q0m8, q0m3, q0m5, orderBuffer.View, smallCyclesView);
|
||||
incKernel(
|
||||
stream,
|
||||
currentSize,
|
||||
exponent,
|
||||
twoPGpu,
|
||||
(GpuUInt128)kStart,
|
||||
last,
|
||||
divMul,
|
||||
q0m10,
|
||||
q0m8,
|
||||
q0m3,
|
||||
q0m5,
|
||||
orderBuffer.View,
|
||||
smallCyclesView);
|
||||
}
|
||||
|
||||
stream.Synchronize();
|
||||
@@ -79,7 +114,6 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
else
|
||||
{
|
||||
q = twoP * kStart + UInt128.One;
|
||||
// TODO: Replace this per-lane multiplication and the subsequent q += twoP stride with the shared divisor-cycle ladder so each iteration advances by cached cycle increments and automatically triggers on-demand GPU computations when the snapshot lacks the requested cycle.
|
||||
for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP)
|
||||
{
|
||||
if (orders[i] != 0UL && q.IsPrimeCandidate())
|
||||
@@ -101,4 +135,3 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,681 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal static partial class PrimeOrderGpuHeuristics
|
||||
{
|
||||
internal readonly struct Pow2OddPowerTableGpuUInt128
|
||||
{
|
||||
public Pow2OddPowerTableGpuUInt128(GpuUInt128 baseValue, GpuUInt128 modulus, int oddPowerCount)
|
||||
{
|
||||
Element0 = baseValue;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder that follows.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
// TODO: We can calculate baseValue % modulus before loop and use it to increase ladderEntry calculation speed - we'll reuse the base for incremental calculations.
|
||||
GpuUInt128 current = baseValue;
|
||||
|
||||
// We're manually assigning each field to prevent the compiler to initialize each field twice due to auto-initialization. We're using the action to lower the code base size.
|
||||
for (int i = 1; i < oddPowerCount; i++)
|
||||
{
|
||||
current.MulMod(baseValue, modulus);
|
||||
switch (i)
|
||||
{
|
||||
case 1:
|
||||
Element1 = current;
|
||||
break;
|
||||
case 2:
|
||||
Element2 = current;
|
||||
break;
|
||||
case 3:
|
||||
Element3 = current;
|
||||
break;
|
||||
case 4:
|
||||
Element4 = current;
|
||||
break;
|
||||
case 5:
|
||||
Element5 = current;
|
||||
break;
|
||||
case 6:
|
||||
Element6 = current;
|
||||
break;
|
||||
case 7:
|
||||
Element7 = current;
|
||||
break;
|
||||
case 8:
|
||||
Element8 = current;
|
||||
break;
|
||||
case 9:
|
||||
Element9 = current;
|
||||
break;
|
||||
case 10:
|
||||
Element10 = current;
|
||||
break;
|
||||
case 11:
|
||||
Element11 = current;
|
||||
break;
|
||||
case 12:
|
||||
Element12 = current;
|
||||
break;
|
||||
case 13:
|
||||
Element13 = current;
|
||||
break;
|
||||
case 14:
|
||||
Element14 = current;
|
||||
break;
|
||||
case 15:
|
||||
Element15 = current;
|
||||
break;
|
||||
case 16:
|
||||
Element16 = current;
|
||||
break;
|
||||
case 17:
|
||||
Element17 = current;
|
||||
break;
|
||||
case 18:
|
||||
Element18 = current;
|
||||
break;
|
||||
case 19:
|
||||
Element19 = current;
|
||||
break;
|
||||
case 20:
|
||||
Element20 = current;
|
||||
break;
|
||||
case 21:
|
||||
Element21 = current;
|
||||
break;
|
||||
case 22:
|
||||
Element22 = current;
|
||||
break;
|
||||
case 23:
|
||||
Element23 = current;
|
||||
break;
|
||||
case 24:
|
||||
Element24 = current;
|
||||
break;
|
||||
case 25:
|
||||
Element25 = current;
|
||||
break;
|
||||
case 26:
|
||||
Element26 = current;
|
||||
break;
|
||||
case 27:
|
||||
Element27 = current;
|
||||
break;
|
||||
case 28:
|
||||
Element28 = current;
|
||||
break;
|
||||
case 29:
|
||||
Element29 = current;
|
||||
break;
|
||||
case 30:
|
||||
Element30 = current;
|
||||
break;
|
||||
case 31:
|
||||
Element31 = current;
|
||||
break;
|
||||
case 32:
|
||||
Element32 = current;
|
||||
break;
|
||||
case 33:
|
||||
Element33 = current;
|
||||
break;
|
||||
case 34:
|
||||
Element34 = current;
|
||||
break;
|
||||
case 35:
|
||||
Element35 = current;
|
||||
break;
|
||||
case 36:
|
||||
Element36 = current;
|
||||
break;
|
||||
case 37:
|
||||
Element37 = current;
|
||||
break;
|
||||
case 38:
|
||||
Element38 = current;
|
||||
break;
|
||||
case 39:
|
||||
Element39 = current;
|
||||
break;
|
||||
case 40:
|
||||
Element40 = current;
|
||||
break;
|
||||
case 41:
|
||||
Element41 = current;
|
||||
break;
|
||||
case 42:
|
||||
Element42 = current;
|
||||
break;
|
||||
case 43:
|
||||
Element43 = current;
|
||||
break;
|
||||
case 44:
|
||||
Element44 = current;
|
||||
break;
|
||||
case 45:
|
||||
Element45 = current;
|
||||
break;
|
||||
case 46:
|
||||
Element46 = current;
|
||||
break;
|
||||
case 47:
|
||||
Element47 = current;
|
||||
break;
|
||||
case 48:
|
||||
Element48 = current;
|
||||
break;
|
||||
case 49:
|
||||
Element49 = current;
|
||||
break;
|
||||
case 50:
|
||||
Element50 = current;
|
||||
break;
|
||||
case 51:
|
||||
Element51 = current;
|
||||
break;
|
||||
case 52:
|
||||
Element52 = current;
|
||||
break;
|
||||
case 53:
|
||||
Element53 = current;
|
||||
break;
|
||||
case 54:
|
||||
Element54 = current;
|
||||
break;
|
||||
case 55:
|
||||
Element55 = current;
|
||||
break;
|
||||
case 56:
|
||||
Element56 = current;
|
||||
break;
|
||||
case 57:
|
||||
Element57 = current;
|
||||
break;
|
||||
case 58:
|
||||
Element58 = current;
|
||||
break;
|
||||
case 59:
|
||||
Element59 = current;
|
||||
break;
|
||||
case 60:
|
||||
Element60 = current;
|
||||
break;
|
||||
case 61:
|
||||
Element61 = current;
|
||||
break;
|
||||
case 62:
|
||||
Element62 = current;
|
||||
break;
|
||||
case 63:
|
||||
Element63 = current;
|
||||
break;
|
||||
case 64:
|
||||
Element64 = current;
|
||||
break;
|
||||
case 65:
|
||||
Element65 = current;
|
||||
break;
|
||||
case 66:
|
||||
Element66 = current;
|
||||
break;
|
||||
case 67:
|
||||
Element67 = current;
|
||||
break;
|
||||
case 68:
|
||||
Element68 = current;
|
||||
break;
|
||||
case 69:
|
||||
Element69 = current;
|
||||
break;
|
||||
case 70:
|
||||
Element70 = current;
|
||||
break;
|
||||
case 71:
|
||||
Element71 = current;
|
||||
break;
|
||||
case 72:
|
||||
Element72 = current;
|
||||
break;
|
||||
case 73:
|
||||
Element73 = current;
|
||||
break;
|
||||
case 74:
|
||||
Element74 = current;
|
||||
break;
|
||||
case 75:
|
||||
Element75 = current;
|
||||
break;
|
||||
case 76:
|
||||
Element76 = current;
|
||||
break;
|
||||
case 77:
|
||||
Element77 = current;
|
||||
break;
|
||||
case 78:
|
||||
Element78 = current;
|
||||
break;
|
||||
case 79:
|
||||
Element79 = current;
|
||||
break;
|
||||
case 80:
|
||||
Element80 = current;
|
||||
break;
|
||||
case 81:
|
||||
Element81 = current;
|
||||
break;
|
||||
case 82:
|
||||
Element82 = current;
|
||||
break;
|
||||
case 83:
|
||||
Element83 = current;
|
||||
break;
|
||||
case 84:
|
||||
Element84 = current;
|
||||
break;
|
||||
case 85:
|
||||
Element85 = current;
|
||||
break;
|
||||
case 86:
|
||||
Element86 = current;
|
||||
break;
|
||||
case 87:
|
||||
Element87 = current;
|
||||
break;
|
||||
case 88:
|
||||
Element88 = current;
|
||||
break;
|
||||
case 89:
|
||||
Element89 = current;
|
||||
break;
|
||||
case 90:
|
||||
Element90 = current;
|
||||
break;
|
||||
case 91:
|
||||
Element91 = current;
|
||||
break;
|
||||
case 92:
|
||||
Element92 = current;
|
||||
break;
|
||||
case 93:
|
||||
Element93 = current;
|
||||
break;
|
||||
case 94:
|
||||
Element94 = current;
|
||||
break;
|
||||
case 95:
|
||||
Element95 = current;
|
||||
break;
|
||||
case 96:
|
||||
Element96 = current;
|
||||
break;
|
||||
case 97:
|
||||
Element97 = current;
|
||||
break;
|
||||
case 98:
|
||||
Element98 = current;
|
||||
break;
|
||||
case 99:
|
||||
Element99 = current;
|
||||
break;
|
||||
case 100:
|
||||
Element100 = current;
|
||||
break;
|
||||
case 101:
|
||||
Element101 = current;
|
||||
break;
|
||||
case 102:
|
||||
Element102 = current;
|
||||
break;
|
||||
case 103:
|
||||
Element103 = current;
|
||||
break;
|
||||
case 104:
|
||||
Element104 = current;
|
||||
break;
|
||||
case 105:
|
||||
Element105 = current;
|
||||
break;
|
||||
case 106:
|
||||
Element106 = current;
|
||||
break;
|
||||
case 107:
|
||||
Element107 = current;
|
||||
break;
|
||||
case 108:
|
||||
Element108 = current;
|
||||
break;
|
||||
case 109:
|
||||
Element109 = current;
|
||||
break;
|
||||
case 110:
|
||||
Element110 = current;
|
||||
break;
|
||||
case 111:
|
||||
Element111 = current;
|
||||
break;
|
||||
case 112:
|
||||
Element112 = current;
|
||||
break;
|
||||
case 113:
|
||||
Element113 = current;
|
||||
break;
|
||||
case 114:
|
||||
Element114 = current;
|
||||
break;
|
||||
case 115:
|
||||
Element115 = current;
|
||||
break;
|
||||
case 116:
|
||||
Element116 = current;
|
||||
break;
|
||||
case 117:
|
||||
Element117 = current;
|
||||
break;
|
||||
case 118:
|
||||
Element118 = current;
|
||||
break;
|
||||
case 119:
|
||||
Element119 = current;
|
||||
break;
|
||||
case 120:
|
||||
Element120 = current;
|
||||
break;
|
||||
case 121:
|
||||
Element121 = current;
|
||||
break;
|
||||
case 122:
|
||||
Element122 = current;
|
||||
break;
|
||||
case 123:
|
||||
Element123 = current;
|
||||
break;
|
||||
case 124:
|
||||
Element124 = current;
|
||||
break;
|
||||
case 125:
|
||||
Element125 = current;
|
||||
break;
|
||||
case 126:
|
||||
Element126 = current;
|
||||
break;
|
||||
case 127:
|
||||
Element127 = current;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly GpuUInt128 Element0;
|
||||
public readonly GpuUInt128 Element1;
|
||||
public readonly GpuUInt128 Element2;
|
||||
public readonly GpuUInt128 Element3;
|
||||
public readonly GpuUInt128 Element4;
|
||||
public readonly GpuUInt128 Element5;
|
||||
public readonly GpuUInt128 Element6;
|
||||
public readonly GpuUInt128 Element7;
|
||||
public readonly GpuUInt128 Element8;
|
||||
public readonly GpuUInt128 Element9;
|
||||
public readonly GpuUInt128 Element10;
|
||||
public readonly GpuUInt128 Element11;
|
||||
public readonly GpuUInt128 Element12;
|
||||
public readonly GpuUInt128 Element13;
|
||||
public readonly GpuUInt128 Element14;
|
||||
public readonly GpuUInt128 Element15;
|
||||
public readonly GpuUInt128 Element16;
|
||||
public readonly GpuUInt128 Element17;
|
||||
public readonly GpuUInt128 Element18;
|
||||
public readonly GpuUInt128 Element19;
|
||||
public readonly GpuUInt128 Element20;
|
||||
public readonly GpuUInt128 Element21;
|
||||
public readonly GpuUInt128 Element22;
|
||||
public readonly GpuUInt128 Element23;
|
||||
public readonly GpuUInt128 Element24;
|
||||
public readonly GpuUInt128 Element25;
|
||||
public readonly GpuUInt128 Element26;
|
||||
public readonly GpuUInt128 Element27;
|
||||
public readonly GpuUInt128 Element28;
|
||||
public readonly GpuUInt128 Element29;
|
||||
public readonly GpuUInt128 Element30;
|
||||
public readonly GpuUInt128 Element31;
|
||||
public readonly GpuUInt128 Element32;
|
||||
public readonly GpuUInt128 Element33;
|
||||
public readonly GpuUInt128 Element34;
|
||||
public readonly GpuUInt128 Element35;
|
||||
public readonly GpuUInt128 Element36;
|
||||
public readonly GpuUInt128 Element37;
|
||||
public readonly GpuUInt128 Element38;
|
||||
public readonly GpuUInt128 Element39;
|
||||
public readonly GpuUInt128 Element40;
|
||||
public readonly GpuUInt128 Element41;
|
||||
public readonly GpuUInt128 Element42;
|
||||
public readonly GpuUInt128 Element43;
|
||||
public readonly GpuUInt128 Element44;
|
||||
public readonly GpuUInt128 Element45;
|
||||
public readonly GpuUInt128 Element46;
|
||||
public readonly GpuUInt128 Element47;
|
||||
public readonly GpuUInt128 Element48;
|
||||
public readonly GpuUInt128 Element49;
|
||||
public readonly GpuUInt128 Element50;
|
||||
public readonly GpuUInt128 Element51;
|
||||
public readonly GpuUInt128 Element52;
|
||||
public readonly GpuUInt128 Element53;
|
||||
public readonly GpuUInt128 Element54;
|
||||
public readonly GpuUInt128 Element55;
|
||||
public readonly GpuUInt128 Element56;
|
||||
public readonly GpuUInt128 Element57;
|
||||
public readonly GpuUInt128 Element58;
|
||||
public readonly GpuUInt128 Element59;
|
||||
public readonly GpuUInt128 Element60;
|
||||
public readonly GpuUInt128 Element61;
|
||||
public readonly GpuUInt128 Element62;
|
||||
public readonly GpuUInt128 Element63;
|
||||
public readonly GpuUInt128 Element64;
|
||||
public readonly GpuUInt128 Element65;
|
||||
public readonly GpuUInt128 Element66;
|
||||
public readonly GpuUInt128 Element67;
|
||||
public readonly GpuUInt128 Element68;
|
||||
public readonly GpuUInt128 Element69;
|
||||
public readonly GpuUInt128 Element70;
|
||||
public readonly GpuUInt128 Element71;
|
||||
public readonly GpuUInt128 Element72;
|
||||
public readonly GpuUInt128 Element73;
|
||||
public readonly GpuUInt128 Element74;
|
||||
public readonly GpuUInt128 Element75;
|
||||
public readonly GpuUInt128 Element76;
|
||||
public readonly GpuUInt128 Element77;
|
||||
public readonly GpuUInt128 Element78;
|
||||
public readonly GpuUInt128 Element79;
|
||||
public readonly GpuUInt128 Element80;
|
||||
public readonly GpuUInt128 Element81;
|
||||
public readonly GpuUInt128 Element82;
|
||||
public readonly GpuUInt128 Element83;
|
||||
public readonly GpuUInt128 Element84;
|
||||
public readonly GpuUInt128 Element85;
|
||||
public readonly GpuUInt128 Element86;
|
||||
public readonly GpuUInt128 Element87;
|
||||
public readonly GpuUInt128 Element88;
|
||||
public readonly GpuUInt128 Element89;
|
||||
public readonly GpuUInt128 Element90;
|
||||
public readonly GpuUInt128 Element91;
|
||||
public readonly GpuUInt128 Element92;
|
||||
public readonly GpuUInt128 Element93;
|
||||
public readonly GpuUInt128 Element94;
|
||||
public readonly GpuUInt128 Element95;
|
||||
public readonly GpuUInt128 Element96;
|
||||
public readonly GpuUInt128 Element97;
|
||||
public readonly GpuUInt128 Element98;
|
||||
public readonly GpuUInt128 Element99;
|
||||
public readonly GpuUInt128 Element100;
|
||||
public readonly GpuUInt128 Element101;
|
||||
public readonly GpuUInt128 Element102;
|
||||
public readonly GpuUInt128 Element103;
|
||||
public readonly GpuUInt128 Element104;
|
||||
public readonly GpuUInt128 Element105;
|
||||
public readonly GpuUInt128 Element106;
|
||||
public readonly GpuUInt128 Element107;
|
||||
public readonly GpuUInt128 Element108;
|
||||
public readonly GpuUInt128 Element109;
|
||||
public readonly GpuUInt128 Element110;
|
||||
public readonly GpuUInt128 Element111;
|
||||
public readonly GpuUInt128 Element112;
|
||||
public readonly GpuUInt128 Element113;
|
||||
public readonly GpuUInt128 Element114;
|
||||
public readonly GpuUInt128 Element115;
|
||||
public readonly GpuUInt128 Element116;
|
||||
public readonly GpuUInt128 Element117;
|
||||
public readonly GpuUInt128 Element118;
|
||||
public readonly GpuUInt128 Element119;
|
||||
public readonly GpuUInt128 Element120;
|
||||
public readonly GpuUInt128 Element121;
|
||||
public readonly GpuUInt128 Element122;
|
||||
public readonly GpuUInt128 Element123;
|
||||
public readonly GpuUInt128 Element124;
|
||||
public readonly GpuUInt128 Element125;
|
||||
public readonly GpuUInt128 Element126;
|
||||
public readonly GpuUInt128 Element127;
|
||||
|
||||
public readonly GpuUInt128 this[int index]
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get
|
||||
{
|
||||
return index switch
|
||||
{
|
||||
0 => Element0,
|
||||
1 => Element1,
|
||||
2 => Element2,
|
||||
3 => Element3,
|
||||
4 => Element4,
|
||||
5 => Element5,
|
||||
6 => Element6,
|
||||
7 => Element7,
|
||||
8 => Element8,
|
||||
9 => Element9,
|
||||
10 => Element10,
|
||||
11 => Element11,
|
||||
12 => Element12,
|
||||
13 => Element13,
|
||||
14 => Element14,
|
||||
15 => Element15,
|
||||
16 => Element16,
|
||||
17 => Element17,
|
||||
18 => Element18,
|
||||
19 => Element19,
|
||||
20 => Element20,
|
||||
21 => Element21,
|
||||
22 => Element22,
|
||||
23 => Element23,
|
||||
24 => Element24,
|
||||
25 => Element25,
|
||||
26 => Element26,
|
||||
27 => Element27,
|
||||
28 => Element28,
|
||||
29 => Element29,
|
||||
30 => Element30,
|
||||
31 => Element31,
|
||||
32 => Element32,
|
||||
33 => Element33,
|
||||
34 => Element34,
|
||||
35 => Element35,
|
||||
36 => Element36,
|
||||
37 => Element37,
|
||||
38 => Element38,
|
||||
39 => Element39,
|
||||
40 => Element40,
|
||||
41 => Element41,
|
||||
42 => Element42,
|
||||
43 => Element43,
|
||||
44 => Element44,
|
||||
45 => Element45,
|
||||
46 => Element46,
|
||||
47 => Element47,
|
||||
48 => Element48,
|
||||
49 => Element49,
|
||||
50 => Element50,
|
||||
51 => Element51,
|
||||
52 => Element52,
|
||||
53 => Element53,
|
||||
54 => Element54,
|
||||
55 => Element55,
|
||||
56 => Element56,
|
||||
57 => Element57,
|
||||
58 => Element58,
|
||||
59 => Element59,
|
||||
60 => Element60,
|
||||
61 => Element61,
|
||||
62 => Element62,
|
||||
63 => Element63,
|
||||
64 => Element64,
|
||||
65 => Element65,
|
||||
66 => Element66,
|
||||
67 => Element67,
|
||||
68 => Element68,
|
||||
69 => Element69,
|
||||
70 => Element70,
|
||||
71 => Element71,
|
||||
72 => Element72,
|
||||
73 => Element73,
|
||||
74 => Element74,
|
||||
75 => Element75,
|
||||
76 => Element76,
|
||||
77 => Element77,
|
||||
78 => Element78,
|
||||
79 => Element79,
|
||||
80 => Element80,
|
||||
81 => Element81,
|
||||
82 => Element82,
|
||||
83 => Element83,
|
||||
84 => Element84,
|
||||
85 => Element85,
|
||||
86 => Element86,
|
||||
87 => Element87,
|
||||
88 => Element88,
|
||||
89 => Element89,
|
||||
90 => Element90,
|
||||
91 => Element91,
|
||||
92 => Element92,
|
||||
93 => Element93,
|
||||
94 => Element94,
|
||||
95 => Element95,
|
||||
96 => Element96,
|
||||
97 => Element97,
|
||||
98 => Element98,
|
||||
99 => Element99,
|
||||
100 => Element100,
|
||||
101 => Element101,
|
||||
102 => Element102,
|
||||
103 => Element103,
|
||||
104 => Element104,
|
||||
105 => Element105,
|
||||
106 => Element106,
|
||||
107 => Element107,
|
||||
108 => Element108,
|
||||
109 => Element109,
|
||||
110 => Element110,
|
||||
111 => Element111,
|
||||
112 => Element112,
|
||||
113 => Element113,
|
||||
114 => Element114,
|
||||
115 => Element115,
|
||||
116 => Element116,
|
||||
117 => Element117,
|
||||
118 => Element118,
|
||||
119 => Element119,
|
||||
120 => Element120,
|
||||
121 => Element121,
|
||||
122 => Element122,
|
||||
123 => Element123,
|
||||
124 => Element124,
|
||||
125 => Element125,
|
||||
126 => Element126,
|
||||
127 => Element127,
|
||||
_ => Element0,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Numerics;
|
||||
using PerfectNumbers.Core;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU.Runtime.OpenCL;
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
internal enum GpuPow2ModStatus
|
||||
@@ -26,73 +27,37 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
private static readonly ConcurrentDictionary<Accelerator, OrderKernelLauncher> OrderKernelCache = new();
|
||||
private static readonly ConcurrentDictionary<Accelerator, SmallPrimeDeviceCache> SmallPrimeDeviceCaches = new();
|
||||
|
||||
private readonly struct OrderKernelConfig
|
||||
public readonly struct OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode)
|
||||
{
|
||||
public OrderKernelConfig(ulong previousOrder, byte hasPreviousOrder, uint smallFactorLimit, int maxPowChecks, int mode)
|
||||
{
|
||||
PreviousOrder = previousOrder;
|
||||
HasPreviousOrder = hasPreviousOrder;
|
||||
SmallFactorLimit = smallFactorLimit;
|
||||
MaxPowChecks = maxPowChecks;
|
||||
Mode = mode;
|
||||
}
|
||||
|
||||
public ulong PreviousOrder { get; }
|
||||
|
||||
public byte HasPreviousOrder { get; }
|
||||
|
||||
public uint SmallFactorLimit { get; }
|
||||
|
||||
public int MaxPowChecks { get; }
|
||||
|
||||
public int Mode { get; }
|
||||
public readonly ulong PreviousOrder = previousOrder;
|
||||
public readonly byte HasPreviousOrder = hasPreviousOrder;
|
||||
public readonly uint SmallFactorLimit = smallFactorLimit;
|
||||
public readonly int MaxPowChecks = maxPowChecks;
|
||||
public readonly int Mode = mode;
|
||||
}
|
||||
|
||||
private readonly struct OrderKernelBuffers
|
||||
public readonly struct OrderKernelBuffers(
|
||||
ArrayView1D<ulong, Stride1D.Dense> phiFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> phiExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> workFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> workExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> candidates,
|
||||
ArrayView1D<int, Stride1D.Dense> stackIndex,
|
||||
ArrayView1D<int, Stride1D.Dense> stackExponent,
|
||||
ArrayView1D<ulong, Stride1D.Dense> stackProduct,
|
||||
ArrayView1D<ulong, Stride1D.Dense> result,
|
||||
ArrayView1D<byte, Stride1D.Dense> status)
|
||||
{
|
||||
public OrderKernelBuffers(
|
||||
ArrayView1D<ulong, Stride1D.Dense> phiFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> phiExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> workFactors,
|
||||
ArrayView1D<int, Stride1D.Dense> workExponents,
|
||||
ArrayView1D<ulong, Stride1D.Dense> candidates,
|
||||
ArrayView1D<int, Stride1D.Dense> stackIndex,
|
||||
ArrayView1D<int, Stride1D.Dense> stackExponent,
|
||||
ArrayView1D<ulong, Stride1D.Dense> stackProduct,
|
||||
ArrayView1D<ulong, Stride1D.Dense> result,
|
||||
ArrayView1D<byte, Stride1D.Dense> status)
|
||||
{
|
||||
PhiFactors = phiFactors;
|
||||
PhiExponents = phiExponents;
|
||||
WorkFactors = workFactors;
|
||||
WorkExponents = workExponents;
|
||||
Candidates = candidates;
|
||||
StackIndex = stackIndex;
|
||||
StackExponent = stackExponent;
|
||||
StackProduct = stackProduct;
|
||||
Result = result;
|
||||
Status = status;
|
||||
}
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> PhiFactors { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> PhiExponents { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> WorkFactors { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> WorkExponents { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> Candidates { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> StackIndex { get; }
|
||||
|
||||
public ArrayView1D<int, Stride1D.Dense> StackExponent { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> StackProduct { get; }
|
||||
|
||||
public ArrayView1D<ulong, Stride1D.Dense> Result { get; }
|
||||
|
||||
public ArrayView1D<byte, Stride1D.Dense> Status { get; }
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> PhiFactors = phiFactors;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> PhiExponents = phiExponents;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> WorkFactors = workFactors;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> WorkExponents = workExponents;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> Candidates = candidates;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> StackIndex = stackIndex;
|
||||
public readonly ArrayView1D<int, Stride1D.Dense> StackExponent = stackExponent;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> StackProduct = stackProduct;
|
||||
public readonly ArrayView1D<ulong, Stride1D.Dense> Result = result;
|
||||
public readonly ArrayView1D<byte, Stride1D.Dense> Status = status;
|
||||
}
|
||||
|
||||
private delegate void OrderKernelLauncher(
|
||||
@@ -124,13 +89,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
|
||||
private const int GpuSmallPrimeFactorSlots = 64;
|
||||
|
||||
|
||||
[InlineArray(Pow2WindowOddPowerCount)]
|
||||
private struct Pow2OddPowerTable
|
||||
{
|
||||
private GpuUInt128 _element0;
|
||||
}
|
||||
|
||||
internal static ConcurrentDictionary<ulong, byte> OverflowRegistry => OverflowedPrimes;
|
||||
internal static ConcurrentDictionary<UInt128, byte> OverflowRegistryWide => OverflowedPrimesWide;
|
||||
|
||||
@@ -237,63 +195,72 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
try
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
|
||||
var kernel = GetPartialFactorKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
var kernel = GetPartialFactorKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
|
||||
using var factorBuffer = accelerator.Allocate1D<ulong>(primeTargets.Length);
|
||||
using var exponentBuffer = accelerator.Allocate1D<int>(exponentTargets.Length);
|
||||
using var countBuffer = accelerator.Allocate1D<int>(1);
|
||||
using var remainingBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
using var fullyFactoredBuffer = accelerator.Allocate1D<byte>(1);
|
||||
var factorBuffer = accelerator.Allocate1D<ulong>(primeTargets.Length);
|
||||
var exponentBuffer = accelerator.Allocate1D<int>(exponentTargets.Length);
|
||||
var countBuffer = accelerator.Allocate1D<int>(1);
|
||||
var remainingBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
var fullyFactoredBuffer = accelerator.Allocate1D<byte>(1);
|
||||
|
||||
factorBuffer.MemSetToZero();
|
||||
exponentBuffer.MemSetToZero();
|
||||
countBuffer.MemSetToZero();
|
||||
remainingBuffer.MemSetToZero();
|
||||
fullyFactoredBuffer.MemSetToZero();
|
||||
factorBuffer.MemSetToZero();
|
||||
exponentBuffer.MemSetToZero();
|
||||
countBuffer.MemSetToZero();
|
||||
remainingBuffer.MemSetToZero();
|
||||
fullyFactoredBuffer.MemSetToZero();
|
||||
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
primeTargets.Length,
|
||||
value,
|
||||
limit,
|
||||
factorBuffer.View,
|
||||
exponentBuffer.View,
|
||||
countBuffer.View,
|
||||
remainingBuffer.View,
|
||||
fullyFactoredBuffer.View);
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
primeTargets.Length,
|
||||
value,
|
||||
limit,
|
||||
factorBuffer.View,
|
||||
exponentBuffer.View,
|
||||
countBuffer.View,
|
||||
remainingBuffer.View,
|
||||
fullyFactoredBuffer.View);
|
||||
|
||||
stream.Synchronize();
|
||||
stream.Synchronize();
|
||||
|
||||
countBuffer.View.CopyToCPU(ref factorCount, 1);
|
||||
factorCount = Math.Min(factorCount, primeTargets.Length);
|
||||
factorBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(primeTargets), primeTargets.Length);
|
||||
exponentBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentTargets), exponentTargets.Length);
|
||||
remainingBuffer.View.CopyToCPU(ref remaining, 1);
|
||||
countBuffer.View.CopyToCPU(ref factorCount, 1);
|
||||
factorCount = Math.Min(factorCount, primeTargets.Length);
|
||||
factorBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(primeTargets), primeTargets.Length);
|
||||
exponentBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentTargets), exponentTargets.Length);
|
||||
remainingBuffer.View.CopyToCPU(ref remaining, 1);
|
||||
|
||||
byte fullyFactoredFlag = 0;
|
||||
fullyFactoredBuffer.View.CopyToCPU(ref fullyFactoredFlag, 1);
|
||||
fullyFactored = fullyFactoredFlag != 0;
|
||||
byte fullyFactoredFlag = 0;
|
||||
fullyFactoredBuffer.View.CopyToCPU(ref fullyFactoredFlag, 1);
|
||||
fullyFactored = fullyFactoredFlag != 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
factorBuffer.Dispose();
|
||||
exponentBuffer.Dispose();
|
||||
countBuffer.Dispose();
|
||||
remainingBuffer.Dispose();
|
||||
fullyFactoredBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
lease.Dispose();
|
||||
return true;
|
||||
}
|
||||
catch (CLException ex)
|
||||
{
|
||||
Console.WriteLine($"GPU ERROR ({ex.Error}): {ex.Message}");
|
||||
factorCount = 0;
|
||||
remaining = value;
|
||||
fullyFactored = false;
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
{
|
||||
Console.WriteLine($"GPU ERROR: {ex.Message}");
|
||||
factorCount = 0;
|
||||
remaining = value;
|
||||
fullyFactored = false;
|
||||
@@ -361,36 +328,44 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
remainingOut[0] = remainingLocal;
|
||||
fullyFactoredOut[0] = remainingLocal == 1UL ? (byte)1 : (byte)0;
|
||||
}
|
||||
public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder)
|
||||
public static GpuPow2ModStatus TryPow2Mod(ulong exponent, ulong prime, out ulong remainder, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
Span<ulong> exponents = stackalloc ulong[1];
|
||||
Span<ulong> remainders = stackalloc ulong[1];
|
||||
exponents[0] = exponent;
|
||||
|
||||
GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders);
|
||||
GpuPow2ModStatus status = TryPow2ModBatch(exponents, prime, remainders, divisorData);
|
||||
remainder = remainders[0];
|
||||
return status;
|
||||
}
|
||||
|
||||
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> remainders)
|
||||
public static GpuPow2ModStatus TryPow2ModBatch(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> remainders, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (exponents.Length == 0)
|
||||
{
|
||||
return GpuPow2ModStatus.Success;
|
||||
}
|
||||
// This will never occur in production code
|
||||
// if (exponents.Length == 0)
|
||||
// {
|
||||
// return GpuPow2ModStatus.Success;
|
||||
// }
|
||||
|
||||
if (remainders.Length < exponents.Length)
|
||||
{
|
||||
throw new ArgumentException("Remainder span is shorter than the exponent span.", nameof(remainders));
|
||||
}
|
||||
// This will never occur in production code
|
||||
// if (remainders.Length < exponents.Length)
|
||||
// {
|
||||
// throw new ArgumentException("Remainder span is shorter than the exponent span.", nameof(remainders));
|
||||
// }
|
||||
|
||||
Span<ulong> target = remainders.Slice(0, exponents.Length);
|
||||
target.Clear();
|
||||
// This will never occur in production code
|
||||
// if (remainders.Length > exponents.Length)
|
||||
// throw new ArgumentException("Remainder span is longer than the exponent span.", nameof(remainders));
|
||||
|
||||
if (prime <= 1UL)
|
||||
{
|
||||
return GpuPow2ModStatus.Unavailable;
|
||||
}
|
||||
// We need to clear more, but we save on allocations
|
||||
// Span<ulong> target = remainders[..exponents.Length];
|
||||
remainders.Clear();
|
||||
|
||||
// This will never occur in production code
|
||||
// if (prime <= 1UL)
|
||||
// {
|
||||
// return GpuPow2ModStatus.Unavailable;
|
||||
// }
|
||||
|
||||
ConcurrentDictionary<ulong, byte> overflowRegistry = OverflowedPrimes;
|
||||
|
||||
@@ -415,11 +390,11 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
}
|
||||
|
||||
bool computed = TryComputeOnGpu(exponents, prime, target);
|
||||
bool computed = TryComputeOnGpu(exponents, prime, divisorData, remainders);
|
||||
return computed ? GpuPow2ModStatus.Success : GpuPow2ModStatus.Unavailable;
|
||||
}
|
||||
|
||||
public static GpuPow2ModStatus TryPow2Mod(UInt128 exponent, UInt128 prime, out UInt128 remainder)
|
||||
public static GpuPow2ModStatus TryPow2Mod(in UInt128 exponent, in UInt128 prime, out UInt128 remainder)
|
||||
{
|
||||
Span<UInt128> exponents = stackalloc UInt128[1];
|
||||
Span<UInt128> remainders = stackalloc UInt128[1];
|
||||
@@ -444,104 +419,111 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
{
|
||||
result = default;
|
||||
|
||||
try
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
|
||||
var kernel = GetOrderKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
|
||||
var phiFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
var phiExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
var workFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
var workExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
var candidateBuffer = accelerator.Allocate1D<ulong>(HeuristicCandidateLimit);
|
||||
var stackIndexBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
var stackExponentBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
var stackProductBuffer = accelerator.Allocate1D<ulong>(HeuristicStackCapacity);
|
||||
var resultBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
var statusBuffer = accelerator.Allocate1D<byte>(1);
|
||||
|
||||
phiFactorBuffer.MemSetToZero();
|
||||
phiExponentBuffer.MemSetToZero();
|
||||
workFactorBuffer.MemSetToZero();
|
||||
workExponentBuffer.MemSetToZero();
|
||||
candidateBuffer.MemSetToZero();
|
||||
stackIndexBuffer.MemSetToZero();
|
||||
stackExponentBuffer.MemSetToZero();
|
||||
stackProductBuffer.MemSetToZero();
|
||||
resultBuffer.MemSetToZero();
|
||||
statusBuffer.MemSetToZero();
|
||||
|
||||
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
|
||||
byte hasPrevious = previousOrder.HasValue ? (byte)1 : (byte)0;
|
||||
ulong previousValue = previousOrder ?? 0UL;
|
||||
|
||||
var kernelConfig = new OrderKernelConfig(previousValue, hasPrevious, limit, config.MaxPowChecks, (int)config.Mode);
|
||||
var buffers = new OrderKernelBuffers(
|
||||
phiFactorBuffer.View,
|
||||
phiExponentBuffer.View,
|
||||
workFactorBuffer.View,
|
||||
workExponentBuffer.View,
|
||||
candidateBuffer.View,
|
||||
stackIndexBuffer.View,
|
||||
stackExponentBuffer.View,
|
||||
stackProductBuffer.View,
|
||||
resultBuffer.View,
|
||||
statusBuffer.View);
|
||||
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
prime,
|
||||
kernelConfig,
|
||||
divisorData,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
buffers);
|
||||
|
||||
stream.Synchronize();
|
||||
|
||||
byte status = 0;
|
||||
statusBuffer.View.CopyToCPU(ref status, 1);
|
||||
|
||||
PrimeOrderKernelStatus kernelStatus = (PrimeOrderKernelStatus)status;
|
||||
if (kernelStatus == PrimeOrderKernelStatus.Fallback)
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
|
||||
var kernel = GetOrderKernel(accelerator);
|
||||
SmallPrimeDeviceCache cache = GetSmallPrimeDeviceCache(accelerator);
|
||||
|
||||
using var phiFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
using var phiExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
using var workFactorBuffer = accelerator.Allocate1D<ulong>(GpuSmallPrimeFactorSlots);
|
||||
using var workExponentBuffer = accelerator.Allocate1D<int>(GpuSmallPrimeFactorSlots);
|
||||
using var candidateBuffer = accelerator.Allocate1D<ulong>(HeuristicCandidateLimit);
|
||||
using var stackIndexBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
using var stackExponentBuffer = accelerator.Allocate1D<int>(HeuristicStackCapacity);
|
||||
using var stackProductBuffer = accelerator.Allocate1D<ulong>(HeuristicStackCapacity);
|
||||
using var resultBuffer = accelerator.Allocate1D<ulong>(1);
|
||||
using var statusBuffer = accelerator.Allocate1D<byte>(1);
|
||||
|
||||
phiFactorBuffer.MemSetToZero();
|
||||
phiExponentBuffer.MemSetToZero();
|
||||
workFactorBuffer.MemSetToZero();
|
||||
workExponentBuffer.MemSetToZero();
|
||||
candidateBuffer.MemSetToZero();
|
||||
stackIndexBuffer.MemSetToZero();
|
||||
stackExponentBuffer.MemSetToZero();
|
||||
stackProductBuffer.MemSetToZero();
|
||||
resultBuffer.MemSetToZero();
|
||||
statusBuffer.MemSetToZero();
|
||||
|
||||
uint limit = config.SmallFactorLimit == 0 ? uint.MaxValue : config.SmallFactorLimit;
|
||||
byte hasPrevious = previousOrder.HasValue ? (byte)1 : (byte)0;
|
||||
ulong previousValue = previousOrder ?? 0UL;
|
||||
|
||||
var kernelConfig = new OrderKernelConfig(previousValue, hasPrevious, limit, config.MaxPowChecks, (int)config.Mode);
|
||||
var buffers = new OrderKernelBuffers(
|
||||
phiFactorBuffer.View,
|
||||
phiExponentBuffer.View,
|
||||
workFactorBuffer.View,
|
||||
workExponentBuffer.View,
|
||||
candidateBuffer.View,
|
||||
stackIndexBuffer.View,
|
||||
stackExponentBuffer.View,
|
||||
stackProductBuffer.View,
|
||||
resultBuffer.View,
|
||||
statusBuffer.View);
|
||||
|
||||
kernel(
|
||||
stream,
|
||||
1,
|
||||
prime,
|
||||
kernelConfig,
|
||||
divisorData,
|
||||
cache.Primes!.View,
|
||||
cache.Squares!.View,
|
||||
cache.Count,
|
||||
buffers);
|
||||
|
||||
stream.Synchronize();
|
||||
|
||||
byte status = 0;
|
||||
statusBuffer.View.CopyToCPU(ref status, 1);
|
||||
|
||||
PrimeOrderKernelStatus kernelStatus = (PrimeOrderKernelStatus)status;
|
||||
if (kernelStatus == PrimeOrderKernelStatus.Fallback)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kernelStatus == PrimeOrderKernelStatus.PollardOverflow)
|
||||
{
|
||||
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
||||
}
|
||||
|
||||
ulong order = 0UL;
|
||||
resultBuffer.View.CopyToCPU(ref order, 1);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderStatus finalStatus = kernelStatus == PrimeOrderKernelStatus.Found
|
||||
? PrimeOrderCalculator.PrimeOrderStatus.Found
|
||||
: PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved;
|
||||
|
||||
result = new PrimeOrderCalculator.PrimeOrderResult(finalStatus, order);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
{
|
||||
result = default;
|
||||
DisposeResources();
|
||||
lease.Dispose();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (kernelStatus == PrimeOrderKernelStatus.PollardOverflow)
|
||||
{
|
||||
DisposeResources();
|
||||
lease.Dispose();
|
||||
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
||||
}
|
||||
|
||||
ulong order = 0UL;
|
||||
resultBuffer.View.CopyToCPU(ref order, 1);
|
||||
|
||||
PrimeOrderCalculator.PrimeOrderStatus finalStatus = kernelStatus == PrimeOrderKernelStatus.Found
|
||||
? PrimeOrderCalculator.PrimeOrderStatus.Found
|
||||
: PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved;
|
||||
|
||||
result = new PrimeOrderCalculator.PrimeOrderResult(finalStatus, order);
|
||||
DisposeResources();
|
||||
lease.Dispose();
|
||||
|
||||
return true;
|
||||
|
||||
void DisposeResources()
|
||||
{
|
||||
phiFactorBuffer.Dispose();
|
||||
phiExponentBuffer.Dispose();
|
||||
workFactorBuffer.Dispose();
|
||||
workExponentBuffer.Dispose();
|
||||
candidateBuffer.Dispose();
|
||||
stackIndexBuffer.Dispose();
|
||||
stackExponentBuffer.Dispose();
|
||||
stackProductBuffer.Dispose();
|
||||
resultBuffer.Dispose();
|
||||
statusBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static OrderKernelLauncher GetOrderKernel(Accelerator accelerator)
|
||||
@@ -830,7 +812,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
ulong reduced = phi / factor;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (reduced.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -846,7 +828,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
if (residue == 1UL || residue == 7UL)
|
||||
{
|
||||
ulong half = phi >> 1;
|
||||
if (Pow2EqualsOneKernel(half, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(half, divisor))
|
||||
{
|
||||
order = half;
|
||||
}
|
||||
@@ -880,7 +862,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
ulong reduced = order / primeFactor;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||
{
|
||||
order = reduced;
|
||||
continue;
|
||||
@@ -911,7 +893,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!Pow2EqualsOneKernel(order, prime, divisor))
|
||||
if (!Pow2EqualsOneKernel(order, divisor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -955,7 +937,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1046,7 +1028,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (!Pow2EqualsOneKernel(candidate, prime, divisor))
|
||||
if (!Pow2EqualsOneKernel(candidate, divisor))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -1346,7 +1328,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (Pow2EqualsOneKernel(reduced, prime, divisor))
|
||||
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -1612,7 +1594,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
}
|
||||
|
||||
private static bool Pow2EqualsOneKernel(ulong exponent, ulong prime, in MontgomeryDivisorData divisor)
|
||||
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
||||
{
|
||||
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL;
|
||||
}
|
||||
@@ -1678,49 +1660,48 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return computed ? GpuPow2ModStatus.Success : GpuPow2ModStatus.Unavailable;
|
||||
}
|
||||
|
||||
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> results)
|
||||
private static bool TryComputeOnGpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var kernel = GetPow2ModKernel(accelerator);
|
||||
var exponentBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
var remainderBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
|
||||
exponentBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(exponents), exponents.Length);
|
||||
remainderBuffer.MemSetToZero();
|
||||
|
||||
try
|
||||
{
|
||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var kernel = GetPow2ModKernel(accelerator);
|
||||
using var exponentBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
using var remainderBuffer = accelerator.Allocate1D<ulong>(exponents.Length);
|
||||
|
||||
exponentBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(exponents), exponents.Length);
|
||||
remainderBuffer.MemSetToZero();
|
||||
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorDataCache.Get(prime);
|
||||
kernel(stream, exponents.Length, exponentBuffer.View, divisor, remainderBuffer.View);
|
||||
|
||||
stream.Synchronize();
|
||||
|
||||
remainderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), results.Length);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
lease.Dispose();
|
||||
}
|
||||
kernel(stream, exponents.Length, exponentBuffer.View, divisorData, remainderBuffer.View);
|
||||
stream.Synchronize();
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
catch (Exception)
|
||||
{
|
||||
ComputePow2ModCpu(exponents, prime, results);
|
||||
return true;
|
||||
Console.WriteLine($"Exception for {prime} and exponents: {string.Join(",", exponents.ToArray())}.");
|
||||
exponentBuffer.Dispose();
|
||||
remainderBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
lease.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
remainderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), exponents.Length);
|
||||
exponentBuffer.Dispose();
|
||||
remainderBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
lease.Dispose();
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ComputePow2ModCpu(ReadOnlySpan<ulong> exponents, ulong prime, Span<ulong> results)
|
||||
private static void ComputePow2ModCpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
|
||||
{
|
||||
int length = exponents.Length;
|
||||
for (int i = 0; i < length; i++)
|
||||
{
|
||||
results[i] = Pow2ModCpu(exponents[i], prime);
|
||||
results[i] = Pow2ModCpu(exponents[i], prime, divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1738,12 +1719,12 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
|
||||
try
|
||||
{
|
||||
using var execution = lease.EnterExecutionScope();
|
||||
var execution = lease.EnterExecutionScope();
|
||||
Accelerator accelerator = lease.Accelerator;
|
||||
AcceleratorStream stream = lease.Stream;
|
||||
var kernel = GetPow2ModWideKernel(accelerator);
|
||||
using var exponentBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
using var remainderBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
var exponentBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
var remainderBuffer = accelerator.Allocate1D<GpuUInt128>(length);
|
||||
|
||||
Span<GpuUInt128> exponentSpan = length <= WideStackThreshold
|
||||
? stackalloc GpuUInt128[length]
|
||||
@@ -1773,12 +1754,15 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
results[i] = (UInt128)resultSpan[i];
|
||||
}
|
||||
|
||||
exponentBuffer.Dispose();
|
||||
remainderBuffer.Dispose();
|
||||
execution.Dispose();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) when (ex is AcceleratorException or InternalCompilerException or NotSupportedException or InvalidOperationException or AggregateException)
|
||||
catch (Exception)
|
||||
{
|
||||
ComputePow2ModCpuWide(exponents, prime, results);
|
||||
return true;
|
||||
Console.WriteLine($"Exception when computing {prime} with exponents: {string.Join(", ", exponents.ToArray())}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -1805,15 +1789,14 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
}
|
||||
}
|
||||
|
||||
private static ulong Pow2ModCpu(ulong exponent, ulong modulus)
|
||||
private static ulong Pow2ModCpu(ulong exponent, ulong modulus, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (modulus <= 1UL)
|
||||
{
|
||||
return 0UL;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorDataCache.Get(modulus);
|
||||
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
|
||||
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false);
|
||||
}
|
||||
|
||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
||||
@@ -1848,39 +1831,68 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
remainders[index] = Pow2ModKernelCore(exponent, modulus);
|
||||
}
|
||||
|
||||
private static GpuUInt128[] InitializeOddPowersTable(GpuUInt128 baseValue, GpuUInt128 modulus, int oddPowerCount)
|
||||
{
|
||||
GpuUInt128[] result = new GpuUInt128[PerfectNumberConstants.MaxOddPowersCount];
|
||||
result[0] = baseValue;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder that follows.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
|
||||
// TODO: We can calculate baseValue % modulus before loop and use it to increase ladderEntry calculation speed - we'll reuse the base for incremental calculations.
|
||||
GpuUInt128 current = baseValue;
|
||||
|
||||
// We're manually assigning each field to prevent the compiler to initialize each field twice due to auto-initialization. We're using the action to lower the code base size.
|
||||
for (int i = 1; i < oddPowerCount; i++)
|
||||
{
|
||||
current.MulMod(baseValue, modulus);
|
||||
result[i] = current;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static GpuUInt128 Pow2ModKernelCore(GpuUInt128 exponent, GpuUInt128 modulus)
|
||||
{
|
||||
if (modulus == GpuUInt128.One)
|
||||
{
|
||||
return GpuUInt128.Zero;
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (modulus == GpuUInt128.One)
|
||||
// {
|
||||
// return GpuUInt128.Zero;
|
||||
// }
|
||||
|
||||
if (exponent.IsZero)
|
||||
{
|
||||
return GpuUInt128.One;
|
||||
}
|
||||
// This should never happen in production code.
|
||||
// if (exponent.IsZero)
|
||||
// {
|
||||
// return GpuUInt128.One;
|
||||
// }
|
||||
|
||||
GpuUInt128 baseValue = new GpuUInt128(2UL);
|
||||
if (baseValue.CompareTo(modulus) >= 0)
|
||||
{
|
||||
baseValue.Sub(modulus);
|
||||
}
|
||||
GpuUInt128 baseValue = UInt128Numbers.TwoGpu;
|
||||
|
||||
// This should never happen in production code - 2 should never be greater or equal modulus.
|
||||
// if (baseValue.CompareTo(modulus) >= 0)
|
||||
// {
|
||||
// baseValue.Sub(modulus);
|
||||
// }
|
||||
|
||||
if (ShouldUseSingleBit(exponent))
|
||||
{
|
||||
return Pow2MontgomeryModSingleBit(exponent, modulus, baseValue);
|
||||
}
|
||||
|
||||
int bitLength = exponent.GetBitLength();
|
||||
int windowSize = GetWindowSize(bitLength);
|
||||
int index = exponent.GetBitLength();
|
||||
int windowSize = GetWindowSize(index);
|
||||
index--;
|
||||
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
Pow2OddPowerTable oddPowers = default;
|
||||
InitializeOddPowers(ref oddPowers, baseValue, modulus, oddPowerCount);
|
||||
|
||||
GpuUInt128[] oddPowers = InitializeOddPowersTable(baseValue, modulus, oddPowerCount);
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
int index = bitLength - 1;
|
||||
|
||||
int windowStart;
|
||||
ulong windowValue;
|
||||
while (index >= 0)
|
||||
{
|
||||
if (!IsBitSet(exponent, index))
|
||||
@@ -1890,7 +1902,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
continue;
|
||||
}
|
||||
|
||||
int windowStart = index - windowSize + 1;
|
||||
windowStart = index - windowSize + 1;
|
||||
if (windowStart < 0)
|
||||
{
|
||||
windowStart = 0;
|
||||
@@ -1901,24 +1913,27 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
windowStart++;
|
||||
}
|
||||
|
||||
int windowBitCount = index - windowStart + 1;
|
||||
for (int square = 0; square < windowBitCount; square++)
|
||||
// We're reusing oddPowerCount as windowBitCount here to lower registry pressure & avoid additional allocation
|
||||
oddPowerCount = index - windowStart + 1;
|
||||
index = windowStart - 1;
|
||||
windowValue = ExtractWindowValue(exponent, windowStart, oddPowerCount);
|
||||
|
||||
// We're reusing windowStart as square here to lower registry pressure & avoid additional allocation
|
||||
for (windowStart = 0; windowStart < oddPowerCount; windowStart++)
|
||||
{
|
||||
result.MulMod(result, modulus);
|
||||
}
|
||||
|
||||
ulong windowValue = ExtractWindowValue(exponent, windowStart, windowBitCount);
|
||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||
GpuUInt128 factor = oddPowers[tableIndex];
|
||||
result.MulMod(factor, modulus);
|
||||
|
||||
index = windowStart - 1;
|
||||
// We're reusing windowStart as tableIndex here to lower registry pressure & avoid additional allocation
|
||||
windowStart = (int)((windowValue - 1UL) >> 1);
|
||||
result.MulMod(oddPowers[windowStart], modulus);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool ShouldUseSingleBit(in GpuUInt128 exponent) => exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static bool ShouldUseSingleBit(GpuUInt128 exponent) => exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold;
|
||||
|
||||
private static int GetWindowSize(int bitLength)
|
||||
{
|
||||
@@ -1950,23 +1965,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return Pow2WindowSizeBits;
|
||||
}
|
||||
|
||||
private static void InitializeOddPowers(ref Pow2OddPowerTable oddPowers, GpuUInt128 baseValue, GpuUInt128 modulus, int oddPowerCount)
|
||||
{
|
||||
oddPowers[0] = baseValue;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Reusing baseValue to hold base^2 for the shared odd-power ladder that follows.
|
||||
baseValue.MulMod(baseValue, modulus);
|
||||
for (int i = 1; i < oddPowerCount; i++)
|
||||
{
|
||||
oddPowers[i] = oddPowers[i - 1];
|
||||
oddPowers[i].MulMod(baseValue, modulus);
|
||||
}
|
||||
}
|
||||
|
||||
private static GpuUInt128 Pow2MontgomeryModSingleBit(GpuUInt128 exponent, GpuUInt128 modulus, GpuUInt128 baseValue)
|
||||
{
|
||||
GpuUInt128 result = GpuUInt128.One;
|
||||
@@ -1991,7 +1989,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return result;
|
||||
}
|
||||
|
||||
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
||||
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
||||
{
|
||||
if (bitIndex >= 64)
|
||||
{
|
||||
@@ -2001,14 +1999,13 @@ internal static partial class PrimeOrderGpuHeuristics
|
||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
||||
}
|
||||
|
||||
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||
{
|
||||
if (windowStart != 0)
|
||||
{
|
||||
GpuUInt128 shifted = exponent;
|
||||
shifted.ShiftRight(windowStart);
|
||||
exponent.ShiftRight(windowStart);
|
||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||
return shifted.Low & mask;
|
||||
return exponent.Low & mask;
|
||||
}
|
||||
|
||||
ulong directMask = (1UL << windowBitCount) - 1UL;
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace PerfectNumbers.Core.Gpu;
|
||||
|
||||
public readonly struct ReadOnlyGpuUInt128 : IEquatable<ReadOnlyGpuUInt128>
|
||||
{
|
||||
public readonly ulong High;
|
||||
public readonly ulong Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(ulong high, ulong low)
|
||||
{
|
||||
High = high;
|
||||
Low = low;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(ulong low)
|
||||
: this(0UL, low)
|
||||
{
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(UInt128 value)
|
||||
{
|
||||
High = (ulong)(value >> 64);
|
||||
Low = (ulong)value;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128(GpuUInt128 value)
|
||||
{
|
||||
High = value.High;
|
||||
Low = value.Low;
|
||||
}
|
||||
|
||||
public bool IsZero
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => High == 0UL && Low == 0UL;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int GetBitLength()
|
||||
{
|
||||
if (High != 0UL)
|
||||
{
|
||||
return 64 + (64 - BitOperations.LeadingZeroCount(High));
|
||||
}
|
||||
|
||||
if (Low == 0UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 64 - BitOperations.LeadingZeroCount(Low);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public GpuUInt128 ToMutable() => new(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public bool Equals(ReadOnlyGpuUInt128 other) => High == other.High && Low == other.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool Equals(object? obj) => obj is ReadOnlyGpuUInt128 other && Equals(other);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override int GetHashCode() => HashCode.Combine(High, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyGpuUInt128(ulong value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyGpuUInt128(UInt128 value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator ReadOnlyGpuUInt128(GpuUInt128 value) => new(value);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static implicit operator UInt128(ReadOnlyGpuUInt128 value) => ((UInt128)value.High << 64) | value.Low;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128 WithHigh(ulong high) => new(high, Low);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ReadOnlyGpuUInt128 WithLow(ulong low) => new(High, low);
|
||||
}
|
||||
@@ -20,6 +20,6 @@ public interface IMersenneNumberDivisorByDivisorTester
|
||||
|
||||
public interface IDivisorScanSession : IDisposable
|
||||
{
|
||||
void CheckDivisor(ulong divisor, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
|
||||
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large.
Load diff
@@ -2,7 +2,9 @@ using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using Open.Numeric.Primes;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
@@ -26,29 +28,97 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
|
||||
_ = threadCount;
|
||||
|
||||
candidates.Sort();
|
||||
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||
int candidateCount = candidateSpan.Length;
|
||||
|
||||
bool applyStartPrime = startPrime > 0UL;
|
||||
int skippedByPreviousResults = 0;
|
||||
List<ulong> primesToTest = new(candidates.Count);
|
||||
|
||||
if (previousResults is not null && previousResults.Count > 0)
|
||||
{
|
||||
int recordedCount = previousResults.Count;
|
||||
ulong[] recordedCandidates = ArrayPool<ulong>.Shared.Rent(recordedCount);
|
||||
try
|
||||
{
|
||||
Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
|
||||
int recordedIndex = 0;
|
||||
foreach (ulong value in previousResults.Keys)
|
||||
{
|
||||
recordedSpan[recordedIndex++] = value;
|
||||
}
|
||||
|
||||
recordedSpan.Sort();
|
||||
|
||||
int writeIndex = 0;
|
||||
int skipIndex = 0;
|
||||
|
||||
for (int readIndex = 0; readIndex < candidateCount; readIndex++)
|
||||
{
|
||||
ulong candidate = candidateSpan[readIndex];
|
||||
|
||||
while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate)
|
||||
{
|
||||
skipIndex++;
|
||||
}
|
||||
|
||||
if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate)
|
||||
{
|
||||
skippedByPreviousResults++;
|
||||
skipIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
candidateSpan[writeIndex++] = candidate;
|
||||
}
|
||||
|
||||
if (writeIndex < candidateCount)
|
||||
{
|
||||
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
|
||||
candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||
candidateCount = candidateSpan.Length;
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
|
||||
}
|
||||
}
|
||||
|
||||
if (skippedByPreviousResults > 0)
|
||||
{
|
||||
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
|
||||
}
|
||||
|
||||
int startIndex = 0;
|
||||
if (applyStartPrime)
|
||||
{
|
||||
startIndex = candidates.BinarySearch(startPrime);
|
||||
if (startIndex < 0)
|
||||
{
|
||||
startIndex = ~startIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime)
|
||||
{
|
||||
startIndex--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<ulong> primesToTest = candidates;
|
||||
Span<ulong> primesSpan = CollectionsMarshal.AsSpan(primesToTest);
|
||||
ulong maxPrime = 0UL;
|
||||
int primeWriteIndex = 0;
|
||||
|
||||
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
|
||||
bool hasPrime = primeEnumerator.MoveNext();
|
||||
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
|
||||
|
||||
for (int index = 0; index < candidates.Count; index++)
|
||||
for (int index = startIndex; index < candidateCount; index++)
|
||||
{
|
||||
ulong candidate = candidates[index];
|
||||
|
||||
if (applyStartPrime && candidate < startPrime)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (previousResults is not null && previousResults.ContainsKey(candidate))
|
||||
{
|
||||
skippedByPreviousResults++;
|
||||
continue;
|
||||
}
|
||||
ulong candidate = primesSpan[index];
|
||||
|
||||
if (candidate <= 1UL)
|
||||
{
|
||||
@@ -75,17 +145,17 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
continue;
|
||||
}
|
||||
|
||||
primesSpan[primeWriteIndex++] = candidate;
|
||||
|
||||
if (candidate > maxPrime)
|
||||
{
|
||||
maxPrime = candidate;
|
||||
}
|
||||
|
||||
primesToTest.Add(candidate);
|
||||
}
|
||||
|
||||
if (skippedByPreviousResults > 0)
|
||||
if (primeWriteIndex < primesToTest.Count)
|
||||
{
|
||||
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
|
||||
primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex);
|
||||
}
|
||||
|
||||
if (primesToTest.Count == 0)
|
||||
@@ -158,7 +228,13 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ulong prime in filteredPrimes)
|
||||
int workerCount = threadCount <= 0 ? Environment.ProcessorCount : threadCount;
|
||||
if (workerCount < 1)
|
||||
{
|
||||
workerCount = 1;
|
||||
}
|
||||
|
||||
void ProcessPrime(ulong prime)
|
||||
{
|
||||
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
|
||||
|
||||
@@ -166,11 +242,28 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
{
|
||||
markComposite();
|
||||
printResult(prime, true, true, false);
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
clearComposite();
|
||||
printResult(prime, true, divisorsExhausted, true);
|
||||
}
|
||||
|
||||
if (workerCount == 1)
|
||||
{
|
||||
foreach (ulong prime in filteredPrimes)
|
||||
{
|
||||
ProcessPrime(prime);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ParallelOptions options = new()
|
||||
{
|
||||
MaxDegreeOfParallelism = workerCount
|
||||
};
|
||||
|
||||
Parallel.ForEach(filteredPrimes, options, ProcessPrime);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
@@ -14,29 +14,6 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong
|
||||
|
||||
public readonly ulong MontgomeryTwo = montgomeryTwo;
|
||||
public readonly ulong MontgomeryTwoSquared = montgomeryTwoSquared;
|
||||
}
|
||||
|
||||
internal static class MontgomeryDivisorDataCache
|
||||
{
|
||||
private static readonly ConcurrentDictionary<ulong, MontgomeryDivisorData> Cache = new();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MontgomeryDivisorData Get(ulong modulus) => Cache.GetOrAdd(modulus, static m => Create(m));
|
||||
|
||||
private static MontgomeryDivisorData Create(ulong modulus)
|
||||
{
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ComputeMontgomeryResidue(UInt128 value, ulong modulus) => (ulong)(value % modulus);
|
||||
@@ -53,4 +30,47 @@ internal static class MontgomeryDivisorDataCache
|
||||
inv *= unchecked(2UL - modulus * inv);
|
||||
return unchecked(0UL - inv);
|
||||
}
|
||||
|
||||
public static MontgomeryDivisorData FromModulus(ulong modulus)
|
||||
{
|
||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||
ulong montgomeryOne = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64, modulus);
|
||||
ulong montgomeryTwo = ComputeMontgomeryResidue(UInt128Numbers.OneShiftedLeft64x2, modulus);
|
||||
ulong montgomeryTwoSquared = ULongExtensions.MontgomeryMultiply(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||
|
||||
return new MontgomeryDivisorData(
|
||||
modulus,
|
||||
nPrime,
|
||||
montgomeryOne,
|
||||
montgomeryTwo,
|
||||
montgomeryTwoSquared);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal static class MontgomeryDivisorDataCache
|
||||
{
|
||||
[ThreadStatic]
|
||||
private static bool s_hasCachedValue;
|
||||
|
||||
[ThreadStatic]
|
||||
private static ulong s_cachedModulus;
|
||||
|
||||
[ThreadStatic]
|
||||
private static MontgomeryDivisorData s_cachedData;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static MontgomeryDivisorData Get(ulong modulus)
|
||||
{
|
||||
if (s_hasCachedValue && s_cachedModulus == modulus)
|
||||
{
|
||||
return s_cachedData;
|
||||
}
|
||||
|
||||
MontgomeryDivisorData computed = MontgomeryDivisorData.FromModulus(modulus);
|
||||
s_cachedModulus = modulus;
|
||||
s_cachedData = computed;
|
||||
s_hasCachedValue = true;
|
||||
return computed;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ public static class PerfectNumberConstants
|
||||
public const ulong ExtraDivisorCycleSearchLimit = 64UL;
|
||||
public const int MaxQForDivisorCycles = 4_000_000;
|
||||
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
||||
public const int MaxOddPowersCount = 128;
|
||||
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
||||
// divisor-cycle datasets we generate offline.
|
||||
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
<ItemGroup>
|
||||
<InternalsVisibleTo Include="EvenPerfectBitScanner.Tests" />
|
||||
<InternalsVisibleTo Include="PerfectNumbers.Core.Tests" />
|
||||
<InternalsVisibleTo Include="ILGPURuntime" />
|
||||
<InternalsVisibleTo Include="HeuristicMultiplyShiftTiming" />
|
||||
<InternalsVisibleTo Include="EvenPerfectBitScanner.Benchmarks" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -32,6 +32,7 @@ internal static partial class PrimeOrderCalculator
|
||||
PrimeOrderHeuristicDevice device)
|
||||
{
|
||||
using var scope = UsePow2Mode(device);
|
||||
MontgomeryDivisorData divisorData;
|
||||
if (prime <= ulong.MaxValue)
|
||||
{
|
||||
ulong? previous = null;
|
||||
@@ -48,14 +49,20 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
PrimeOrderResult result = Calculate((ulong)prime, previous, config, device);
|
||||
ulong prime64 = (ulong)prime;
|
||||
divisorData = MontgomeryDivisorData.FromModulus(prime64);
|
||||
PrimeOrderResult result = Calculate(prime64, previous, divisorData, config, device);
|
||||
return new PrimeOrderResultWide(result.Status, result.Order);
|
||||
}
|
||||
else
|
||||
{
|
||||
divisorData = default;
|
||||
}
|
||||
|
||||
return CalculateWideInternal(prime, previousOrder, config);
|
||||
return CalculateWideInternal(prime, previousOrder, divisorData, config);
|
||||
}
|
||||
|
||||
private static PrimeOrderResultWide CalculateWideInternal(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
||||
private static PrimeOrderResultWide CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (prime <= UInt128.One)
|
||||
{
|
||||
@@ -72,53 +79,54 @@ internal static partial class PrimeOrderCalculator
|
||||
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
||||
if (phiFactors.Factors is null)
|
||||
{
|
||||
return FinishStrictlyWide(prime, config.Mode);
|
||||
return FinishStrictlyWide(prime, divisorData, config.Mode);
|
||||
}
|
||||
|
||||
return RunHeuristicPipelineWide(prime, previousOrder, config, phi, phiFactors);
|
||||
return RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
||||
}
|
||||
|
||||
private static PrimeOrderResultWide RunHeuristicPipelineWide(
|
||||
UInt128 prime,
|
||||
UInt128? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
PrimeOrderSearchConfig config,
|
||||
UInt128 phi,
|
||||
PartialFactorResult128 phiFactors)
|
||||
{
|
||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, phiFactors))
|
||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, phi);
|
||||
}
|
||||
|
||||
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi);
|
||||
candidateOrder = ExponentLoweringWide(candidateOrder, prime, phiFactors);
|
||||
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
||||
candidateOrder = ExponentLoweringWide(candidateOrder, prime, divisorData, phiFactors);
|
||||
|
||||
if (TryConfirmOrderWide(prime, candidateOrder, config))
|
||||
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, candidateOrder);
|
||||
}
|
||||
|
||||
if (config.Mode == PrimeOrderMode.Strict)
|
||||
{
|
||||
return FinishStrictlyWide(prime, PrimeOrderMode.Strict);
|
||||
return FinishStrictlyWide(prime, divisorData, PrimeOrderMode.Strict);
|
||||
}
|
||||
|
||||
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, config, out UInt128 order))
|
||||
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
||||
{
|
||||
return new PrimeOrderResultWide(PrimeOrderStatus.Found, order);
|
||||
}
|
||||
|
||||
return FinishStrictlyWide(prime, config.Mode);
|
||||
return FinishStrictlyWide(prime, divisorData, config.Mode);
|
||||
}
|
||||
|
||||
private static PrimeOrderResultWide FinishStrictlyWide(UInt128 prime, PrimeOrderMode mode)
|
||||
private static PrimeOrderResultWide FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData, PrimeOrderMode mode)
|
||||
{
|
||||
UInt128 strictOrder = CalculateByFactorizationWide(prime);
|
||||
UInt128 strictOrder = CalculateByFactorizationWide(prime, divisorData);
|
||||
PrimeOrderStatus status = mode == PrimeOrderMode.Strict ? PrimeOrderStatus.Found : PrimeOrderStatus.HeuristicUnresolved;
|
||||
return new PrimeOrderResultWide(status, strictOrder);
|
||||
}
|
||||
|
||||
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, PartialFactorResult128 factors)
|
||||
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
||||
{
|
||||
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
|
||||
int length = factors.Count;
|
||||
@@ -126,7 +134,7 @@ internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
UInt128 factor = factorSpan[i].Value;
|
||||
UInt128 reduced = phi / factor;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -135,14 +143,14 @@ internal static partial class PrimeOrderCalculator
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UInt128 InitializeStartingOrderWide(UInt128 prime, UInt128 phi)
|
||||
private static UInt128 InitializeStartingOrderWide(UInt128 prime, UInt128 phi, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
UInt128 order = phi;
|
||||
UInt128 mod8 = prime & (UInt128)7UL;
|
||||
if (mod8 == UInt128.One || mod8 == (UInt128)7UL)
|
||||
{
|
||||
UInt128 half = phi >> 1;
|
||||
if (Pow2ModWide(half, prime) == UInt128.One)
|
||||
if (Pow2ModWide(half, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = half;
|
||||
}
|
||||
@@ -151,7 +159,7 @@ internal static partial class PrimeOrderCalculator
|
||||
return order;
|
||||
}
|
||||
|
||||
private static UInt128 ExponentLoweringWide(UInt128 order, UInt128 prime, PartialFactorResult128 factors)
|
||||
private static UInt128 ExponentLoweringWide(UInt128 order, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
||||
{
|
||||
FactorEntry128[]? tempArray = null;
|
||||
try
|
||||
@@ -184,7 +192,7 @@ internal static partial class PrimeOrderCalculator
|
||||
if ((order % primeFactor) == UInt128.Zero)
|
||||
{
|
||||
UInt128 reduced = order / primeFactor;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = reduced;
|
||||
continue;
|
||||
@@ -206,14 +214,14 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConfirmOrderWide(UInt128 prime, UInt128 order, PrimeOrderSearchConfig config)
|
||||
private static bool TryConfirmOrderWide(UInt128 prime, UInt128 order, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (order == UInt128.Zero)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Pow2ModWide(order, prime) != UInt128.One)
|
||||
if (Pow2ModWide(order, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -253,7 +261,7 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
|
||||
reduced /= primeFactor;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -267,6 +275,7 @@ internal static partial class PrimeOrderCalculator
|
||||
UInt128 prime,
|
||||
UInt128 order,
|
||||
UInt128? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
PrimeOrderSearchConfig config,
|
||||
out UInt128 result)
|
||||
{
|
||||
@@ -322,12 +331,12 @@ internal static partial class PrimeOrderCalculator
|
||||
UInt128 candidate = candidates[i];
|
||||
powUsed++;
|
||||
|
||||
if (Pow2ModWide(candidate, prime) != UInt128.One)
|
||||
if (Pow2ModWide(candidate, prime, divisorData) != UInt128.One)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryConfirmCandidateWide(prime, candidate, config, ref powUsed, powBudget))
|
||||
if (!TryConfirmCandidateWide(prime, candidate, config, ref powUsed, powBudget, divisorData))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -511,7 +520,7 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
private static bool TryConfirmCandidateWide(UInt128 prime, UInt128 candidate, PrimeOrderSearchConfig config, ref int powUsed, int powBudget)
|
||||
private static bool TryConfirmCandidateWide(UInt128 prime, UInt128 candidate, PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
|
||||
if (factorization.Factors is null)
|
||||
@@ -554,7 +563,7 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
|
||||
powUsed++;
|
||||
if (Pow2ModWide(reduced, prime) == UInt128.One)
|
||||
if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
@@ -826,7 +835,7 @@ internal static partial class PrimeOrderCalculator
|
||||
return 64 + BitOperations.TrailingZeroCount(high);
|
||||
}
|
||||
|
||||
private static UInt128 CalculateByFactorizationWide(UInt128 prime)
|
||||
private static UInt128 CalculateByFactorizationWide(in UInt128 prime, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
UInt128 phi = prime - UInt128.One;
|
||||
Dictionary<UInt128, int> counts = new(capacity: 8);
|
||||
@@ -850,7 +859,7 @@ internal static partial class PrimeOrderCalculator
|
||||
if ((order % primeFactor) != UInt128.Zero)
|
||||
{
|
||||
UInt128 candidate = order / primeFactor;
|
||||
if (Pow2ModWide(candidate, prime) == UInt128.One)
|
||||
if (Pow2ModWide(candidate, prime, divisorData) == UInt128.One)
|
||||
{
|
||||
order = candidate;
|
||||
}
|
||||
@@ -996,7 +1005,7 @@ internal static partial class PrimeOrderCalculator
|
||||
return true;
|
||||
}
|
||||
|
||||
private static UInt128 Pow2ModWide(UInt128 exponent, UInt128 modulus)
|
||||
private static UInt128 Pow2ModWide(UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (modulus == UInt128.One)
|
||||
{
|
||||
@@ -1009,7 +1018,7 @@ internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
ulong prime64 = (ulong)modulus;
|
||||
ulong exponent64 = (ulong)exponent;
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent64, prime64, out ulong remainder);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent64, prime64, out ulong remainder, divisorData);
|
||||
if (status == GpuPow2ModStatus.Success)
|
||||
{
|
||||
return remainder;
|
||||
|
||||
@@ -146,20 +146,21 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
}
|
||||
|
||||
public static PrimeOrderResult Calculate(ulong prime, ulong? previousOrder, PrimeOrderSearchConfig config)
|
||||
=> Calculate(prime, previousOrder, config, PrimeOrderHeuristicDevice.Gpu);
|
||||
public static PrimeOrderResult Calculate(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||
=> Calculate(prime, previousOrder, divisorData, config, PrimeOrderHeuristicDevice.Gpu);
|
||||
|
||||
public static PrimeOrderResult Calculate(
|
||||
ulong prime,
|
||||
ulong? previousOrder,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
PrimeOrderSearchConfig config,
|
||||
PrimeOrderHeuristicDevice device)
|
||||
{
|
||||
using var scope = UsePow2Mode(device);
|
||||
return CalculateInternal(prime, previousOrder, config);
|
||||
return CalculateInternal(prime, previousOrder, divisorData, config);
|
||||
}
|
||||
|
||||
private static PrimeOrderResult CalculateInternal(ulong prime, ulong? previousOrder, PrimeOrderSearchConfig config)
|
||||
private static PrimeOrderResult CalculateInternal(ulong prime, ulong? previousOrder, MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||
{
|
||||
if (prime <= 3UL)
|
||||
{
|
||||
@@ -167,7 +168,6 @@ internal static partial class PrimeOrderCalculator
|
||||
}
|
||||
|
||||
ulong phi = prime - 1UL;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(prime);
|
||||
|
||||
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder, config, divisorData, out PrimeOrderResult gpuResult))
|
||||
{
|
||||
@@ -448,8 +448,8 @@ internal static partial class PrimeOrderCalculator
|
||||
|
||||
DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
|
||||
int index = 0;
|
||||
const int MaxGpuBatchSize = 256;
|
||||
const int StackGpuBatchSize = 32;
|
||||
const int MaxGpuBatchSize = 1024;
|
||||
const int StackGpuBatchSize = 1024;
|
||||
Span<ulong> stackGpuRemainders = stackalloc ulong[StackGpuBatchSize];
|
||||
while (index < candidateCount && powUsed < powBudget)
|
||||
{
|
||||
@@ -473,7 +473,7 @@ internal static partial class PrimeOrderCalculator
|
||||
if (batchSize <= StackGpuBatchSize)
|
||||
{
|
||||
Span<ulong> localRemainders = stackGpuRemainders.Slice(0, batchSize);
|
||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders);
|
||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders, divisorData);
|
||||
if (status == GpuPow2ModStatus.Success)
|
||||
{
|
||||
gpuSuccess = true;
|
||||
@@ -484,7 +484,7 @@ internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
gpuPool = ArrayPool<ulong>.Shared.Rent(batchSize);
|
||||
Span<ulong> pooledRemainders = gpuPool.AsSpan(0, batchSize);
|
||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders);
|
||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders, divisorData);
|
||||
if (status == GpuPow2ModStatus.Success)
|
||||
{
|
||||
pooledGpuRemainders = pooledRemainders;
|
||||
@@ -775,7 +775,7 @@ internal static partial class PrimeOrderCalculator
|
||||
{
|
||||
if (IsGpuPow2Allowed)
|
||||
{
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder);
|
||||
GpuPow2ModStatus status = PrimeOrderGpuHeuristics.TryPow2Mod(exponent, prime, out ulong remainder, divisorData);
|
||||
if (status == GpuPow2ModStatus.Success)
|
||||
{
|
||||
return remainder == 1UL;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace PerfectNumbers.Core
|
||||
{
|
||||
public static class UInt128Numbers
|
||||
@@ -9,6 +11,8 @@ namespace PerfectNumbers.Core
|
||||
public static readonly UInt128 OneShiftedLeft64 = UInt128.One << 64;
|
||||
public static readonly UInt128 OneShiftedLeft64x2 = Two * OneShiftedLeft64;
|
||||
|
||||
public static readonly GpuUInt128 TwoGpu = new(2UL);
|
||||
|
||||
// TODO: Populate this table from the UInt128 residue benchmarks so GPU kernels can pull precomputed constants
|
||||
// without re-deriving them at runtime.
|
||||
// TODO: Extend the table with cached Mod3/Mod5 folding constants so both CPU and GPU residue helpers can reuse
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using ILGPU.Algorithms;
|
||||
using PerfectNumbers.Core.Gpu;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
@@ -11,412 +12,6 @@ public static class ULongExtensions
|
||||
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
||||
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSize - 1);
|
||||
|
||||
private struct Pow2OddPowerTable
|
||||
{
|
||||
public ulong Element0;
|
||||
public ulong Element1;
|
||||
public ulong Element2;
|
||||
public ulong Element3;
|
||||
public ulong Element4;
|
||||
public ulong Element5;
|
||||
public ulong Element6;
|
||||
public ulong Element7;
|
||||
public ulong Element8;
|
||||
public ulong Element9;
|
||||
public ulong Element10;
|
||||
public ulong Element11;
|
||||
public ulong Element12;
|
||||
public ulong Element13;
|
||||
public ulong Element14;
|
||||
public ulong Element15;
|
||||
public ulong Element16;
|
||||
public ulong Element17;
|
||||
public ulong Element18;
|
||||
public ulong Element19;
|
||||
public ulong Element20;
|
||||
public ulong Element21;
|
||||
public ulong Element22;
|
||||
public ulong Element23;
|
||||
public ulong Element24;
|
||||
public ulong Element25;
|
||||
public ulong Element26;
|
||||
public ulong Element27;
|
||||
public ulong Element28;
|
||||
public ulong Element29;
|
||||
public ulong Element30;
|
||||
public ulong Element31;
|
||||
public ulong Element32;
|
||||
public ulong Element33;
|
||||
public ulong Element34;
|
||||
public ulong Element35;
|
||||
public ulong Element36;
|
||||
public ulong Element37;
|
||||
public ulong Element38;
|
||||
public ulong Element39;
|
||||
public ulong Element40;
|
||||
public ulong Element41;
|
||||
public ulong Element42;
|
||||
public ulong Element43;
|
||||
public ulong Element44;
|
||||
public ulong Element45;
|
||||
public ulong Element46;
|
||||
public ulong Element47;
|
||||
public ulong Element48;
|
||||
public ulong Element49;
|
||||
public ulong Element50;
|
||||
public ulong Element51;
|
||||
public ulong Element52;
|
||||
public ulong Element53;
|
||||
public ulong Element54;
|
||||
public ulong Element55;
|
||||
public ulong Element56;
|
||||
public ulong Element57;
|
||||
public ulong Element58;
|
||||
public ulong Element59;
|
||||
public ulong Element60;
|
||||
public ulong Element61;
|
||||
public ulong Element62;
|
||||
public ulong Element63;
|
||||
public ulong Element64;
|
||||
public ulong Element65;
|
||||
public ulong Element66;
|
||||
public ulong Element67;
|
||||
public ulong Element68;
|
||||
public ulong Element69;
|
||||
public ulong Element70;
|
||||
public ulong Element71;
|
||||
public ulong Element72;
|
||||
public ulong Element73;
|
||||
public ulong Element74;
|
||||
public ulong Element75;
|
||||
public ulong Element76;
|
||||
public ulong Element77;
|
||||
public ulong Element78;
|
||||
public ulong Element79;
|
||||
public ulong Element80;
|
||||
public ulong Element81;
|
||||
public ulong Element82;
|
||||
public ulong Element83;
|
||||
public ulong Element84;
|
||||
public ulong Element85;
|
||||
public ulong Element86;
|
||||
public ulong Element87;
|
||||
public ulong Element88;
|
||||
public ulong Element89;
|
||||
public ulong Element90;
|
||||
public ulong Element91;
|
||||
public ulong Element92;
|
||||
public ulong Element93;
|
||||
public ulong Element94;
|
||||
public ulong Element95;
|
||||
public ulong Element96;
|
||||
public ulong Element97;
|
||||
public ulong Element98;
|
||||
public ulong Element99;
|
||||
public ulong Element100;
|
||||
public ulong Element101;
|
||||
public ulong Element102;
|
||||
public ulong Element103;
|
||||
public ulong Element104;
|
||||
public ulong Element105;
|
||||
public ulong Element106;
|
||||
public ulong Element107;
|
||||
public ulong Element108;
|
||||
public ulong Element109;
|
||||
public ulong Element110;
|
||||
public ulong Element111;
|
||||
public ulong Element112;
|
||||
public ulong Element113;
|
||||
public ulong Element114;
|
||||
public ulong Element115;
|
||||
public ulong Element116;
|
||||
public ulong Element117;
|
||||
public ulong Element118;
|
||||
public ulong Element119;
|
||||
public ulong Element120;
|
||||
public ulong Element121;
|
||||
public ulong Element122;
|
||||
public ulong Element123;
|
||||
public ulong Element124;
|
||||
public ulong Element125;
|
||||
public ulong Element126;
|
||||
public ulong Element127;
|
||||
|
||||
public ref ulong this[int index]
|
||||
{
|
||||
get
|
||||
{
|
||||
return ref GetElementRef(index);
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private ref ulong GetElementRef(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0:
|
||||
return ref Element0;
|
||||
case 1:
|
||||
return ref Element1;
|
||||
case 2:
|
||||
return ref Element2;
|
||||
case 3:
|
||||
return ref Element3;
|
||||
case 4:
|
||||
return ref Element4;
|
||||
case 5:
|
||||
return ref Element5;
|
||||
case 6:
|
||||
return ref Element6;
|
||||
case 7:
|
||||
return ref Element7;
|
||||
case 8:
|
||||
return ref Element8;
|
||||
case 9:
|
||||
return ref Element9;
|
||||
case 10:
|
||||
return ref Element10;
|
||||
case 11:
|
||||
return ref Element11;
|
||||
case 12:
|
||||
return ref Element12;
|
||||
case 13:
|
||||
return ref Element13;
|
||||
case 14:
|
||||
return ref Element14;
|
||||
case 15:
|
||||
return ref Element15;
|
||||
case 16:
|
||||
return ref Element16;
|
||||
case 17:
|
||||
return ref Element17;
|
||||
case 18:
|
||||
return ref Element18;
|
||||
case 19:
|
||||
return ref Element19;
|
||||
case 20:
|
||||
return ref Element20;
|
||||
case 21:
|
||||
return ref Element21;
|
||||
case 22:
|
||||
return ref Element22;
|
||||
case 23:
|
||||
return ref Element23;
|
||||
case 24:
|
||||
return ref Element24;
|
||||
case 25:
|
||||
return ref Element25;
|
||||
case 26:
|
||||
return ref Element26;
|
||||
case 27:
|
||||
return ref Element27;
|
||||
case 28:
|
||||
return ref Element28;
|
||||
case 29:
|
||||
return ref Element29;
|
||||
case 30:
|
||||
return ref Element30;
|
||||
case 31:
|
||||
return ref Element31;
|
||||
case 32:
|
||||
return ref Element32;
|
||||
case 33:
|
||||
return ref Element33;
|
||||
case 34:
|
||||
return ref Element34;
|
||||
case 35:
|
||||
return ref Element35;
|
||||
case 36:
|
||||
return ref Element36;
|
||||
case 37:
|
||||
return ref Element37;
|
||||
case 38:
|
||||
return ref Element38;
|
||||
case 39:
|
||||
return ref Element39;
|
||||
case 40:
|
||||
return ref Element40;
|
||||
case 41:
|
||||
return ref Element41;
|
||||
case 42:
|
||||
return ref Element42;
|
||||
case 43:
|
||||
return ref Element43;
|
||||
case 44:
|
||||
return ref Element44;
|
||||
case 45:
|
||||
return ref Element45;
|
||||
case 46:
|
||||
return ref Element46;
|
||||
case 47:
|
||||
return ref Element47;
|
||||
case 48:
|
||||
return ref Element48;
|
||||
case 49:
|
||||
return ref Element49;
|
||||
case 50:
|
||||
return ref Element50;
|
||||
case 51:
|
||||
return ref Element51;
|
||||
case 52:
|
||||
return ref Element52;
|
||||
case 53:
|
||||
return ref Element53;
|
||||
case 54:
|
||||
return ref Element54;
|
||||
case 55:
|
||||
return ref Element55;
|
||||
case 56:
|
||||
return ref Element56;
|
||||
case 57:
|
||||
return ref Element57;
|
||||
case 58:
|
||||
return ref Element58;
|
||||
case 59:
|
||||
return ref Element59;
|
||||
case 60:
|
||||
return ref Element60;
|
||||
case 61:
|
||||
return ref Element61;
|
||||
case 62:
|
||||
return ref Element62;
|
||||
case 63:
|
||||
return ref Element63;
|
||||
case 64:
|
||||
return ref Element64;
|
||||
case 65:
|
||||
return ref Element65;
|
||||
case 66:
|
||||
return ref Element66;
|
||||
case 67:
|
||||
return ref Element67;
|
||||
case 68:
|
||||
return ref Element68;
|
||||
case 69:
|
||||
return ref Element69;
|
||||
case 70:
|
||||
return ref Element70;
|
||||
case 71:
|
||||
return ref Element71;
|
||||
case 72:
|
||||
return ref Element72;
|
||||
case 73:
|
||||
return ref Element73;
|
||||
case 74:
|
||||
return ref Element74;
|
||||
case 75:
|
||||
return ref Element75;
|
||||
case 76:
|
||||
return ref Element76;
|
||||
case 77:
|
||||
return ref Element77;
|
||||
case 78:
|
||||
return ref Element78;
|
||||
case 79:
|
||||
return ref Element79;
|
||||
case 80:
|
||||
return ref Element80;
|
||||
case 81:
|
||||
return ref Element81;
|
||||
case 82:
|
||||
return ref Element82;
|
||||
case 83:
|
||||
return ref Element83;
|
||||
case 84:
|
||||
return ref Element84;
|
||||
case 85:
|
||||
return ref Element85;
|
||||
case 86:
|
||||
return ref Element86;
|
||||
case 87:
|
||||
return ref Element87;
|
||||
case 88:
|
||||
return ref Element88;
|
||||
case 89:
|
||||
return ref Element89;
|
||||
case 90:
|
||||
return ref Element90;
|
||||
case 91:
|
||||
return ref Element91;
|
||||
case 92:
|
||||
return ref Element92;
|
||||
case 93:
|
||||
return ref Element93;
|
||||
case 94:
|
||||
return ref Element94;
|
||||
case 95:
|
||||
return ref Element95;
|
||||
case 96:
|
||||
return ref Element96;
|
||||
case 97:
|
||||
return ref Element97;
|
||||
case 98:
|
||||
return ref Element98;
|
||||
case 99:
|
||||
return ref Element99;
|
||||
case 100:
|
||||
return ref Element100;
|
||||
case 101:
|
||||
return ref Element101;
|
||||
case 102:
|
||||
return ref Element102;
|
||||
case 103:
|
||||
return ref Element103;
|
||||
case 104:
|
||||
return ref Element104;
|
||||
case 105:
|
||||
return ref Element105;
|
||||
case 106:
|
||||
return ref Element106;
|
||||
case 107:
|
||||
return ref Element107;
|
||||
case 108:
|
||||
return ref Element108;
|
||||
case 109:
|
||||
return ref Element109;
|
||||
case 110:
|
||||
return ref Element110;
|
||||
case 111:
|
||||
return ref Element111;
|
||||
case 112:
|
||||
return ref Element112;
|
||||
case 113:
|
||||
return ref Element113;
|
||||
case 114:
|
||||
return ref Element114;
|
||||
case 115:
|
||||
return ref Element115;
|
||||
case 116:
|
||||
return ref Element116;
|
||||
case 117:
|
||||
return ref Element117;
|
||||
case 118:
|
||||
return ref Element118;
|
||||
case 119:
|
||||
return ref Element119;
|
||||
case 120:
|
||||
return ref Element120;
|
||||
case 121:
|
||||
return ref Element121;
|
||||
case 122:
|
||||
return ref Element122;
|
||||
case 123:
|
||||
return ref Element123;
|
||||
case 124:
|
||||
return ref Element124;
|
||||
case 125:
|
||||
return ref Element125;
|
||||
case 126:
|
||||
return ref Element126;
|
||||
case 127:
|
||||
return ref Element127;
|
||||
default:
|
||||
return ref Element0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong CalculateOrder(this ulong q)
|
||||
{
|
||||
if (q <= 2UL)
|
||||
@@ -744,14 +339,13 @@ public static class ULongExtensions
|
||||
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
|
||||
}
|
||||
|
||||
int bitLength = BitOperations.Log2(exponent) + 1;
|
||||
int bitLength = GetPortableBitLength(exponent);
|
||||
int windowSize = GetWindowSize(bitLength);
|
||||
int oddPowerCount = 1 << (windowSize - 1);
|
||||
|
||||
ulong result = divisor.MontgomeryOne;
|
||||
ulong nPrime = divisor.NPrime;
|
||||
Pow2OddPowerTable oddPowers = default;
|
||||
InitializeMontgomeryOddPowers(divisor, modulus, nPrime, ref oddPowers, oddPowerCount);
|
||||
ulong[] oddPowers = InitializeMontgomeryOddPowers(divisor, modulus, nPrime, oddPowerCount);
|
||||
|
||||
int index = bitLength - 1;
|
||||
while (index >= 0)
|
||||
@@ -826,6 +420,16 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int GetPortableBitLength(ulong value)
|
||||
{
|
||||
if (value == 0UL)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 64 - XMath.LeadingZeroCount(value);
|
||||
}
|
||||
|
||||
private static int GetWindowSize(int bitLength)
|
||||
{
|
||||
if (bitLength <= 6)
|
||||
@@ -857,12 +461,13 @@ public static class ULongExtensions
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void InitializeMontgomeryOddPowers(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, ref Pow2OddPowerTable oddPowers, int oddPowerCount)
|
||||
private static ulong[] InitializeMontgomeryOddPowers(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||
{
|
||||
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
||||
oddPowers[0] = divisor.MontgomeryTwo;
|
||||
if (oddPowerCount == 1)
|
||||
{
|
||||
return;
|
||||
return oddPowers;
|
||||
}
|
||||
|
||||
ulong square = divisor.MontgomeryTwoSquared;
|
||||
@@ -871,6 +476,8 @@ public static class ULongExtensions
|
||||
ulong previous = oddPowers[i - 1];
|
||||
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
||||
}
|
||||
|
||||
return oddPowers;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
- implement full routing for CPU or GPU execution based on the (...)-device=cpu|gpu switches. The current --mersenne=bydivisor GPU flow appears to run every stage on the GPU and ignore switches such as --primes-device for the primality check.
|
||||
- investigate why the --mersenne=bydivisor GPU path runs out of host memory. Exceptions must remain visible.
|
||||
- Reimplement the GPU cycle heuristics for MersenneNumberIncrementalGpuTester and re-enable Scan_handles_various_prime_exponents once the device path is fixed.
|
||||
- Extend the GPU numeric type probe to cover proposed custom types such as GpuUInt256 and GpuUInt512 once their definitions are available so we can plan the wide-order heuristics on the device.
|
||||
- document the GPU by-divisor regressions introduced in commit cd1debe ("Various micro- and medium- optimizations"): the refactor dropped divisor-cycle caching, remainder stepper reuse, and honoring the cached cycle parameter so we can plan which pieces to restore.
|
||||
+591
@@ -0,0 +1,591 @@
|
||||
EvenPerfectBitScanner\Program.cs:69: bool useModuloWorkaround = false; // TODO: Remove once the runtime defaults to the ImmediateModulo path measured fastest in GpuUInt128NativeModuloBenchmarks.
|
||||
EvenPerfectBitScanner\Program.cs:78: bool useDivisorCycles = false; // TODO: Default this flag to true and remove the opt-out once divisor cycle lookups are mandatory per the optimized by-divisor benchmarks.
|
||||
EvenPerfectBitScanner\Program.cs:109: // TODO: Replace this ulong.Parse with the Utf8Parser-based span helper once CLI option parsing
|
||||
EvenPerfectBitScanner\Program.cs:124: // TODO: Swap int.Parse for the span-based Utf8Parser fast path to avoid transient strings when
|
||||
EvenPerfectBitScanner\Program.cs:133: kernelType = GpuKernelType.Pow2Mod; // TODO: Switch this mode to the ProcessEightBitWindows kernel once Pow2Minus1Mod adopts the benchmarked faster windowed ladder.
|
||||
EvenPerfectBitScanner\Program.cs:164: // TODO: Replace UInt128.Parse with the benchmarked fast-path parser once we expose the
|
||||
EvenPerfectBitScanner\Program.cs:177: // TODO: Replace ulong.TryParse with the Utf8Parser fast-path once CLI parsing utilities expose
|
||||
EvenPerfectBitScanner\Program.cs:186: // TODO: Swap this TryParse for the zero-allocation Utf8Parser helper to keep residue CLI option
|
||||
EvenPerfectBitScanner\Program.cs:211: // TODO: Use the Utf8Parser-based reader here to eliminate the temporary substring allocation
|
||||
EvenPerfectBitScanner\Program.cs:273: // TODO: Replace this TryParse with the Utf8Parser span helper once shared CLI parsing utilities
|
||||
EvenPerfectBitScanner\Program.cs:288: // TODO: Swap to the Utf8Parser double fast-path so zero-hard parsing avoids culture-dependent
|
||||
EvenPerfectBitScanner\Program.cs:304: // TODO: Replace these double/int TryParse calls with span-based Utf8Parser helpers once
|
||||
EvenPerfectBitScanner\Program.cs:338: // TODO: Convert this int.Parse to the Utf8Parser-based helper to keep CLI option parsing
|
||||
EvenPerfectBitScanner\Program.cs:344: // TODO: Swap int.Parse for Utf8Parser to align with the faster CLI numeric parsing path.
|
||||
EvenPerfectBitScanner\Program.cs:349: // TODO: Replace int.Parse with Utf8Parser-based parsing to eliminate temporary strings when
|
||||
EvenPerfectBitScanner\Program.cs:355: // TODO: Use the Utf8Parser-based fast path here to match the other CLI numeric parsing
|
||||
EvenPerfectBitScanner\Program.cs:361: // TODO: Inline the Utf8Parser-based int reader here once shared CLI parsing utilities land so
|
||||
EvenPerfectBitScanner\Program.cs:371: // TODO: Replace int.Parse with the shared Utf8Parser fast-path to keep hot CLI option parsing
|
||||
EvenPerfectBitScanner\Program.cs:385: // TODO: Convert this int.Parse to Utf8Parser once the optimized CLI parsing helper is shared
|
||||
EvenPerfectBitScanner\Program.cs:443: // TODO: Wire GenerateGpu to the unrolled-hex kernel that led the MersenneDivisorCycleLengthGpuBenchmarks once it lands.
|
||||
EvenPerfectBitScanner\Program.cs:464: // TODO: Keep a single cached block loaded from disk and honor the configured device when
|
||||
EvenPerfectBitScanner\Program.cs:468: // TODO: Stop reloading the full snapshot once the ad-hoc path streams results straight from
|
||||
EvenPerfectBitScanner\Program.cs:504: // TODO: Swap the underlying pow2mod kernels to ProcessEightBitWindows once Pow2Minus1Mod migrates to the windowed helper highlighted in GpuPow2ModBenchmarks.
|
||||
EvenPerfectBitScanner\Program.cs:539: _byDivisorTester.UseDivisorCycles = useDivisorCycles; // TODO: Collapse this assignment once the runtime enforces divisor-cycle acceleration for every by-divisor scan path.
|
||||
EvenPerfectBitScanner\Program.cs:621: // TODO: Replace this File.WriteAllText call with the pooled TextFileWriter pipeline once the
|
||||
EvenPerfectBitScanner\Program.cs:642: // TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
|
||||
EvenPerfectBitScanner\Program.cs:717: // TODO: Rent this buffer from ArrayPool once the pooled prime-block allocator lands so worker
|
||||
EvenPerfectBitScanner\Program.cs:800: // TODO: Swap this char-by-char parser for the benchmarked Utf8Parser-based fast-path once
|
||||
EvenPerfectBitScanner\Program.cs:818: // TODO: Swap ulong.TryParse for the Utf8Parser-based fast path once the shared span helpers
|
||||
EvenPerfectBitScanner\Program.cs:837: // TODO: Inline this helper by invoking MersenneNumberDivisorByDivisorTester.Run directly at call sites so
|
||||
EvenPerfectBitScanner\Program.cs:885: // TODO: Replace this ulong.Parse call with the Utf8Parser-based span fast-path we benchmarked so results
|
||||
EvenPerfectBitScanner\Program.cs:905: // TODO: Replace these bool.TryParse calls with the span-based fast path once we expose the
|
||||
EvenPerfectBitScanner\Program.cs:974: // TODO: Replace this string interpolation with the pooled ValueStringBuilder pipeline from the
|
||||
EvenPerfectBitScanner\Program.cs:997: // TODO: Inline the fast-path transform here once we collapse the delegate* indirection so
|
||||
EvenPerfectBitScanner\Program.cs:1076: // TODO: Switch this StringBuilder-based formatter to a span-friendly Utf8Formatter pipeline so
|
||||
EvenPerfectBitScanner\Program.cs:1137: // TODO: Replace these per-flush FileStream/StreamWriter allocations with a pooled TextFileWriter-style
|
||||
EvenPerfectBitScanner\Program.cs:1155: // TODO: Remove this wrapper once callers can invoke BitOperations.PopCount directly so the hot bit-stat path
|
||||
EvenPerfectBitScanner\Program.cs:1310: // TODO: Remove this pass-through once callers can provide the full out parameters
|
||||
EvenPerfectBitScanner\Program.cs:1388: // TODO: Route by-divisor scans through the ProcessEightBitWindows pow2mod kernel once the windowed Pow2Minus1Mod helper replaces the slower single-bit ladder.
|
||||
EvenPerfectBitScanner\Program.cs:1394: // TODO: Adopt the windowed Pow2Minus1Mod helper here so divisor checks benefit from the ProcessEightBitWindows speedups.
|
||||
EvenPerfectBitScanner\Program.cs:1406: // TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of
|
||||
EvenPerfectBitScanner\Program.cs:1462: // TODO: Replace this byte-by-byte scan with the lookup-table based statistics collector validated in the
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:154: // TODO: Replace TryParse with the Utf8Parser-based span helper once the results parser accepts ReadOnlySpan<char>
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:166: // TODO: Switch to the Utf8Parser-based fast path so integer arguments avoid transient strings while parsing.
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:192: // TODO: Swap this string slicing for ReadOnlySpan<char>-based parsing once the CLI helpers expose
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:234: // TODO: Replace this vanilla StreamReader with a pooled FileStreamOptions + ArrayPool-backed reader so large
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:250: // TODO: Stream candidates through a pooled buffer pipeline so we can filter and dispatch without allocating
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:317: // TODO: Replace ulong.Parse with the Utf8Parser-based span helper so CSV reload stays on the zero-allocation
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:325: // TODO: Swap bool.Parse for the Utf8Parser-powered boolean reader once exposed so reload avoids allocating
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:333: // TODO: Switch this path composition to Span<char>-based stack buffers once we expose the pooled
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:349: // TODO: Switch WriteCsv to a span-based Utf8Formatter pipeline backed by ArrayPool-rented buffers so
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:358: // TODO: Replace this per-entry WriteLine with a batched chunk writer that reuses pooled
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:380: // TODO: Replace the Task-based scheduling with the pooled work queue used in the scanner so candidate
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:388: // TODO: Rent this line buffer from ArrayPool<string> to eliminate repeated allocations when scanning
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:452: // TODO: Rent this full-capacity buffer from ArrayPool<string> and reuse it between reads instead
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:458: // TODO: Rent the partial chunk buffer from ArrayPool<string> instead of allocating a fresh
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:499: // TODO: Rent the candidate array from ArrayPool<CandidateResult> so chunk processing remains allocation-free
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:531: // TODO: Replace this copy with an in-place filtering pipeline that reuses pooled buffers; duplicating the list
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:537: // TODO: Keep using the Open.Numeric prime enumerator for the merge walk but hoist a shared instance so reloads
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:578: // TODO: Replace this Dictionary allocation with the pooled span-based frequency map once the ValueListBuilder
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:593: // TODO: Rent the raw prime accumulator from ArrayPool<List<CandidateResult>> or migrate to a pooled struct
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:661: // TODO: Rent these segment arrays from ArrayPool<List<CandidateResult>> so parallel splitting avoids allocating
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:666: // TODO: Rent the tasks array from ArrayPool<Task> so repeated reloads avoid allocating new
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:670: // TODO: Replace the separate division/modulo here with Math.DivRem (or the branchless chunk
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:690: // TODO: Replace Task.Run with the pooled work queue from the scanner so scheduling
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:696: // TODO: Rent these per-worker lists from a dedicated pool so parallel splits reuse
|
||||
EvenPerfectBitScanner.ResultsParser\Program.cs:721: // TODO: Promote these final result lists to pooled builders so we can reuse the buffers
|
||||
PerfectNumbers.Core.Tests\AlphaCacheTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128Montgomery64Benchmarks.cs:50: // TODO: Migrate remaining callers of MulModMontgomery64 to the extension helper; the struct
|
||||
PerfectNumbers.Core.Tests\AlphaCalculationsTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModBenchmarks.cs:52: // TODO: Switch the extension method to allocate per iteration, as it's faster. Keep the benchmarks.
|
||||
PerfectNumbers.Core\AlphaCache.cs:11: // TODO: Replace this dictionary lookup with a lock-free cache that reuses pooled AlphaValues so alpha requests avoid
|
||||
PerfectNumbers.Core\AlphaCache.cs:26: // TODO: Return pooled AlphaValues instances to a shared cache before clearing so repeated warm-ups do not thrash the
|
||||
PerfectNumbers.Core\AlphaCalculations.cs:11: // TODO: Reuse pooled numerator/denominator buffers here so the alphaP computation stops allocating
|
||||
PerfectNumbers.Core\AlphaCalculations.cs:20: // TODO: Reuse pooled ERational builders here so repeated factor computations pull precomputed
|
||||
PerfectNumbers.Core\AlphaCalculations.cs:31: // TODO: Replace the repeated Multiply calls with a span-based aggregator that batches factors,
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModByLimbBenchmarks.cs:59: // TODO: Switch the extension method back to legacy method, as it's faster. Keep the benchmarks.
|
||||
PerfectNumbers.Core\AlphaMScannerCandidateStatus.cs:5: // TODO: Store these statuses as byte-coded constants so hot-path evaluations can stay branchless when scanning candidates.
|
||||
EvenPerfectBitScanner.Benchmarks\GpuUInt128NativeModuloBenchmarks.cs:49: // TODO: Drop MulModWithNativeModulo from production once all callers use the immediate
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:28: // TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:42: // TODO: Swap this `%` for the shared divisor-cycle remainder helper so initialization reuses the cached
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:72: // TODO: Replace this `%` with the UInt128-aware cycle reducer so large deltas use the cached subtraction
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:85: // TODO: Route this `%` through the shared divisor-cycle helper so repeated wrap-arounds avoid
|
||||
PerfectNumbers.Core\CycleRemainderStepper.cs:96: // TODO: Expose these fields directly once the residue scanners adopt the single-cycle helper
|
||||
PerfectNumbers.Core.Tests\InputParserTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core.Tests\KRangeFinderTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:63: private const int CycleGenerationBatchSize = 262_144; // TODO: Remove the block-sized batch once the single-cycle generator
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:69: private readonly object _sync = new(); // TODO: Delete the synchronization object once the cache exposes only snapshot reads and single-cycle computation so callers no longer contend on locks.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:70: private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new(); // TODO: Drop the concurrent dictionary entirely when the block pipeline is replaced with single-cycle generation; the new plan never queues background work.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:71: private readonly ConcurrentDictionary<Accelerator, Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance); // TODO: Swap this ConcurrentDictionary for a simple accelerator-indexed array once the single-cycle helper precompiles kernels during startup, eliminating the need for thread-safe lookups.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:73: private CycleBlock? _activeBlock; // TODO: Remove dynamic blocks altogether once lookups route to the snapshot or compute an individual cycle without mutating shared state.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:74: private CycleBlock? _prefetchedBlock; // TODO: Delete prefetch support so the cache never materializes extra blocks and callers always compute the missing cycle inline.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:89: // TODO: Remove the ability to reload at runtime once the cache becomes a read-only snapshot plus
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:103: // TODO: Remove the lock once single-cycle generation eliminates shared mutable state; a simple
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:125: // TODO: Remove the dictionary once prefetching and background block tracking disappear; with single-cycle generation we will keep only the snapshot and skip additional bookkeeping entirely.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:139: // TODO: Replace this block-based acquisition with a direct GetCycle API that computes the missing cycle synchronously on the configured device so the cache never hands out mutable blocks or requires disposal.
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:160: // TODO: Replace Task.Run with an inline single-cycle computation that runs on the configured
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:179: // TODO: Stop promoting freshly generated blocks into the shared cache and, instead of
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:227: StartPrefetchLocked(prefetched.Index + 1); // TODO: Remove this call once prefetching is dropped
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:253: StartPrefetchLocked(block.Index + 1); // TODO: Remove once prefetching is removed and the cache
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:259: // TODO: Remove prefetching entirely so the cache maintains a single snapshot block and computes
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:283: // TODO: Delete this once prefetching is removed; single-cycle generation will happen inline and
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:292: // TODO: Delete this callback when we remove prefetching. Single-cycle generation will run inline,
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:309: // TODO: Remove this entire method when prefetching goes away. The single-cycle flow should never
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:391: // TODO: Replace this block generator with a single-cycle helper that reuses shared buffers,
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:424: // TODO: Port this CPU fallback to the unrolled-hex cycle generator once it is shared so misses outside the snapshot
|
||||
PerfectNumbers.Core\DivisorCycleCache.cs:440: // TODO: Once single-cycle generation is in place, bypass this block-sized kernel entirely and
|
||||
PerfectNumbers.Core\EulerPrimeTester.cs:17: // TODO: Inline this helper into callers so we skip the wrapper and pass the divisibility check directly to the optimized
|
||||
PerfectNumbers.Core\Cpu\CpuConstants.cs:9: // TODO: Generate these masks at startup from the benchmarked Mod10 automaton tables so CPU filtering stays aligned with
|
||||
PerfectNumbers.Core\Cpu\CpuConstants.cs:11: // TODO: Fold in the Mod6ComparisonBenchmarks stride table here so callers can merge the mod 6 skips with these masks and
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:47: // TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:57: // TODO: Once divisor cycle lengths are mandatory, pull the delta multiplier from the
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:60: // TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:78: // TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:87: // TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:89: // TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:124: // TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||
PerfectNumbers.Core\ExponentRemainderStepper.cs:133: // TODO: Once the divisor-cycle cache exposes a direct Montgomery delta, multiply it here instead
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:21: set => _useDivisorCycles = value; // TODO: Delete the toggle once divisor cycle data is always consulted so CPU scans never fall back to the slower pure-Montgomery path.
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:194: // TODO: Hoist MontgomeryDivisorData acquisition into the divisor-cycle cache so we reuse the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:203: // TODO: Swap this block lease for the direct single-cycle lookup once the cache exposes it
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||
PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:11: // TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:34: // TODO: Port this Lucas–Lehmer powmod to the ProcessEightBitWindows helper so the CPU
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:15: // TODO: Wire this residue scan into DivisorCycleCache so the mandatory cycle acceleration from the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:17: // TODO: When a required cycle is missing from the snapshot, compute only that single cycle on the device
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:91: // TODO: Once cycle data is exposed for residue scans, consult the cached divisor cycle here
|
||||
PerfectNumbers.Core\InputParser.cs:13: // TODO: Replace Split/Parse with the span-based exponent parser so BigInteger inputs avoid allocations
|
||||
PerfectNumbers.Core\InputParser.cs:20: // TODO: Replace BigInteger.Parse(string) with the span overload once callers provide ReadOnlySpan<char> so
|
||||
PerfectNumbers.Core\InputParser.cs:27: // TODO: Swap decimal.Parse for the Utf8Parser-based decimal reader so callers avoid culture-aware conversions
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:30: // TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:43: // TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:53: // TODO: Swap this PowModWithCycle call for the ProcessEightBitWindows helper once
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:65: // TODO: Route the phi-based powmods through the upcoming windowed helper to
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:70: // TODO: Once the windowed pow2 helper is available expose a version
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:77: // TODO: Replace this divisor powmod with the shared windowed
|
||||
PerfectNumbers.Core\Gpu\GpuConstants.cs:7: // TODO: Auto-tune these GPU constants per accelerator so batch sizing aligns with the fastest kernel
|
||||
todo_list.txt:1:EvenPerfectBitScanner\Program.cs:69: bool useModuloWorkaround = false; // TODO: Remove once the runtime defaults to the ImmediateModulo path measured fastest in GpuUInt128NativeModuloBenchmarks.
|
||||
todo_list.txt:2:EvenPerfectBitScanner\Program.cs:78: bool useDivisorCycles = false; // TODO: Default this flag to true and remove the opt-out once divisor cycle lookups are mandatory per the optimized by-divisor benchmarks.
|
||||
todo_list.txt:3:EvenPerfectBitScanner\Program.cs:109: // TODO: Replace this ulong.Parse with the Utf8Parser-based span helper once CLI option parsing
|
||||
todo_list.txt:4:EvenPerfectBitScanner\Program.cs:124: // TODO: Swap int.Parse for the span-based Utf8Parser fast path to avoid transient strings when
|
||||
todo_list.txt:5:EvenPerfectBitScanner\Program.cs:133: kernelType = GpuKernelType.Pow2Mod; // TODO: Switch this mode to the ProcessEightBitWindows kernel once Pow2Minus1Mod adopts the benchmarked faster windowed ladder.
|
||||
todo_list.txt:6:EvenPerfectBitScanner\Program.cs:164: // TODO: Replace UInt128.Parse with the benchmarked fast-path parser once we expose the
|
||||
todo_list.txt:7:EvenPerfectBitScanner\Program.cs:177: // TODO: Replace ulong.TryParse with the Utf8Parser fast-path once CLI parsing utilities expose
|
||||
todo_list.txt:8:EvenPerfectBitScanner\Program.cs:186: // TODO: Swap this TryParse for the zero-allocation Utf8Parser helper to keep residue CLI option
|
||||
todo_list.txt:9:EvenPerfectBitScanner\Program.cs:211: // TODO: Use the Utf8Parser-based reader here to eliminate the temporary substring allocation
|
||||
todo_list.txt:10:EvenPerfectBitScanner\Program.cs:273: // TODO: Replace this TryParse with the Utf8Parser span helper once shared CLI parsing utilities
|
||||
todo_list.txt:11:EvenPerfectBitScanner\Program.cs:288: // TODO: Swap to the Utf8Parser double fast-path so zero-hard parsing avoids culture-dependent
|
||||
todo_list.txt:12:EvenPerfectBitScanner\Program.cs:304: // TODO: Replace these double/int TryParse calls with span-based Utf8Parser helpers once
|
||||
todo_list.txt:13:EvenPerfectBitScanner\Program.cs:338: // TODO: Convert this int.Parse to the Utf8Parser-based helper to keep CLI option parsing
|
||||
todo_list.txt:14:EvenPerfectBitScanner\Program.cs:344: // TODO: Swap int.Parse for Utf8Parser to align with the faster CLI numeric parsing path.
|
||||
todo_list.txt:15:EvenPerfectBitScanner\Program.cs:349: // TODO: Replace int.Parse with Utf8Parser-based parsing to eliminate temporary strings when
|
||||
todo_list.txt:16:EvenPerfectBitScanner\Program.cs:355: // TODO: Use the Utf8Parser-based fast path here to match the other CLI numeric parsing
|
||||
todo_list.txt:17:EvenPerfectBitScanner\Program.cs:361: // TODO: Inline the Utf8Parser-based int reader here once shared CLI parsing utilities land so
|
||||
todo_list.txt:18:EvenPerfectBitScanner\Program.cs:371: // TODO: Replace int.Parse with the shared Utf8Parser fast-path to keep hot CLI option parsing
|
||||
todo_list.txt:19:EvenPerfectBitScanner\Program.cs:385: // TODO: Convert this int.Parse to Utf8Parser once the optimized CLI parsing helper is shared
|
||||
todo_list.txt:20:EvenPerfectBitScanner\Program.cs:443: // TODO: Wire GenerateGpu to the unrolled-hex kernel that led the MersenneDivisorCycleLengthGpuBenchmarks once it lands.
|
||||
todo_list.txt:21:EvenPerfectBitScanner\Program.cs:464: // TODO: Keep a single cached block loaded from disk and honor the configured device when
|
||||
todo_list.txt:22:EvenPerfectBitScanner\Program.cs:468: // TODO: Stop reloading the full snapshot once the ad-hoc path streams results straight from
|
||||
todo_list.txt:23:EvenPerfectBitScanner\Program.cs:504: // TODO: Swap the underlying pow2mod kernels to ProcessEightBitWindows once Pow2Minus1Mod migrates to the windowed helper highlighted in GpuPow2ModBenchmarks.
|
||||
todo_list.txt:24:EvenPerfectBitScanner\Program.cs:539: _byDivisorTester.UseDivisorCycles = useDivisorCycles; // TODO: Collapse this assignment once the runtime enforces divisor-cycle acceleration for every by-divisor scan path.
|
||||
todo_list.txt:25:EvenPerfectBitScanner\Program.cs:621: // TODO: Replace this File.WriteAllText call with the pooled TextFileWriter pipeline once the
|
||||
todo_list.txt:26:EvenPerfectBitScanner\Program.cs:642: // TODO: Rent this filter buffer from ArrayPool<ulong> and reuse it across reload batches so
|
||||
todo_list.txt:27:EvenPerfectBitScanner\Program.cs:717: // TODO: Rent this buffer from ArrayPool once the pooled prime-block allocator lands so worker
|
||||
todo_list.txt:28:EvenPerfectBitScanner\Program.cs:800: // TODO: Swap this char-by-char parser for the benchmarked Utf8Parser-based fast-path once
|
||||
todo_list.txt:29:EvenPerfectBitScanner\Program.cs:818: // TODO: Swap ulong.TryParse for the Utf8Parser-based fast path once the shared span helpers
|
||||
todo_list.txt:30:EvenPerfectBitScanner\Program.cs:837: // TODO: Inline this helper by invoking MersenneNumberDivisorByDivisorTester.Run directly at call sites so
|
||||
todo_list.txt:31:EvenPerfectBitScanner\Program.cs:885: // TODO: Replace this ulong.Parse call with the Utf8Parser-based span fast-path we benchmarked so results
|
||||
todo_list.txt:32:EvenPerfectBitScanner\Program.cs:905: // TODO: Replace these bool.TryParse calls with the span-based fast path once we expose the
|
||||
todo_list.txt:33:EvenPerfectBitScanner\Program.cs:974: // TODO: Replace this string interpolation with the pooled ValueStringBuilder pipeline from the
|
||||
todo_list.txt:34:EvenPerfectBitScanner\Program.cs:997: // TODO: Inline the fast-path transform here once we collapse the delegate* indirection so
|
||||
todo_list.txt:35:EvenPerfectBitScanner\Program.cs:1076: // TODO: Switch this StringBuilder-based formatter to a span-friendly Utf8Formatter pipeline so
|
||||
todo_list.txt:36:EvenPerfectBitScanner\Program.cs:1137: // TODO: Replace these per-flush FileStream/StreamWriter allocations with a pooled TextFileWriter-style
|
||||
todo_list.txt:37:EvenPerfectBitScanner\Program.cs:1155: // TODO: Remove this wrapper once callers can invoke BitOperations.PopCount directly so the hot bit-stat path
|
||||
todo_list.txt:38:EvenPerfectBitScanner\Program.cs:1310: // TODO: Remove this pass-through once callers can provide the full out parameters
|
||||
todo_list.txt:39:EvenPerfectBitScanner\Program.cs:1388: // TODO: Route by-divisor scans through the ProcessEightBitWindows pow2mod kernel once the windowed Pow2Minus1Mod helper replaces the slower single-bit ladder.
|
||||
todo_list.txt:40:EvenPerfectBitScanner\Program.cs:1394: // TODO: Adopt the windowed Pow2Minus1Mod helper here so divisor checks benefit from the ProcessEightBitWindows speedups.
|
||||
todo_list.txt:41:EvenPerfectBitScanner\Program.cs:1406: // TODO: Integrate the divisor-cycle cache here so the small-prime sweep reuses precomputed remainders instead of
|
||||
todo_list.txt:42:EvenPerfectBitScanner\Program.cs:1462: // TODO: Replace this byte-by-byte scan with the lookup-table based statistics collector validated in the
|
||||
todo_list.txt:43:EvenPerfectBitScanner.ResultsParser\Program.cs:154: // TODO: Replace TryParse with the Utf8Parser-based span helper once the results parser accepts ReadOnlySpan<char>
|
||||
todo_list.txt:44:EvenPerfectBitScanner.ResultsParser\Program.cs:166: // TODO: Switch to the Utf8Parser-based fast path so integer arguments avoid transient strings while parsing.
|
||||
todo_list.txt:45:EvenPerfectBitScanner.ResultsParser\Program.cs:192: // TODO: Swap this string slicing for ReadOnlySpan<char>-based parsing once the CLI helpers expose
|
||||
todo_list.txt:46:EvenPerfectBitScanner.ResultsParser\Program.cs:234: // TODO: Replace this vanilla StreamReader with a pooled FileStreamOptions + ArrayPool-backed reader so large
|
||||
todo_list.txt:47:EvenPerfectBitScanner.ResultsParser\Program.cs:250: // TODO: Stream candidates through a pooled buffer pipeline so we can filter and dispatch without allocating
|
||||
todo_list.txt:48:EvenPerfectBitScanner.ResultsParser\Program.cs:317: // TODO: Replace ulong.Parse with the Utf8Parser-based span helper so CSV reload stays on the zero-allocation
|
||||
todo_list.txt:49:EvenPerfectBitScanner.ResultsParser\Program.cs:325: // TODO: Swap bool.Parse for the Utf8Parser-powered boolean reader once exposed so reload avoids allocating
|
||||
todo_list.txt:50:EvenPerfectBitScanner.ResultsParser\Program.cs:333: // TODO: Switch this path composition to Span<char>-based stack buffers once we expose the pooled
|
||||
todo_list.txt:51:EvenPerfectBitScanner.ResultsParser\Program.cs:349: // TODO: Switch WriteCsv to a span-based Utf8Formatter pipeline backed by ArrayPool-rented buffers so
|
||||
todo_list.txt:52:EvenPerfectBitScanner.ResultsParser\Program.cs:358: // TODO: Replace this per-entry WriteLine with a batched chunk writer that reuses pooled
|
||||
todo_list.txt:53:EvenPerfectBitScanner.ResultsParser\Program.cs:380: // TODO: Replace the Task-based scheduling with the pooled work queue used in the scanner so candidate
|
||||
todo_list.txt:54:EvenPerfectBitScanner.ResultsParser\Program.cs:388: // TODO: Rent this line buffer from ArrayPool<string> to eliminate repeated allocations when scanning
|
||||
todo_list.txt:55:EvenPerfectBitScanner.ResultsParser\Program.cs:452: // TODO: Rent this full-capacity buffer from ArrayPool<string> and reuse it between reads instead
|
||||
todo_list.txt:56:EvenPerfectBitScanner.ResultsParser\Program.cs:458: // TODO: Rent the partial chunk buffer from ArrayPool<string> instead of allocating a fresh
|
||||
todo_list.txt:57:EvenPerfectBitScanner.ResultsParser\Program.cs:499: // TODO: Rent the candidate array from ArrayPool<CandidateResult> so chunk processing remains allocation-free
|
||||
todo_list.txt:58:EvenPerfectBitScanner.ResultsParser\Program.cs:531: // TODO: Replace this copy with an in-place filtering pipeline that reuses pooled buffers; duplicating the list
|
||||
todo_list.txt:59:EvenPerfectBitScanner.ResultsParser\Program.cs:537: // TODO: Keep using the Open.Numeric prime enumerator for the merge walk but hoist a shared instance so reloads
|
||||
todo_list.txt:60:EvenPerfectBitScanner.ResultsParser\Program.cs:578: // TODO: Replace this Dictionary allocation with the pooled span-based frequency map once the ValueListBuilder
|
||||
todo_list.txt:61:EvenPerfectBitScanner.ResultsParser\Program.cs:593: // TODO: Rent the raw prime accumulator from ArrayPool<List<CandidateResult>> or migrate to a pooled struct
|
||||
todo_list.txt:62:EvenPerfectBitScanner.ResultsParser\Program.cs:661: // TODO: Rent these segment arrays from ArrayPool<List<CandidateResult>> so parallel splitting avoids allocating
|
||||
todo_list.txt:63:EvenPerfectBitScanner.ResultsParser\Program.cs:666: // TODO: Rent the tasks array from ArrayPool<Task> so repeated reloads avoid allocating new
|
||||
todo_list.txt:64:EvenPerfectBitScanner.ResultsParser\Program.cs:670: // TODO: Replace the separate division/modulo here with Math.DivRem (or the branchless chunk
|
||||
todo_list.txt:65:EvenPerfectBitScanner.ResultsParser\Program.cs:690: // TODO: Replace Task.Run with the pooled work queue from the scanner so scheduling
|
||||
todo_list.txt:66:EvenPerfectBitScanner.ResultsParser\Program.cs:696: // TODO: Rent these per-worker lists from a dedicated pool so parallel splits reuse
|
||||
todo_list.txt:67:EvenPerfectBitScanner.ResultsParser\Program.cs:721: // TODO: Promote these final result lists to pooled builders so we can reuse the buffers
|
||||
todo_list.txt:68:PerfectNumbers.Core.Tests\AlphaCacheTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:69:EvenPerfectBitScanner.Benchmarks\GpuUInt128Montgomery64Benchmarks.cs:50: // TODO: Migrate remaining callers of MulModMontgomery64 to the extension helper; the struct
|
||||
todo_list.txt:70:PerfectNumbers.Core.Tests\AlphaCalculationsTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:71:EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModBenchmarks.cs:52: // TODO: Switch the extension method to allocate per iteration, as it's faster. Keep the benchmarks.
|
||||
todo_list.txt:72:PerfectNumbers.Core\AlphaCache.cs:11: // TODO: Replace this dictionary lookup with a lock-free cache that reuses pooled AlphaValues so alpha requests avoid
|
||||
todo_list.txt:73:PerfectNumbers.Core\AlphaCache.cs:26: // TODO: Return pooled AlphaValues instances to a shared cache before clearing so repeated warm-ups do not thrash the
|
||||
todo_list.txt:74:PerfectNumbers.Core\AlphaCalculations.cs:11: // TODO: Reuse pooled numerator/denominator buffers here so the alphaP computation stops allocating
|
||||
todo_list.txt:75:PerfectNumbers.Core\AlphaCalculations.cs:20: // TODO: Reuse pooled ERational builders here so repeated factor computations pull precomputed
|
||||
todo_list.txt:76:PerfectNumbers.Core\AlphaCalculations.cs:31: // TODO: Replace the repeated Multiply calls with a span-based aggregator that batches factors,
|
||||
todo_list.txt:77:EvenPerfectBitScanner.Benchmarks\GpuUInt128MulModByLimbBenchmarks.cs:59: // TODO: Switch the extension method back to legacy method, as it's faster. Keep the benchmarks.
|
||||
todo_list.txt:78:PerfectNumbers.Core\AlphaMScannerCandidateStatus.cs:5: // TODO: Store these statuses as byte-coded constants so hot-path evaluations can stay branchless when scanning candidates.
|
||||
todo_list.txt:79:EvenPerfectBitScanner.Benchmarks\GpuUInt128NativeModuloBenchmarks.cs:49: // TODO: Drop MulModWithNativeModulo from production once all callers use the immediate
|
||||
todo_list.txt:80:PerfectNumbers.Core\CycleRemainderStepper.cs:28: // TODO: Inline this reset at the call sites so the hot loops reuse struct reinitialization
|
||||
todo_list.txt:81:PerfectNumbers.Core\CycleRemainderStepper.cs:42: // TODO: Swap this `%` for the shared divisor-cycle remainder helper so initialization reuses the cached
|
||||
todo_list.txt:82:PerfectNumbers.Core\CycleRemainderStepper.cs:72: // TODO: Replace this `%` with the UInt128-aware cycle reducer so large deltas use the cached subtraction
|
||||
todo_list.txt:83:PerfectNumbers.Core\CycleRemainderStepper.cs:85: // TODO: Route this `%` through the shared divisor-cycle helper so repeated wrap-arounds avoid
|
||||
todo_list.txt:84:PerfectNumbers.Core\CycleRemainderStepper.cs:96: // TODO: Expose these fields directly once the residue scanners adopt the single-cycle helper
|
||||
todo_list.txt:85:PerfectNumbers.Core.Tests\InputParserTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:86:PerfectNumbers.Core.Tests\KRangeFinderTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
todo_list.txt:87:PerfectNumbers.Core\DivisorCycleCache.cs:63: private const int CycleGenerationBatchSize = 262_144; // TODO: Remove the block-sized batch once the single-cycle generator
|
||||
todo_list.txt:88:PerfectNumbers.Core\DivisorCycleCache.cs:69: private readonly object _sync = new(); // TODO: Delete the synchronization object once the cache exposes only snapshot reads and single-cycle computation so callers no longer contend on locks.
|
||||
todo_list.txt:89:PerfectNumbers.Core\DivisorCycleCache.cs:70: private readonly ConcurrentDictionary<int, Task<CycleBlock>> _pending = new(); // TODO: Drop the concurrent dictionary entirely when the block pipeline is replaced with single-cycle generation; the new plan never queues background work.
|
||||
todo_list.txt:90:PerfectNumbers.Core\DivisorCycleCache.cs:71: private readonly ConcurrentDictionary<Accelerator, Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance); // TODO: Swap this ConcurrentDictionary for a simple accelerator-indexed array once the single-cycle helper precompiles kernels during startup, eliminating the need for thread-safe lookups.
|
||||
todo_list.txt:91:PerfectNumbers.Core\DivisorCycleCache.cs:73: private CycleBlock? _activeBlock; // TODO: Remove dynamic blocks altogether once lookups route to the snapshot or compute an individual cycle without mutating shared state.
|
||||
todo_list.txt:92:PerfectNumbers.Core\DivisorCycleCache.cs:74: private CycleBlock? _prefetchedBlock; // TODO: Delete prefetch support so the cache never materializes extra blocks and callers always compute the missing cycle inline.
|
||||
todo_list.txt:93:PerfectNumbers.Core\DivisorCycleCache.cs:89: // TODO: Remove the ability to reload at runtime once the cache becomes a read-only snapshot plus
|
||||
todo_list.txt:94:PerfectNumbers.Core\DivisorCycleCache.cs:103: // TODO: Remove the lock once single-cycle generation eliminates shared mutable state; a simple
|
||||
todo_list.txt:95:PerfectNumbers.Core\DivisorCycleCache.cs:125: // TODO: Remove the dictionary once prefetching and background block tracking disappear; with single-cycle generation we will keep only the snapshot and skip additional bookkeeping entirely.
|
||||
todo_list.txt:96:PerfectNumbers.Core\DivisorCycleCache.cs:139: // TODO: Replace this block-based acquisition with a direct GetCycle API that computes the missing cycle synchronously on the configured device so the cache never hands out mutable blocks or requires disposal.
|
||||
todo_list.txt:97:PerfectNumbers.Core\DivisorCycleCache.cs:160: // TODO: Replace Task.Run with an inline single-cycle computation that runs on the configured
|
||||
todo_list.txt:98:PerfectNumbers.Core\DivisorCycleCache.cs:179: // TODO: Stop promoting freshly generated blocks into the shared cache and, instead of
|
||||
todo_list.txt:99:PerfectNumbers.Core\DivisorCycleCache.cs:227: StartPrefetchLocked(prefetched.Index + 1); // TODO: Remove this call once prefetching is dropped
|
||||
todo_list.txt:100:PerfectNumbers.Core\DivisorCycleCache.cs:253: StartPrefetchLocked(block.Index + 1); // TODO: Remove once prefetching is removed and the cache
|
||||
todo_list.txt:101:PerfectNumbers.Core\DivisorCycleCache.cs:259: // TODO: Remove prefetching entirely so the cache maintains a single snapshot block and computes
|
||||
todo_list.txt:102:PerfectNumbers.Core\DivisorCycleCache.cs:283: // TODO: Delete this once prefetching is removed; single-cycle generation will happen inline and
|
||||
todo_list.txt:103:PerfectNumbers.Core\DivisorCycleCache.cs:292: // TODO: Delete this callback when we remove prefetching. Single-cycle generation will run inline,
|
||||
todo_list.txt:104:PerfectNumbers.Core\DivisorCycleCache.cs:309: // TODO: Remove this entire method when prefetching goes away. The single-cycle flow should never
|
||||
todo_list.txt:105:PerfectNumbers.Core\DivisorCycleCache.cs:391: // TODO: Replace this block generator with a single-cycle helper that reuses shared buffers,
|
||||
todo_list.txt:106:PerfectNumbers.Core\DivisorCycleCache.cs:424: // TODO: Port this CPU fallback to the unrolled-hex cycle generator once it is shared so misses outside the snapshot
|
||||
todo_list.txt:107:PerfectNumbers.Core\DivisorCycleCache.cs:440: // TODO: Once single-cycle generation is in place, bypass this block-sized kernel entirely and
|
||||
todo_list.txt:108:PerfectNumbers.Core\EulerPrimeTester.cs:17: // TODO: Inline this helper into callers so we skip the wrapper and pass the divisibility check directly to the optimized
|
||||
todo_list.txt:109:PerfectNumbers.Core\Cpu\CpuConstants.cs:9: // TODO: Generate these masks at startup from the benchmarked Mod10 automaton tables so CPU filtering stays aligned with
|
||||
todo_list.txt:110:PerfectNumbers.Core\Cpu\CpuConstants.cs:11: // TODO: Fold in the Mod6ComparisonBenchmarks stride table here so callers can merge the mod 6 skips with these masks and
|
||||
todo_list.txt:111:PerfectNumbers.Core\ExponentRemainderStepper.cs:47: // TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||
todo_list.txt:112:PerfectNumbers.Core\ExponentRemainderStepper.cs:57: // TODO: Once divisor cycle lengths are mandatory, pull the delta multiplier from the
|
||||
todo_list.txt:113:PerfectNumbers.Core\ExponentRemainderStepper.cs:60: // TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||
todo_list.txt:114:PerfectNumbers.Core\ExponentRemainderStepper.cs:78: // TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||
todo_list.txt:115:PerfectNumbers.Core\ExponentRemainderStepper.cs:87: // TODO: Reuse the divisor-cycle derived Montgomery delta once the cache exposes single-cycle
|
||||
todo_list.txt:116:PerfectNumbers.Core\ExponentRemainderStepper.cs:89: // TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
||||
todo_list.txt:117:PerfectNumbers.Core\ExponentRemainderStepper.cs:124: // TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||
todo_list.txt:118:PerfectNumbers.Core\ExponentRemainderStepper.cs:133: // TODO: Once the divisor-cycle cache exposes a direct Montgomery delta, multiply it here instead
|
||||
todo_list.txt:119:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:21: set => _useDivisorCycles = value; // TODO: Delete the toggle once divisor cycle data is always consulted so CPU scans never fall back to the slower pure-Montgomery path.
|
||||
todo_list.txt:120:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:194: // TODO: Hoist MontgomeryDivisorData acquisition into the divisor-cycle cache so we reuse the
|
||||
todo_list.txt:121:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:203: // TODO: Swap this block lease for the direct single-cycle lookup once the cache exposes it
|
||||
todo_list.txt:122:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
||||
todo_list.txt:123:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||
todo_list.txt:124:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||
todo_list.txt:125:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||
todo_list.txt:126:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
||||
todo_list.txt:127:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||
todo_list.txt:128:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||
todo_list.txt:129:PerfectNumbers.Core\IMersenneNumberDivisorByDivisorTester.cs:7: bool UseDivisorCycles { get; set; } // TODO: Remove the setter once divisor cycle usage becomes mandatory so all implementations always leverage the cached cycles.
|
||||
todo_list.txt:130:PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:11: // TODO: Replace these `%` checks with Mod3/Mod5/Mod7/Mod11 helpers once Lucas–Lehmer CPU filtering
|
||||
todo_list.txt:131:PerfectNumbers.Core\Cpu\MersenneNumberLucasLehmerCpuTester.cs:34: // TODO: Port this Lucas–Lehmer powmod to the ProcessEightBitWindows helper so the CPU
|
||||
todo_list.txt:132:PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:15: // TODO: Wire this residue scan into DivisorCycleCache so the mandatory cycle acceleration from the
|
||||
todo_list.txt:133:PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:17: // TODO: When a required cycle is missing from the snapshot, compute only that single cycle on the device
|
||||
todo_list.txt:134:PerfectNumbers.Core\Cpu\MersenneNumberResidueCpuTester.cs:91: // TODO: Once cycle data is exposed for residue scans, consult the cached divisor cycle here
|
||||
todo_list.txt:135:PerfectNumbers.Core\InputParser.cs:13: // TODO: Replace Split/Parse with the span-based exponent parser so BigInteger inputs avoid allocations
|
||||
todo_list.txt:136:PerfectNumbers.Core\InputParser.cs:20: // TODO: Replace BigInteger.Parse(string) with the span overload once callers provide ReadOnlySpan<char> so
|
||||
todo_list.txt:137:PerfectNumbers.Core\InputParser.cs:27: // TODO: Swap decimal.Parse for the Utf8Parser-based decimal reader so callers avoid culture-aware conversions
|
||||
todo_list.txt:138:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:30: // TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
|
||||
todo_list.txt:139:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:43: // TODO: Swap these Mod3/Mod5 zero checks to the cached residue tables once the automaton exposes
|
||||
todo_list.txt:140:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:53: // TODO: Swap this PowModWithCycle call for the ProcessEightBitWindows helper once
|
||||
todo_list.txt:141:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:65: // TODO: Route the phi-based powmods through the upcoming windowed helper to
|
||||
todo_list.txt:142:PerfectNumbers.Core\Cpu\MersenneNumberIncrementalCpuTester.cs:70: // TODO: Once the windowed pow2 helper is available expose
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:17: // TODO: When this lookup misses the snapshot, invoke the configured device to compute the
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:38: // TODO: Point this CPU pow2mod check at the ProcessEightBitWindows helper once it
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:52: // TODO: Switch these phi-based powmods to the shared windowed helper so CPU
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:56: // TODO: Reuse the windowed pow2 helper for halfPow as soon as it is
|
||||
PerfectNumbers.Core\Cpu\MersenneNumberOrderCpuTester.cs:63: // TODO: Replace this divisor powmod with the ProcessEightBitWindows
|
||||
PerfectNumbers.Core\Gpu\GpuContextPool.cs:10: // TODO: Introduce accelerator-specific warmup so pooled contexts precompile the ProcessEightBitWindows kernels and load
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:115: // TODO: Replace this naive doubling fallback with the unrolled-hex generator from
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:118: // TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:133: // TODO: Port this UInt128 path to the unrolled-hex cycle calculator so wide
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:205: // TODO: Replace this modulo-based filter with the cached Mod3/Mod5/Mod7/Mod11
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:213: // TODO: Migrate this generation path to the shared
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:287: // TODO: Swap these modulo checks for the cached Mod3/Mod5/Mod7/Mod11 helpers so
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:337: // TODO: Replace this modulo sieve with the cached Mod helpers once the divisor-cycle cache
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:459: // TODO: Switch this scalar fallback to the unrolled-hex stepping sequence once
|
||||
PerfectNumbers.Core\MersenneDivisorCycles.cs:462: // TODO: Expose a GPU-first branch here so high divisors leverage the ProcessEightBitWindows
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:194: // TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:202: // TODO: Replace these `%` computations with the precomputed Mod3/Mod5 tables so GPU kernels reuse cached residues
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:221: // TODO: Swap these `%` filters to the shared Mod helpers so the residue automaton matches the benchmarked
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:271: // TODO: Replace these Pow2Mod calls with the ProcessEightBitWindows helper when the shared windowed
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:308: // TODO: Once the ProcessEightBitWindows helper is available, switch this order kernel to that faster
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:420: // TODO: Swap Pow2Minus1Mod for the eight-bit window helper once the scalar version switches;
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:576: // TODO: Upgrade this pow2mod order kernel to the ProcessEightBitWindows helper once available so GPU residue
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:654: // TODO: Replace this Pow2Mod check with the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:697: private static readonly ConcurrentDictionary<Accelerator, KernelContainer> KernelCache = new(); // TODO: Replace this concurrent map with a simple accelerator-indexed lookup once kernel launchers are prewarmed during startup so we can drop the thread-safe wrapper entirely.
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:715: lock (kernels) // TODO: Remove this lock by pre-uploading the immutable small-cycle snapshot during initialization; once no mutation happens at runtime, the pool must expose a simple reference without synchronization.
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:722: var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot.
|
||||
PerfectNumbers.Core\Gpu\GpuKernelPool.cs:741: lock (kernels) // TODO: Inline these small-prime uploads into startup initialization alongside the small-cycle snapshot so we can drop runtime locking and keep the GPU pool free of synchronization.
|
||||
PerfectNumbers.Core\Gpu\GpuPrimeWorkLimiter.cs:11: // TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
|
||||
PerfectNumbers.Core\Gpu\GpuPrimeWorkLimiter.cs:32: // TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:259: // TODO: Replace this single-bit ladder with the ProcessEightBitWindows strategy measured
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:293: // TODO: Mirror the eight-bit window batching once the scalar helper above switches; the
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:380: // TODO: Pre-reduce the operands via the Montgomery ladder used in MulMod64Benchmarks so the GPU
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:398: // TODO: Fold these operands with the ImmediateModulo helper once the GPU shim exposes it, avoiding
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:539: // TODO: Replace this scalar binary GCD with the branchless reduction from
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:698: // TODO(MOD-OPT): Replace this bitwise long-division style reduction
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:709: // TODO(MOD-OPT): Plumb constants through caches in NttGpuMath.SquareCacheEntry
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:735: // TODO: Relocate this limb-based reducer to the benchmark project once the production
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:827: // TODO: Drop this native-modulo path from production after migrating callers to MulMod,
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:864: // TODO: Can we modify these loops to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:912: // TODO: Collapse this eight-step shift ladder into the ProcessEightBitWindows helper once it lands so
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:952: // TODO: Retire this struct-based Montgomery path from production after adopting the extension
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:972: // TODO: Same as above—migrate callers to the scalar extension to avoid this 6-7× slowdown.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1000: // TODO: This should operate on the instance itself, not on a copy. Avoid creating new instances anywhere.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1003: // TODO: Can we modify these loops to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1050: // TODO: Swap this copy-heavy path for the pooled base/exponent ladder used in
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1056: // TODO: Replace the single-bit square-and-multiply loop with the 64-bit windowed
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1076: // TODO: Share the pooled ladder state from GpuUInt128MulModBenchmarks here as well so
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1081: // TODO: Upgrade this loop to the same 64-bit windowed ladder proven fastest in
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1101: // TODO: Replace this Fermat inversion with the Montgomery ladder highlighted in
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1128: // TODO: Reuse variables to reduce register pressure following the fused-limb layout
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1191: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1194: // TODO: Since we ignore the first result element, can we create a version of the function which calculates and returns only the second element?
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1198: // TODO: Why not just modify left instance directly instead using out parameters?
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1205: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1226: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1272: // TODO: Can we identify where we deal with Mersenne exponent in EvenPerfectBitScanner and directly use MulModMersenne there, removing this branch?
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1282: // TODO: Can we modify this loop to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1309: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1330: // TODO: Can we modify this loop to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1362: // TODO: Can we modify this loop to process multiple bits at a time? E.g. 64-bit chunks.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1406: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1434: // TODO: Reuse variables to reduce register pressure.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1463: // TODO: Check if the TODO below is still relevant.
|
||||
PerfectNumbers.Core\Gpu\GpuUInt128.cs:1465: // TODO(MOD-OPT): Montgomery/Barrett integration plan
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:39: // TODO: Swap this ConcurrentDictionary for the pooled dictionary variant highlighted in
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:51: // TODO: Ensure Program passes useResidue correctly and that residue-vs-incremental-vs-LL selection respects CLI flags.
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:74: // TODO: Replace these repeated Mod10/Mod8/Mod3/Mod5 calls with a residue automaton walk so
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:99: // TODO: Swap this fallback to the shared windowed order helper once CalculateOrder migrates
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:117: // TODO: Reuse the same residue-automaton fast path here so the GPU warmup staging avoids `%` and
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:189: // TODO: Replace this CalculateOrder call with the upcoming windowed helper so
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:219: // TODO: Replace this `% 3` check with ULongExtensions.Mod3 to align the early rejection with
|
||||
PerfectNumbers.Core\MersenneNumberTester.cs:328: // TODO: Swap this legacy kernel over to the ProcessEightBitWindows helper so GPU order scans share the
|
||||
PerfectNumbers.Core\Gpu\GpuWorkLimiter.cs:11: // TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the
|
||||
PerfectNumbers.Core\Gpu\GpuWorkLimiter.cs:36: // TODO: Consolidate with GpuPrimeWorkLimiter so both limiters share a pooled SemaphoreSlim and avoid rebuilding the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:39: // TODO: Replace this `%` with the benchmarked Math.DivRem fast path so the hot filter avoids
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:50: // TODO: Replace this dictionary with the divisor-cycle order cache once the benchmarks confirm the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:56: // TODO: Collapse this wrapper once all callers promote operands to UInt128 so we
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:62: // TODO: Inline this shim once callers can invoke OrderOf2ModPrime directly; the extra
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:71: // TODO: Replace this lock with the lock-free order cache once the divisor-cycle snapshot exposes
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:84: // TODO: Pull these factor arrays from ArrayPool once the factoring helpers adopt the pooled
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:98: // TODO: Swap this `%`/`/` loop for the Math.DivRem-based reducer highlighted in the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:119: // TODO: Integrate divisor-cycle data here so repeated order refinement uses cached cycle
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:134: // TODO: Replace this duplicate powmod with ULongExtensions.ModPow64 so we inherit the optimized MulMod64 path that led the MulMod64Benchmarks on large operands.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:166: private static ulong MulMod64(ulong a, ulong b, ulong mod) => (ulong)((UInt128)a * b % mod); // TODO: Route this through ULongExtensions.MulMod64 so the powmod above adopts the inline UInt128 implementation that dominated the MulMod64Benchmarks.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:169: // TODO: Switch to UInt128Extensions.ModPow once its MulMod backend adopts the faster UInt128BuiltIn path highlighted in MulHighBenchmarks to avoid the BigInteger fallback.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:203: BigInteger product = (BigInteger)a * b; // TODO: Replace this BigInteger reduction with the forthcoming UInt128 intrinsic helper measured faster in Mul64Benchmarks and MulHighBenchmarks for huge operands.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:256: var queue = new Queue<ulong>(); // TODO: Replace this Queue with the pooled stack from the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:296: // TODO: Rent the result array from ArrayPool once the factoring pipeline consumes spans so we
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:315: var queue = new Queue<UInt128>(); // TODO: Replace this with the pooled UInt128 stack once the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:354: var result = new UInt128[dict.Count]; // TODO: Rent this array from ArrayPool<UInt128> so
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:374: var rng = new Random(1234567); // TODO: Replace this RNG with the deterministic span-based
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:398: x = (MulMod64(x, x, n) + c) % n; // TODO: Swap the `% n` with the Montgomery folding
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:406: y = (MulMod64(y, y, n) + c) % n; // TODO: Use the same Montgomery folding helper here
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:414: y = (MulMod64(y, y, n) + c) % n; // TODO: Replace this modulo with the optimized helper
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:443: t = a % b; // TODO: Replace this with the binary GCD helper used in the gcd benchmarks so we
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:452: // TODO: Port this Miller–Rabin routine to UInt128 intrinsics so we avoid the BigInteger.ModPow calls that lag by orders of magnitude on the large inputs highlighted in the Pow2MontgomeryMod benchmarks.
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:526: var rng = new Random(1234567); // TODO: Replace with the deterministic UInt128 sequence from
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:549: x = (MulMod128(x, x, n) + c) % n; // TODO: Swap this modulo for the UInt128 Montgomery
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:556: y = (MulMod128(y, y, n) + c) % n; // TODO: Use the same optimized reducer for the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:563: y = (MulMod128(y, y, n) + c) % n; // TODO: Replace with the optimized reducer so both
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:571: d = Gcd128(diff, n, ct); // TODO: Move this to the binary GCD implementation once the
|
||||
PerfectNumbers.Core\MersennePrimeFactorTester.cs:593: t = a % b; // TODO: Replace with the binary GCD helper once the UInt128 benchmarks land so
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:20: // TODO: Replace these `%` reductions with the shared ProcessEightBitWindows helper so initialization benefits from the
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:26: // TODO: Inline this accessor into callers so the hot residue path reads the backing fields directly
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:42: // TODO: Replace this repeated doubling with the shared ProcessEightBitWindows helper (or cycle-aware lookup)
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:45: // TODO: Once divisor cycles power the residue stepper, prefer stepping by cycle lengths instead of incrementing
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:57: // TODO: Once the scalar windowed pow2 helper lands, reroute this `%` path through it to avoid the slower
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:64: // TODO: Switch to a cached divisor-cycle remainder instead of computing a fresh modular inverse for every
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:71: // TODO: Use the shared pow2mod remainder cache here so this `%` becomes a subtraction-based fold instead of the
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:80: // TODO: Collapse this wrapper once callers can invoke Step directly; the extra indirection shows up
|
||||
PerfectNumbers.Core\MersenneResidueStepper.cs:102: // TODO: Replace this division-heavy Euclidean loop with the binary modular inverse that reuses divisor
|
||||
PerfectNumbers.Core\KRangeFinder.cs:13: // TODO: Pull alphaP from AlphaCache (or a pooled rational builder) so this loop reuses the
|
||||
PerfectNumbers.Core\KRangeFinder.cs:16: // TODO: Switch this multiply to the pooled ERational span helpers identified in the scanner's
|
||||
PerfectNumbers.Core\KRangeFinder.cs:41: // TODO: Collapse this wrapper once the callers can consume the tuple directly; the extra call adds
|
||||
PerfectNumbers.Core\KRangeFinder.cs:53: // TODO: Replace Parallel.For with the shared low-overhead scheduler highlighted in the divisor-cycle
|
||||
PerfectNumbers.Core\KRangeFinder.cs:83: // TODO: Remove this pass-through once the scanning CLI switches to tuple-returning APIs; flattening
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:189: // TODO: Replace this subtraction loop with the divisor-cycle stepping helper so identity
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:206: // TODO: Replace this PowMod128 call with the upcoming eight-bit window helper once the
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:252: // TODO: Swap this modulo fallback for the shared divisor-cycle remainder helper so 64-bit
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:257: // TODO: Use the Montgomery folding helper measured faster in the Pow2Montgomery benchmarks so this
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:263: // TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:295: // TODO: Route this through the planned UInt128 windowed powmod so the inner MulMod adopts the
|
||||
PerfectNumbers.Core\ModResidueTracker.cs:314: // TODO: Replace this double-and-add fallback with the UInt128 intrinsic-backed multiplier once the
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:49: set => _useDivisorCycles = value; // TODO: Remove this flag once GPU runs unconditionally fetch cycle lengths so every divisor check benefits from the measured speedups.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:298: // TODO: Stage MontgomeryDivisorData in a GPU-ready cache keyed by divisor so we can copy
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:372: // TODO: Replace the block lease with a direct single-cycle lookup so the GPU path
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:395: // TODO: Drop this block hand-off once the cache exposes a direct
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:409: // TODO: Replace this `%` with the cycle-cache helper that subtracts precomputed
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:425: // TODO: Trigger an immediate GPU computation for the single missing cycle without storing
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:476: // TODO: Preload ProcessEightBitWindows-friendly divisor metadata for GPU batches so this
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:563: hits[index] = exponent.Pow2MontgomeryMod(divisor) == 1UL ? (byte)1 : (byte)0; // TODO: Replace Pow2MontgomeryMod with the ProcessEightBitWindows helper once available so GPU by-divisor scans reuse the benchmarked fast pow2 ladder.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:632: // TODO: Replace this guard with an on-demand cycle computation that runs on the device selected
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:787: // TODO: Replace this `%` with the divisor-cycle remainder helper so we reuse cached
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:858: results[index] = exponent.Pow2MontgomeryModMontgomery(divisor); // TODO: Swap this Montgomery-domain pow2 with the windowed helper slated to replace the slower ladder in Pow2Minus1Mod benchmarks.
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:43: // TODO: Swap this per-run enumerator creation for the shared PrimeIterator so by-divisor scans reuse the
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:219: // TODO: Swap Task.Factory.StartNew for the shared low-overhead work queue once the scanner's
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:240: bool useDivisorCycles = tester.UseDivisorCycles; // TODO: Remove the conditional path once all testers always enable divisor cycles to keep every divisor scan on the faster cached-length track.
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:262: // TODO: Remove this block leasing once the cache exposes a direct
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:266: // TODO: When divisorCycle returns zero, compute only that single cycle on the
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:421: // TODO: Replace this odd-only linear allocator with the divisor-cycle aware stride planner once the cache exposes
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:498: // TODO: Integrate divisor-cycle skip tables here so the local allocator leaps across cached composite
|
||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:521: // TODO: Replace these `%` filters with Mod3/Mod5/Mod7/Mod11 helpers so candidate sieving reuses the
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:78: // TODO: Replace this `%` check with the shared divisor-cycle divisibility helper so GPU scans
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:114: // TODO: When this lookup falls outside the preloaded snapshot, invoke the single-cycle GPU helper
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:117: // TODO: Feed this check through the ProcessEightBitWindows-based residue tracker so we avoid the slow
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:150: // TODO: Swap Pow2Mod for the ProcessEightBitWindows helper once Pow2Minus1Mod adopts it;
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberDivisorGpuTester.cs:169: // TODO: Replace this trailing Pow2Mod call with the ProcessEightBitWindows variant once the shared
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberIncrementalGpuTester.cs:23: var pow2Kernel = gpuLease.Pow2ModKernel; // TODO: Route this binding to the ProcessEightBitWindows kernel once the scalar helper lands so GPU incremental scans inherit the windowed speedup from GpuPow2ModBenchmarks.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberIncrementalGpuTester.cs:49: // TODO: Swap this multiply-and-add initialization for the divisor-cycle stepping helper so the GPU incremental path reuses cached offsets instead of recomputing twoP multiples every batch.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberIncrementalGpuTester.cs:82: // TODO: Replace this per-lane multiplication and the subsequent q += twoP stride with the shared divisor-cycle ladder so each iteration advances by cached cycle increments and automatically triggers on-demand GPU computations when the snapshot lacks the requested cycle.
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:45: // TODO: Remove this wrapper and expose the field directly once callers switch to struct-based residue
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:78: // TODO: Once divisor cycle lookups feed residue scanning, reuse the cached cycle length here to skip directly to the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:121: // TODO: Replace the `% 10` computation with the Mod10 helper once Ending7Automaton plugs into the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:126: // TODO: Precompute the initial residues via the divisor-cycle cache so this setup phase reuses the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:133: // TODO: Swap this `%` check for the binary-gcd aware reducer once the automaton wiring shares the
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:140: // TODO: Inline this getter into the automaton consumers so residue scans can read the backing field
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:155: // TODO: Pre-register every modulus routed through the automaton so the fallback `%` path disappears;
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:183: // TODO: Once divisor cycles are mandatory, reuse the cached cycle offset per modulus to skip directly to valid
|
||||
PerfectNumbers.Core\ModuloAutomata.cs:201: // TODO: Switch this to the binary GCD helper used in GpuUInt128BinaryGcdBenchmarks so we avoid `%`
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:46: // TODO: Inline this wrapper once callers request IsPrime directly so the Lucas–Lehmer fast path
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:57: // TODO: Replace this `% 3` guard with ULongExtensions.Mod3 once GPU LL filtering reuses the benchmarked bitmask helper.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:85: var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:116: var kernel = GetBatchKernel(accelerator); // TODO: Switch this batch path to the ProcessEightBitWindows residue kernel once Lucas–Lehmer integrates the benchmarked windowed pow2 helper for small exponents.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:126: // TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128)
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:190: // TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:300: // TODO: Replace int.TryParse with the span-based Utf8Parser helper when loading cached parameters so we avoid
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:307: // TODO: Switch these ulong.Parse calls to the Utf8Parser-based fast-path once we expose a zero-allocation reader for
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:327: // TODO: Replace StreamWriter with the pooled TextFileWriter pipeline so persisting NTT
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:344: // TODO: Move this Parallel.For to the shared low-overhead work scheduler once the NTT parameter
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:395: // TODO: Reuse the divisor-cycle cache to factor phi via the precomputed small-prime windows once
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:424: // TODO: Replace these `%` factor checks with the shared Mod helpers (Mod3/Mod5/etc.) once the GPU
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:458: // TODO: Swap this `%` for the divisor-cycle aware Mod helper once the residue pre-checks expose it
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:476: // TODO: Route these powmods through the ProcessEightBitWindows helper once it lands for GPU
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberLucasLehmerGpuTester.cs:490: // TODO: Swap this UInt128 `%` reduction for the GPU-compatible MulMod helper once it adopts the faster
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:22: private static readonly ConcurrentDictionary<ulong, CacheEntry> Cache = new(); // TODO: Replace this with a plain Dictionary that never mutates after startup once the divisor-cycle snapshot becomes read-only so Montgomery lookups avoid any concurrent collections.
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:23: // TODO: Delete the block-membership tracking entirely when the divisor-cycle cache stops producing
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:36: // TODO: Short-circuit block indices greater than zero once the cache stops prefetching future
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:44: // TODO: Remove this entire release hook once the cycle cache no longer hands out dynamic blocks;
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:97: // TODO: Collapse this retry loop once the cache becomes a simple snapshot dictionary. With the
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:104: // TODO: Delete this method once block membership goes away; the single snapshot model must avoid
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:122: // TODO: Return 0 here once the divisor-cycle cache no longer tracks additional blocks so
|
||||
PerfectNumbers.Core\MontgomeryDivisorData.cs:138: // TODO: Replace this generic `%` reduction with the UInt128 Montgomery folding helper measured faster in
|
||||
PerfectNumbers.Core\PerfectNumberConstants.cs:10: // TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
||||
PerfectNumbers.Core\PerfectNumberConstants.cs:12: // TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:25: }; // TODO: Migrate the Pow2Mod branch to the ProcessEightBitWindows order kernel once the shared helper replaces the single-bit ladder so GPU order scans match benchmark wins.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:26: // TODO: Inline the IncrementalOrderKernel call once the ProcessEightBitWindows helper lands so this wrapper stops forwarding directly to the kernel and the hot path loses one indirection.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:28: var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberOrderGpuTester.cs:42: // TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:11: // TODO: Integrate MersenneDivisorCycles.Shared to consult cycle lengths for small q (<= 4M) and fast-reject
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:14: // TODO: When cycles are missing for larger q values, compute only the single required cycle on the device
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:30: var kernel = gpuLease.Pow2ModKernel; // TODO: Swap this to the ProcessEightBitWindows kernel once GpuUInt128.Pow2Minus1Mod adopts the shared windowed helper measured fastest in GpuPow2ModBenchmarks.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:48: // TODO: Replace the direct UInt128 multiply/add updates below with the residue-cycle stepping helper so that q advances reuse cached cycle increments rather than recomputing twoP multiples on every iteration.
|
||||
PerfectNumbers.Core\Gpu\MersenneNumberResidueGpuTester.cs:98: q += twoP * processed; // TODO: Swap this multiplication for the shared cycle stepping helper once residue cycles are mandatory so we can reuse the cached remainder ladder instead of performing UInt128 multiply operations.
|
||||
PerfectNumbers.Core\PowerCache.cs:16: // TODO: Rent this buffer from ArrayPool<BigInteger> (or a dedicated pooled cache) so expanding
|
||||
PerfectNumbers.Core\PowerCache.cs:27: // TODO: Replace Array.Resize with a pooled copy so we reuse previously rented buffers instead
|
||||
PerfectNumbers.Core\PowerCache.cs:42: // TODO: Swap this BigInteger chain with the UInt128-based Montgomery ladder from
|
||||
PerfectNumbers.Core\PowerCache.cs:76: // TODO: Move the high-precision branch to the benchmark project once production
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:28: // TODO(NTT-OPT): Consider passing backend explicitly to avoid global state.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:32: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128>> MulKernelCache = new(); // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:33: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>> StageKernelCache = new(); // TODO: Same as above – materialize staged kernels during startup so we can drop ConcurrentDictionary usage entirely per the benchmark guidance.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:34: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128>> ScaleKernelCache = new(); // TODO: Promote to the static kernel table initialized alongside the GpuModularArithmeticBenchmarks fast path.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:35: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong>> StageMontKernelCache = new(); // TODO: Inline the Montgomery kernels into the startup table instead of using a concurrent cache now that we no longer mutate state at runtime.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:36: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>> StageBarrett128KernelCache = new(); // TODO: Same plan – reuse the precomputed kernels measured fastest in MontgomeryMultiplyBenchmarks rather than looking them up dynamically.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:37: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong>> ScaleBarrett128KernelCache = new(); // TODO: Collapse into the startup kernel array once Barrett128 constants are preloaded.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:38: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>> SquareBarrett128KernelCache = new(); // TODO: Fold these kernels into the same startup table to avoid concurrent access overhead highlighted in GpuModularArithmeticBenchmarks.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:39: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>> ToMont64KernelCache = new(); // TODO: Prebind and reuse the Montgomery conversions from MontgomeryMultiplyBenchmarks instead of storing them in a concurrent cache.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:40: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong>> FromMont64KernelCache = new(); // TODO: As above – drop ConcurrentDictionary once startup prewarms every kernel.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:41: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong>> SquareMont64KernelCache = new(); // TODO: Inline into the static kernel table established during initialization.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:42: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>> ScaleMont64KernelCache = new(); // TODO: Move to the startup kernel table so Montgomery-scaled launches skip concurrent lookups.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:44: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128>> ForwardKernelCache = new(); // TODO: Warm these forward kernels during initialization and store them in a plain array so the LucasLehmerGpuBenchmarks launch costs disappear.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:46: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128>> InverseKernelCache = new(); // TODO: Same as above – replace with startup-prepared tables per LucasLehmerGpuBenchmarks guidance.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:48: private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:60: // TODO(MOD-OPT): Precompute and store Montgomery/Barrett constants for modulus here
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:114: // TODO: Pull these stage roots from the precomputed tables measured fastest in
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:125: // TODO: Reuse the same precomputed tables for inverse roots so we do not repeat the
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:148: // TODO: Preload MontNPrime64/MontR values using the MontgomeryMultiplyBenchmarks helper so
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:235: // TODO(NTT-OPT): Add a TwiddleCache for stage-wise Cooley–Tukey NTT.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:333: // TODO(NTT-OPT): Clear TwiddleCache once introduced.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:356: // TODO(NTT-OPT): Remove per-accelerator twiddle buffers once added.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:398: // TODO(NTT-OPT): Introduce stage-wise Cooley–Tukey kernels and a
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:407: // TODO(NTT-OPT): Mirror the forward stage-wise design for the inverse
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:420: // TODO(NTT-OPT): Consider shared-memory tiling to improve locality:
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:424: // TODO: Replace this `%` with a bitmask when `half` is a power of two so the stage index math matches the faster
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:500: // TODO: Use the bitmask-based remainder helper here as well to remove `%` from the butterfly stage and align with the
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:547: // TODO(MOD-OPT): Stage kernel using 64-bit Montgomery multiplication. Requires data and twiddles in Montgomery domain.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:551: // TODO: Apply the bitmask remainder helper to this stage too so every butterfly path drops the slower `%` operation.
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:752: // TODO(NTT-OPT): Replace this reference O(n^2) kernel with a stage-wise
|
||||
PerfectNumbers.Core\Gpu\NttGpuMath.cs:772: // TODO(NTT-OPT): Replace this O(n^2) inverse with stage-wise butterflies
|
||||
PerfectNumbers.Core\PrimeCache.cs:26: // TODO: Swap the Open.Numeric enumerator for the staged sieve batches we benchmarked; the
|
||||
PerfectNumbers.Core\PrimeCache.cs:28: // TODO: Fold in the Mod6 stride planner that topped Mod6ComparisonBenchmarks so the CPU cache
|
||||
PerfectNumbers.Core\PrimeCache.cs:93: // TODO: Move this incremental append to the shared sieve batches so we amortize the
|
||||
PerfectNumbers.Core\PrimeCache.cs:96: // TODO: Apply the Mod6 stride scheduler here as well so on-demand extensions mirror the
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:55: // TODO: Reuse a single squared local for `p` within this loop so we avoid recalculating
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:63: // TODO: Replace this trial-division `%` with the sieve-based generator that avoids
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:78: // TODO: Reuse a single cached `candidateSquared` per iteration so we avoid issuing
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:104: // TODO: Switch this increment to the Mod6 stepping table identified in the Mod6ComparisonBenchmarks
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:113: // TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
|
||||
PerfectNumbers.Core\PrimesGenerator.cs:125: // TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster
|
||||
PerfectNumbers.Core\PrimeTester.cs:14: // TODO: Eliminate this wrapper once callers can reach IsPrimeInternal directly; the extra
|
||||
PerfectNumbers.Core\PrimeTester.cs:28: // TODO: Inline the single-value GPU sieve fast path from GpuModularArithmeticBenchmarks so this wrapper
|
||||
PerfectNumbers.Core\PrimeTester.cs:60: // TODO: Replace this modulo check with ULongExtensions.Mod5 so the CPU hot path
|
||||
PerfectNumbers.Core\PrimeTester.cs:82: // TODO: Route this small-prime filtering through the shared divisor-cycle cache once
|
||||
PerfectNumbers.Core\PrimeTester.cs:121: // TODO: Replace this ad-hoc ArrayPool buffer with the pinned span cache from
|
||||
PerfectNumbers.Core\PrimeTester.cs:164: // TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in
|
||||
PerfectNumbers.Core\PrimeTester.cs:237: // TODO: Prewarm this per-accelerator cache during startup (and reuse a simple array keyed by accelerator index)
|
||||
PerfectNumbers.Core\PrimeTester.cs:281: // TODO: Replace the `% 5` branch with the GPU Mod5 helper once the sieve kernel can
|
||||
PerfectNumbers.Core\PrimeTester.cs:309: // TODO: Route this modulo through the shared divisor-cycle cache once exposed to GPU kernels
|
||||
PerfectNumbers.Core\PrimeTester.cs:322: // TODO: Replace this on-the-fly GCD probe with the cached factor table derived from
|
||||
PerfectNumbers.Core\PrimeTester.cs:331: // TODO: Route this batch helper through the shared GPU kernel pool from
|
||||
PerfectNumbers.Core\PrimeTester.cs:357: // TODO: Swap this handwritten binary GCD for the optimized helper measured in
|
||||
PerfectNumbers.Core\PrimeTester.cs:398: // TODO: Replace this inline GPU binary GCD with the kernel extracted from
|
||||
PerfectNumbers.Core.Tests\TextFileWriterTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core\RationalHelper.cs:13: // TODO: Replace the BigInteger-based GCD with the UInt128-friendly binary GCD helper so this rounding path avoids
|
||||
PerfectNumbers.Core\RationalHelper.cs:20: // TODO: Cache a reusable byte buffer here so converting to EInteger no longer allocates per call when parsing alpha
|
||||
PerfectNumbers.Core\RationalHelper.cs:28: // TODO: Inline this floor conversion with the span-based ladder that dominated
|
||||
PerfectNumbers.Core\RationalHelper.cs:39: // TODO: Replace this ceiling branch with the branchless adjustment measured fastest
|
||||
PerfectNumbers.Core\RationalNumbers.cs:8: // TODO: Precompute additional frequently used rationals (e.g., OneHalf, ThreeHalves) so hot alpha computations stop
|
||||
PerfectNumbers.Core.Tests\RleBlacklistTests.cs:7: [Fact(Skip = "TODO: implement test")]
|
||||
PerfectNumbers.Core\RleBlacklist.cs:31: // TODO: Replace this HashSet with the pooled ValueHashSet from the blacklist benchmarks so repeated
|
||||
PerfectNumbers.Core\RleBlacklist.cs:144: // TODO: Cache BuildRleKey results per divisor cycle bucket so residue scans reuse the
|
||||
PerfectNumbers.Core\RleBlacklist.cs:154: // TODO: Replace this per-bit loop with the lookup-table driven builder validated in the
|
||||
PerfectNumbers.Core\RleBlacklist.cs:247: // TODO: Replace int.TryParse with the Utf8Parser-based span helper so blacklist normalization avoids
|
||||
PerfectNumbers.Core\RleBlacklist.cs:330: // TODO: Swap this int.TryParse for the upcoming Utf8Parser fast path so nested patterns reuse the
|
||||
PerfectNumbers.Core\StringBuilderPool.cs:12: // TODO: Ensure oversized builders remain eligible for reuse without trimming so call sites keep their full capacity.
|
||||
PerfectNumbers.Core\StringBuilderPool.cs:19: // TODO: Preserve the builder's capacity when returning it so the pool hands the same buffer back without shrinkage.
|
||||
EvenPerfectBitScanner.Benchmarks\MulHighBenchmarks.cs:40: // TODO: Replace ULongExtensions.MulHigh with the UInt128-based helper on CPU paths; it was
|
||||
PerfectNumbers.Core\TextFileWriter.cs:28: // TODO: Surface a lock-free position snapshot (for example via Interlocked.Read on a cached long)
|
||||
PerfectNumbers.Core\TextFileWriter.cs:48: // TODO: Use the pooled writer buffers planned for the TextFileWriter fast path so truncation flushes
|
||||
PerfectNumbers.Core\TextFileWriter.cs:65: // TODO: Buffer these writes through an ArrayPool-backed accumulator so we only flush when the
|
||||
PerfectNumbers.Core\TextFileWriter.cs:80: // TODO: Buffer binary writes with the same ArrayPool-backed accumulator planned for text so GPU result
|
||||
PerfectNumbers.Core\TextFileWriter.cs:89: // TODO: Return the writers to a pooled wrapper once the buffered pipeline lands so disposing the
|
||||
EvenPerfectBitScanner.Benchmarks\MulMod64Benchmarks.cs:172: // TODO: Callers in production should migrate to ULongExtensions.MulMod64 where GPU parity
|
||||
EvenPerfectBitScanner.Benchmarks\MulMod64Benchmarks.cs:188: // TODO: Retire the deferred GPU shim from runtime paths and keep it only for benchmarks;
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:11: // TODO: Replace this hand-rolled binary GCD with the shared subtract-free ladder from
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:63: // TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:79: // TODO: Switch this divisor-order powmod to the ProcessEightBitWindows helper so the
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:123: // TODO: Replace this direct `%` test with the shared divisor-cycle filter once the
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:138: // TODO: Fold these reductions into the multiply-high trick captured in Mod3BenchmarkResults so
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:147: // TODO: Replace the `% 5` operations with the precomputed multiply-high constants from the Mod5
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:167: // TODO: Inline the lookup-based Mod7 reducer validated in the residue benchmarks so this helper
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:191: // TODO: Precompute the 2^64 ≡ 6 folding constants once so this path stops recomputing `% 10`
|
||||
PerfectNumbers.Core\UInt128Extensions.cs:214: // TODO: Collapse this Mod10 switch into the shared lookup table from the CLI benchmarks so we
|
||||
PerfectNumbers.Core\UInt128Numbers.cs:9: // TODO: Populate this table from the UInt128 residue benchmarks so GPU kernels can pull precomputed constants
|
||||
PerfectNumbers.Core\UInt128Numbers.cs:11: // TODO: Extend the table with cached Mod3/Mod5 folding constants so both CPU and GPU residue helpers can reuse
|
||||
PerfectNumbers.Core\UIntExtensions.cs:9: // TODO: Only add Mod7/Mod11 lookup helpers once we have a variant that beats the `%` baseline (current prototypes lose per Mod7/Mod11 benchmarks).
|
||||
PerfectNumbers.Core\ULongExtensions.cs:23: // TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
PerfectNumbers.Core\ULongExtensions.cs:36: // TODO: Replace this `%` driven factor peeling with the divisor-cycle aware
|
||||
PerfectNumbers.Core\ULongExtensions.cs:87: // TODO: Swap this modulo check for the shared small-prime cycle filter once the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:204: // TODO: Investigate replacing this manual decomposition with the UInt128-based implementation
|
||||
PerfectNumbers.Core\ULongExtensions.cs:238: // TODO: Replace this `%` with the Montgomery folding helper highlighted in MulMod64Benchmarks so the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:257: // TODO: Replace this fallback with the UInt128 Montgomery helper measured fastest in
|
||||
PerfectNumbers.Core\ULongExtensions.cs:264: // TODO: Remove this GPU-compatible shim from production once callers migrate to MulMod64,
|
||||
PerfectNumbers.Core\ULongExtensions.cs:273: // TODO: Move this deferred helper to the benchmark suite; the baseline MulMod64 avoids the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:313: // TODO: Switch to the ProcessEightBitWindows helper once the scalar implementation ships so CPU callers
|
||||
PerfectNumbers.Core\ULongExtensions.cs:347: // TODO: Route this Montgomery-domain variant through the same windowed pow2 helper once available so delta
|
||||
PerfectNumbers.Core\ULongExtensions.cs:372: // TODO: Swap this modulo with the divisor-cycle remainder helper from Pow2MontgomeryModCycleComputationBenchmarks so we
|
||||
PerfectNumbers.Core\ULongExtensions.cs:395: // TODO: Port this scalar PowMod fallback to the ProcessEightBitWindows helper so CPU callers get the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:447: // TODO: Wire this cycle-aware overload into the ProcessEightBitWindows helper so the reduced exponent path
|
||||
PerfectNumbers.Core\ULongExtensions.cs:481: // TODO: Replace this modulo with the cached cycle remainder produced by the divisor-cycle cache so PowModWithCycle avoids
|
||||
PerfectNumbers.Core\ULongExtensions.cs:502: // TODO: Replace this UInt128-cycle overload with the ProcessEightBitWindows helper so large-exponent CPU scans
|
||||
PerfectNumbers.Core\ULongExtensions.cs:534: // TODO: Swap this modulo with the upcoming UInt128 cycle remainder helper so large-exponent scans reuse cached
|
||||
PerfectNumbers.Core\ULongExtensions.cs:560: // TODO: Migrate this UInt128 exponent overload to ProcessEightBitWindows so the large-cycle reductions drop the
|
||||
PerfectNumbers.Core\ULongExtensions.cs:592: // TODO: Swap this modulo with the shared UInt128 cycle remainder helper once available so CRT powmods reuse cached
|
||||
PerfectNumbers.Core\ULongExtensions.cs:639: // TODO: Replace this `% modulusCandidate` with the cached residue helper derived from Mod10_8_5_3Benchmarks so CRT
|
||||
PerfectNumbers.Core\ULongExtensions.cs:648: // TODO: Swap this final `% modulus` with the pooled remainder cache so the CRT result write-back avoids one more division,
|
||||
Binary file not shown.
Binary file not shown.
Reference in new issue
Block a user