MersenneDivisorCycles converted to device template.
This commit is contained in:
1 parent
31ecf24522
commit
74837a407e
23 files changed
+272
-647
No files matched your search
@@ -235,7 +235,8 @@ public sealed class DeviceDependentGenerator : IIncrementalGenerator
|
||||
private static TypeDeclarationSyntax TransformTemplateType(TypeDeclarationSyntax templateType, string generatedTypeName)
|
||||
{
|
||||
TypeDeclarationSyntax withoutAttribute = RemoveDeviceDependentAttribute(templateType);
|
||||
return withoutAttribute.WithIdentifier(SyntaxFactory.Identifier(generatedTypeName));
|
||||
TypeDeclarationSyntax renamed = (TypeDeclarationSyntax)new SelfIdentifierRewriter(templateType.Identifier.ValueText, generatedTypeName).Visit(withoutAttribute)!;
|
||||
return renamed.WithIdentifier(SyntaxFactory.Identifier(generatedTypeName));
|
||||
}
|
||||
|
||||
private static TypeDeclarationSyntax RemoveDeviceDependentAttribute(TypeDeclarationSyntax typeDeclaration)
|
||||
@@ -271,6 +272,28 @@ public sealed class DeviceDependentGenerator : IIncrementalGenerator
|
||||
return typeDeclaration.WithAttributeLists(SyntaxFactory.List(newLists));
|
||||
}
|
||||
|
||||
private sealed class SelfIdentifierRewriter : CSharpSyntaxRewriter
|
||||
{
|
||||
private readonly string templateName;
|
||||
private readonly string generatedName;
|
||||
|
||||
public SelfIdentifierRewriter(string templateName, string generatedName)
|
||||
{
|
||||
this.templateName = templateName;
|
||||
this.generatedName = generatedName;
|
||||
}
|
||||
|
||||
public override SyntaxToken VisitToken(SyntaxToken token)
|
||||
{
|
||||
if (token.IsKind(SyntaxKind.IdentifierToken) && string.Equals(token.ValueText, templateName, StringComparison.Ordinal))
|
||||
{
|
||||
return SyntaxFactory.Identifier(token.LeadingTrivia, generatedName, token.TrailingTrivia);
|
||||
}
|
||||
|
||||
return base.VisitToken(token);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsDeviceDependentTemplateAttribute(NameSyntax attributeName)
|
||||
{
|
||||
string text = attributeName.ToString();
|
||||
@@ -380,6 +403,11 @@ public sealed class DeviceDependentGenerator : IIncrementalGenerator
|
||||
return generatedFileName;
|
||||
}
|
||||
|
||||
if (relativeDirectory.StartsWith("..", StringComparison.Ordinal))
|
||||
{
|
||||
return generatedFileName;
|
||||
}
|
||||
|
||||
relativeDirectory = relativeDirectory.Replace('\\', '/');
|
||||
return relativeDirectory.TrimEnd('/') + "/" + generatedFileName;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class Pow2ModBenchmarks
|
||||
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleConvertToStandardCpu(cycle, divisors[i]);
|
||||
}
|
||||
|
||||
@@ -183,7 +183,7 @@ public class Pow2ModBenchmarks
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
for (int i = 0; i < SampleCount; i++)
|
||||
{
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(divisors[i].Modulus);
|
||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(gpu, cycle, divisors[i]);
|
||||
}
|
||||
|
||||
@@ -500,7 +500,7 @@ public class Pow2ModBenchmarks
|
||||
Console.WriteLine($"Calculating small cycle {smallModulus}");
|
||||
smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
smallExponents[i] = NextSmallExponent(random);
|
||||
smallCycles[i] = MersenneDivisorCycles.CalculateCycleLengthCpu(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
smallCycles[i] = MersenneDivisorCyclesCpu.CalculateCycleLength(smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle(random);
|
||||
Console.WriteLine($"Calculating large cycle {largeModulus}");
|
||||
@@ -616,7 +616,7 @@ public class Pow2ModBenchmarks
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
return MersenneDivisorCyclesCpu.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
ulong order = PrimeOrderCalculator.CalculateCpu(
|
||||
@@ -632,14 +632,14 @@ public class Pow2ModBenchmarks
|
||||
}
|
||||
|
||||
previousPrimeOrder = null;
|
||||
return MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
return MersenneDivisorCyclesCpu.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
private static UInt128 CalculateCycleLengthWithHeuristicsCpu(UInt128 modulus, ref UInt128? previousWidePrimeOrder)
|
||||
{
|
||||
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
|
||||
{
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
return MersenneDivisorCyclesCpu.GetCycle(modulus);
|
||||
}
|
||||
|
||||
UInt128 order = PrimeOrderCalculator.CalculateCpu(
|
||||
@@ -654,7 +654,7 @@ public class Pow2ModBenchmarks
|
||||
}
|
||||
|
||||
previousWidePrimeOrder = null;
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
return MersenneDivisorCyclesCpu.GetCycle(modulus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,7 +683,7 @@ public class Pow2ModBenchmarks
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCyclesCpu.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -704,7 +704,7 @@ public class Pow2ModBenchmarks
|
||||
_previousPrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthCpu(modulus, divisorData);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCyclesCpu.CalculateCycleLength(modulus, divisorData);
|
||||
}
|
||||
|
||||
private ulong CalculateCycleLengthWithHeuristicsGpu(PrimeOrderCalculatorAccelerator gpu, ulong modulus, ulong fallbackOrder = 0UL)
|
||||
@@ -712,7 +712,7 @@ public class Pow2ModBenchmarks
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(modulus);
|
||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||
{
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, modulus, divisorData);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, modulus, divisorData);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -734,14 +734,14 @@ public class Pow2ModBenchmarks
|
||||
_previousPrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, modulus, divisorData);
|
||||
return fallbackOrder != 0UL ? fallbackOrder : MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, modulus, divisorData);
|
||||
}
|
||||
|
||||
private UInt128 CalculateCycleLengthWithHeuristicsCpu(UInt128 modulus)
|
||||
{
|
||||
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
|
||||
{
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
return MersenneDivisorCyclesCpu.GetCycle(modulus);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -761,14 +761,14 @@ public class Pow2ModBenchmarks
|
||||
_previousWidePrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return MersenneDivisorCycles.GetCycleCpu(modulus);
|
||||
return MersenneDivisorCyclesCpu.GetCycle(modulus);
|
||||
}
|
||||
|
||||
private UInt128 CalculateCycleLengthWithHeuristicsGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 modulus)
|
||||
{
|
||||
if (modulus <= UInt128.One || (modulus & UInt128.One) == UInt128.Zero)
|
||||
{
|
||||
return MersenneDivisorCycles.GetCycleGpu(gpu, modulus);
|
||||
return MersenneDivisorCyclesGpu.GetCycle(gpu, modulus);
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
@@ -789,7 +789,7 @@ public class Pow2ModBenchmarks
|
||||
_previousWidePrimeOrder = null;
|
||||
|
||||
Console.WriteLine($"Wide heuristic failed for {modulus}, falling back to full cycle calculation");
|
||||
return MersenneDivisorCycles.GetCycleGpu(gpu, modulus);
|
||||
return MersenneDivisorCyclesGpu.GetCycle(gpu, modulus);
|
||||
}
|
||||
|
||||
private static ulong ComputeMontgomeryResidue(ulong value, ulong modulus) => (ulong)((UInt128)value * (UInt128.One << 64) % modulus);
|
||||
@@ -1209,3 +1209,4 @@ public class Pow2ModBenchmarks
|
||||
return (ulong)(product % modulus);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public class Pow2MontgomeryModBenchmarks
|
||||
ulong smallModulus = NextSmallOddModulus();
|
||||
_smallDivisors[i] = CreateMontgomeryDivisorData(smallModulus);
|
||||
_smallExponents[i] = NextSmallExponent();
|
||||
_smallCycles[i] = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
_smallCycles[i] = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, smallModulus, MontgomeryDivisorData.FromModulus(smallModulus));
|
||||
|
||||
Console.WriteLine($"Calculating large cycle {i + 1} with heuristics");
|
||||
(ulong largeModulus, ulong largeCycle) = NextLargeModulusAndCycle();
|
||||
@@ -524,3 +524,4 @@ public class Pow2MontgomeryModBenchmarks
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
||||
Pow2MontgomeryModWindowedBenchmarkSeed seed = seeds[i];
|
||||
ulong modulus = 2UL * seed.Multiplier * seed.Exponent + 1UL;
|
||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(modulus);
|
||||
ulong cycleLength = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, modulus, divisor);
|
||||
ulong cycleLength = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, modulus, divisor);
|
||||
ulong reducedExponent = seed.Exponent % cycleLength;
|
||||
cases[i] = new Pow2MontgomeryModWindowedBenchmarkCase(
|
||||
seed.Exponent,
|
||||
@@ -94,3 +94,4 @@ internal static class Pow2MontgomeryModWindowedBenchmarkInputsProvider
|
||||
public ulong Multiplier { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
||||
MersenneDivisorCyclesGpu.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
using var reader = new BinaryReader(stream);
|
||||
@@ -41,7 +41,7 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
{
|
||||
expected.Should().Contain(pair.divisor);
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLengthCpu(pair.divisor, divisorData));
|
||||
pair.cycle.Should().Be(MersenneDivisorCyclesCpu.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
}
|
||||
finally
|
||||
@@ -61,7 +61,7 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
|
||||
MersenneDivisorCyclesCpu.Generate(path, maxDivisor: 50UL, threads: 4);
|
||||
|
||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||
@@ -91,12 +91,12 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
||||
{
|
||||
uniqueDivisors.Add(pair.divisor).Should().BeTrue();
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLengthCpu(pair.divisor, divisorData));
|
||||
pair.cycle.Should().Be(MersenneDivisorCyclesCpu.CalculateCycleLength(pair.divisor, divisorData));
|
||||
}
|
||||
|
||||
uniqueDivisors.Should().BeEquivalentTo(expected);
|
||||
|
||||
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
|
||||
var (_, completeCount) = MersenneDivisorCyclesCpu.FindLast(path);
|
||||
completeCount.Should().Be(expected.Count);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -23,8 +23,8 @@ public class MersenneDivisorCyclesTests
|
||||
try
|
||||
{
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData).Should().Be(expected);
|
||||
MersenneDivisorCycles.CalculateCycleLengthGpu(divisor).Should().Be(expected);
|
||||
MersenneDivisorCyclesCpu.CalculateCycleLength(divisor, divisorData).Should().Be(expected);
|
||||
MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, divisor, divisorData).Should().Be(expected);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -41,13 +41,13 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
|
||||
ulong[] primes = [5UL, 7UL, 11UL, 13UL];
|
||||
|
||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLengthCpu(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
ulong cycle23 = MersenneDivisorCyclesCpu.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes).Should().BeTrue();
|
||||
|
||||
ulong[] primesWithoutHit = [5UL, 7UL, 13UL];
|
||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primesWithoutHit).Should().BeFalse();
|
||||
|
||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLengthCpu(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||
ulong cycle31 = MersenneDivisorCyclesCpu.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes).Should().BeTrue();
|
||||
|
||||
ulong[] primesBeforeHit = [7UL, 11UL, 13UL];
|
||||
@@ -63,7 +63,7 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
ulong[] exponents = [6UL, 7UL, 9UL, 10UL, 12UL];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthCpu(7UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCyclesCpu.CalculateCycleLength(7UL, divisorData);
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents).Should().BeTrue();
|
||||
|
||||
ulong[] nonDivisibleExponents = [5UL, 11UL];
|
||||
@@ -79,7 +79,7 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
ulong[] exponents = [10UL, 11UL, 20UL, 21UL, 30UL];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthCpu(11UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCyclesCpu.CalculateCycleLength(11UL, divisorData);
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents).Should().BeTrue();
|
||||
|
||||
ulong[] nonDivisibleExponents = [5UL, 17UL];
|
||||
@@ -104,3 +104,4 @@ public class MersenneNumberDivisorCpuTesterTests
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -235,13 +235,13 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
|
||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
ulong cycle223 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
||||
|
||||
ulong[] primesBeforeHit = [31UL];
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primesBeforeHit).Should().BeFalse();
|
||||
|
||||
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||
ulong cycle13367 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes).Should().BeTrue();
|
||||
|
||||
ulong[] primesWithoutMatchingExponent = [31UL, 37UL, 43UL];
|
||||
@@ -271,7 +271,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
|
||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL, 47UL];
|
||||
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
ulong cycle223 = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
||||
|
||||
ulong[] primesBeforeHit = [31UL];
|
||||
@@ -302,7 +302,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
ulong[] exponents = [6UL, 7UL, 9UL, 10UL, 12UL];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 7UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 7UL, divisorData);
|
||||
|
||||
session.CheckDivisor(7UL, divisorData, cycle, exponents).Should().BeTrue();
|
||||
|
||||
@@ -329,7 +329,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
ulong[] exponents = [10UL, 11UL, 20UL, 21UL, 30UL];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 11UL, divisorData);
|
||||
ulong cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 11UL, divisorData);
|
||||
|
||||
session.CheckDivisor(11UL, divisorData, cycle, exponents).Should().BeTrue();
|
||||
|
||||
@@ -346,9 +346,9 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
[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 cycles = MersenneDivisorCyclesGpu.Shared;
|
||||
var tableField = typeof(MersenneDivisorCyclesGpu).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
var smallCyclesField = typeof(MersenneDivisorCyclesGpu).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
|
||||
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||
@@ -360,7 +360,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
||||
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, 191UL, divisorData);
|
||||
patchedSmall[191] = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, 191UL, divisorData);
|
||||
|
||||
tableField.SetValue(cycles, patchedTable);
|
||||
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||
@@ -393,7 +393,7 @@ public class MersenneNumberDivisorGpuTesterTests
|
||||
var session = new MersenneCpuDivisorScanSessionWithCpuOrder();
|
||||
|
||||
ulong[] primes = [31UL, 37UL, 41UL, 43UL, 47UL];
|
||||
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLengthCpu(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
ulong cycle223 = MersenneDivisorCyclesCpu.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes).Should().BeTrue();
|
||||
|
||||
ulong[] primesBeforeHit = [31UL];
|
||||
|
||||
@@ -45,9 +45,9 @@ public class MersenneNumberOrderGpuTesterTests
|
||||
writer.Write((ulong)uint.MaxValue);
|
||||
}
|
||||
|
||||
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 cycles = MersenneDivisorCyclesGpu.Shared;
|
||||
var tableField = typeof(MersenneDivisorCyclesGpu).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
var smallCyclesField = typeof(MersenneDivisorCyclesGpu).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
|
||||
var originalTable = ((List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!).Select(x => x).ToList();
|
||||
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||
@@ -88,4 +88,3 @@ public class MersenneNumberOrderGpuTesterTests
|
||||
isPrime.Should().Be(expectedPrime);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,7 +193,7 @@ public class PrimeOrderCalculatorTests
|
||||
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
|
||||
try
|
||||
{
|
||||
var mersenneDivisorCycles = MersenneDivisorCycles.GetCycleGpu(gpu, prime).Should().Be((UInt128)1229782938247309446UL);
|
||||
var mersenneDivisorCycles = MersenneDivisorCyclesGpu.GetCycle(gpu, prime).Should().Be((UInt128)1229782938247309446UL);
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -225,3 +225,4 @@ public class PrimeOrderCalculatorTests
|
||||
return order;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace PerfectNumbers.Core.Cpu;
|
||||
|
||||
public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
|
||||
{
|
||||
private static readonly MersenneDivisorCycles _cycles = MersenneDivisorCycles.Shared;
|
||||
private static readonly MersenneDivisorCyclesCpu _cycles = MersenneDivisorCyclesCpu.Shared;
|
||||
private readonly GpuKernelType _kernelType = kernelType;
|
||||
|
||||
public void Scan(PrimeOrderCalculatorAccelerator gpu, ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
|
||||
@@ -31,7 +31,7 @@ public class MersenneNumberIncrementalCpuTester(GpuKernelType kernelType)
|
||||
{
|
||||
// TODO: Replace this direct GetCycle call with DivisorCycleCache.Lookup so we reuse the single snapshot block
|
||||
// and compute missing cycles on the configured device without queuing additional block generation.
|
||||
qCycle = MersenneDivisorCycles.GetCycleCpu(q);
|
||||
qCycle = MersenneDivisorCyclesCpu.GetCycle(q);
|
||||
shouldCheck = auto.Mod10R != 5UL;
|
||||
if (shouldCheck)
|
||||
{
|
||||
|
||||
@@ -34,7 +34,7 @@ public class MersenneNumberLucasLehmerCpuTester
|
||||
for (UInt128 i = UInt128.Zero; i < limit; i++)
|
||||
{
|
||||
// Lucas–Lehmer squares the evolving residue, so we stay on the Montgomery powmod path rather than the pow2-specific helper.
|
||||
s = s.PowModWithCycle(two, MersenneDivisorCycles.GetCycleCpu(m)) - two;
|
||||
s = s.PowModWithCycle(two, MersenneDivisorCyclesCpu.GetCycle(m)) - two;
|
||||
if (s < UInt128.Zero)
|
||||
{
|
||||
s += m;
|
||||
@@ -44,3 +44,4 @@ public class MersenneNumberLucasLehmerCpuTester
|
||||
return s == UInt128.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
|
||||
|
||||
while (k <= maxK && Volatile.Read(ref isPrime))
|
||||
{
|
||||
qCycle = MersenneDivisorCycles.GetCycleCpu(q);
|
||||
qCycle = MersenneDivisorCyclesCpu.GetCycle(q);
|
||||
// TODO: When this lookup misses the snapshot, invoke the configured device to compute the
|
||||
// single required cycle on demand without persisting it so the CPU order path retains the
|
||||
// cycle-stepping speedups while honoring the no-extra-cache constraint for large divisors.
|
||||
@@ -76,4 +76,4 @@ public class MersenneNumberOrderCpuTester(GpuKernelType kernelType)
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -39,12 +39,12 @@ public sealed class DivisorCycleCache
|
||||
Accelerator accelerator = AcceleratorPool.Shared.Accelerators[acceleratorIndex];
|
||||
_accelerator = accelerator;
|
||||
_gpuKernel = LoadKernel(accelerator);
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
_snapshot = MersenneDivisorCyclesCpu.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
|
||||
public void RefreshSnapshot()
|
||||
{
|
||||
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
_snapshot = MersenneDivisorCyclesCpu.Shared.ExportSmallCyclesSnapshot();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -60,7 +60,7 @@ public sealed class DivisorCycleCache
|
||||
return cached;
|
||||
}
|
||||
|
||||
return MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData);
|
||||
return MersenneDivisorCyclesCpu.CalculateCycleLength(divisor, divisorData);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
@@ -89,7 +89,7 @@ public sealed class DivisorCycleCache
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
cycleLength = MersenneDivisorCycles.CalculateCycleLengthHybrid(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
cycleLength = MersenneDivisorCyclesHybrid.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ public sealed class DivisorCycleCache
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
cycleLength = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
cycleLength = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: true);
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public sealed class DivisorCycleCache
|
||||
return cached;
|
||||
}
|
||||
|
||||
return MersenneDivisorCycles.CalculateCycleLengthHybrid(gpu, divisor, divisorData);
|
||||
return MersenneDivisorCyclesHybrid.CalculateCycleLength(gpu, divisor, divisorData);
|
||||
}
|
||||
|
||||
public void GetCycleLengthsHybrid(PrimeOrderCalculatorAccelerator gpu, ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
|
||||
@@ -270,7 +270,7 @@ public sealed class DivisorCycleCache
|
||||
int targetIndex = indices[i];
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData);
|
||||
cycles[targetIndex] = MersenneDivisorCyclesCpu.CalculateCycleLength(divisor, divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +282,7 @@ public sealed class DivisorCycleCache
|
||||
int targetIndex = indices[i];
|
||||
ulong divisor = divisors[targetIndex];
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLengthHybrid(gpu, divisor, divisorData);
|
||||
cycles[targetIndex] = MersenneDivisorCyclesHybrid.CalculateCycleLength(gpu, divisor, divisorData);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,7 +150,7 @@ public class GpuKernelPool
|
||||
return;
|
||||
}
|
||||
|
||||
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
var host = MersenneDivisorCyclesGpu.Shared.ExportSmallCyclesSnapshot();
|
||||
MemoryBuffer1D<ulong, Stride1D.Dense>? device;
|
||||
|
||||
// lock (accelerator)
|
||||
|
||||
@@ -452,9 +452,9 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static ulong ResolveDivisorCycle(PrimeOrderCalculatorAccelerator gpu, ulong divisor, ulong prime, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentGpu(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
if (!MersenneDivisorCyclesGpu.TryCalculateCycleLengthForExponent(gpu, divisor, prime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
return MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
return MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
|
||||
return computedCycle;
|
||||
@@ -601,9 +601,9 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
var gpu = _primeOrderCalculatorAccelerator;
|
||||
if (cycle == 0UL)
|
||||
{
|
||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
if (!MersenneDivisorCyclesCpu.TryCalculateCycleLengthForExponent(divisor, firstPrime, divisorData, out ulong computedCycle, out bool primeOrderFailed) || computedCycle == 0UL)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -740,3 +740,4 @@ public sealed partial class MersenneNumberDivisorByDivisorGpuTester : IMersenneN
|
||||
return new DivisorScanSession(gpu, this);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
|
||||
public static void BuildDivisorCandidates()
|
||||
{
|
||||
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
ulong[] snapshot = MersenneDivisorCyclesGpu.Shared.ExportSmallCyclesSnapshot();
|
||||
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
|
||||
ulong cycle;
|
||||
int count = 0, i, snapshotLength = snapshot.Length;
|
||||
@@ -142,7 +142,7 @@ public sealed class MersenneNumberDivisorGpuTester
|
||||
continue;
|
||||
}
|
||||
|
||||
UInt128 cycle128 = MersenneDivisorCycles.GetCycleGpu(gpu, d);
|
||||
UInt128 cycle128 = MersenneDivisorCyclesGpu.GetCycle(gpu, d);
|
||||
if ((UInt128)p % cycle128 != UInt128.Zero)
|
||||
{
|
||||
continue;
|
||||
|
||||
@@ -109,7 +109,7 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
internal static readonly ulong[] CombinedDivisorsEnding7Squares;
|
||||
internal static readonly ulong[] CombinedDivisorsEnding9Squares;
|
||||
|
||||
private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCyclesCpu.Shared.ExportSmallCyclesSnapshot();
|
||||
|
||||
internal enum CombinedDivisorPattern : byte
|
||||
{
|
||||
@@ -507,7 +507,7 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
ulong divisor = preparation.Candidate.Value;
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
bool trySuccess = MersenneDivisorCyclesCpu.TryCalculateCycleLengthForExponent(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
@@ -523,7 +523,7 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
ulong resolvedCycle = MersenneDivisorCyclesCpu.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
@@ -549,7 +549,7 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
ulong divisor = preparation.Candidate.Value;
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
bool trySuccess = MersenneDivisorCyclesCpu.TryCalculateCycleLengthForExponent(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
@@ -565,7 +565,7 @@ public sealed class HeuristicCombinedPrimeTester
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
ulong resolvedCycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
|
||||
@@ -28,7 +28,7 @@ public sealed class HeuristicPrimeTester
|
||||
private static readonly bool UseHeuristicGroupBTrialDivision = false; // Temporary fallback gate for Group B.
|
||||
|
||||
|
||||
private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
|
||||
private static readonly ulong[] HeuristicSmallCycleSnapshot = MersenneDivisorCyclesCpu.Shared.ExportSmallCyclesSnapshot();
|
||||
|
||||
[ThreadStatic]
|
||||
private static BidirectionalCycleRemainderStepper _mod10Stepper;
|
||||
@@ -437,7 +437,7 @@ public sealed class HeuristicPrimeTester
|
||||
ulong divisor = preparation.Candidate.Value;
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
bool trySuccess = MersenneDivisorCyclesCpu.TryCalculateCycleLengthForExponent(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
@@ -453,7 +453,7 @@ public sealed class HeuristicPrimeTester
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthCpu(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
ulong resolvedCycle = MersenneDivisorCyclesCpu.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
@@ -479,7 +479,7 @@ public sealed class HeuristicPrimeTester
|
||||
ulong divisor = preparation.Candidate.Value;
|
||||
MontgomeryDivisorData divisorData = preparation.DivisorData;
|
||||
|
||||
bool trySuccess = MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
bool trySuccess = MersenneDivisorCyclesCpu.TryCalculateCycleLengthForExponent(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
@@ -495,7 +495,7 @@ public sealed class HeuristicPrimeTester
|
||||
}
|
||||
|
||||
primeOrderFailed = primeOrderFailedLocal || !trySuccess || computedCycle == 0UL;
|
||||
ulong resolvedCycle = MersenneDivisorCycles.CalculateCycleLengthGpu(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
ulong resolvedCycle = MersenneDivisorCyclesGpu.CalculateCycleLength(gpu, divisor, divisorData, skipPrimeOrderHeuristic: primeOrderFailed);
|
||||
cycleFromHint = false;
|
||||
cycleComputed = resolvedCycle != 0UL;
|
||||
return resolvedCycle;
|
||||
|
||||
+146
-558
@@ -11,15 +11,17 @@ using PerfectNumbers.Core.Gpu.Accelerators;
|
||||
|
||||
namespace PerfectNumbers.Core;
|
||||
|
||||
public class MersenneDivisorCycles
|
||||
[DeviceDependentTemplate(typeof(ComputationDevice))]
|
||||
public struct MersenneDivisorCyclesTemplate
|
||||
{
|
||||
private List<(ulong divisor, ulong cycleLength)> _table = [];
|
||||
private List<(ulong divisor, ulong cycleLength)> _table;
|
||||
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
||||
private ulong[]? _smallCycles;
|
||||
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
||||
public static readonly MersenneDivisorCyclesTemplate Shared = new();
|
||||
|
||||
public MersenneDivisorCycles()
|
||||
public MersenneDivisorCyclesTemplate()
|
||||
{
|
||||
_table = [];
|
||||
}
|
||||
|
||||
public void LoadFrom(string path)
|
||||
@@ -50,7 +52,11 @@ public class MersenneDivisorCycles
|
||||
return _smallCycles!;
|
||||
}
|
||||
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
public static bool CycleEqualsExponent(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||
#else
|
||||
public static bool CycleEqualsExponent(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||
#endif
|
||||
{
|
||||
if (CycleEqualsExponentForMersenneCandidate(divisor, divisorData, exponent))
|
||||
{
|
||||
@@ -75,88 +81,20 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthHeuristicCpu(divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
if (TryCalculateCycleLengthHeuristic(gpu, divisor, divisorData, out ulong heuristicCycle, skipPrimeOrderHeuristic: false) && heuristicCycle != 0UL)
|
||||
#else
|
||||
if (TryCalculateCycleLengthHeuristic(divisor, divisorData, out ulong heuristicCycle, skipPrimeOrderHeuristic: false) && heuristicCycle != 0UL)
|
||||
#endif
|
||||
{
|
||||
return heuristicCycle == exponent;
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
{
|
||||
return cycleLength == exponent;
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL;
|
||||
}
|
||||
|
||||
public static bool CycleEqualsExponentGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||
{
|
||||
if (CycleEqualsExponentForMersenneCandidate(divisor, divisorData, exponent))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var small = Shared._smallCycles;
|
||||
if (small is not null)
|
||||
{
|
||||
ulong cached = small[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached == exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthHeuristicGpu(gpu, divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||
{
|
||||
return heuristicCycle == exponent;
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
{
|
||||
return cycleLength == exponent;
|
||||
}
|
||||
|
||||
return exponent.Pow2MontgomeryModWindowedConvertToStandardCpu(divisorData) == 1UL;
|
||||
}
|
||||
|
||||
public static bool CycleEqualsExponentHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||
{
|
||||
if (CycleEqualsExponentForMersenneCandidate(divisor, divisorData, exponent))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var small = Shared._smallCycles;
|
||||
if (small is not null)
|
||||
{
|
||||
ulong cached = small[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached == exponent;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthHeuristicHybrid(gpu, divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||
{
|
||||
return heuristicCycle == exponent;
|
||||
}
|
||||
|
||||
if (TryCalculateCycleLengthForExponentCpu(divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
if (TryCalculateCycleLengthForExponent(gpu, divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
#else
|
||||
if (TryCalculateCycleLengthForExponent(divisor, exponent, divisorData, out ulong cycleLength, out bool _) && cycleLength != 0UL)
|
||||
#endif
|
||||
{
|
||||
return cycleLength == exponent;
|
||||
}
|
||||
@@ -175,7 +113,7 @@ public class MersenneDivisorCycles
|
||||
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
MersenneDivisorCycles shared = Shared;
|
||||
MersenneDivisorCyclesTemplate shared = Shared;
|
||||
ulong[]? small = shared._smallCycles;
|
||||
if (small is not null)
|
||||
{
|
||||
@@ -239,7 +177,12 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetCycle(ulong divisor, in MontgomeryDivisorData divisorData)
|
||||
public readonly ulong GetCycle(
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
#endif
|
||||
ulong divisor,
|
||||
in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
// Fast-path: in-memory array for small divisors
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
@@ -284,106 +227,18 @@ public class MersenneDivisorCycles
|
||||
// TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
// configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot
|
||||
// so we respect the large-p memory limits and avoid extra cache locks.
|
||||
return CalculateCycleLengthCpu(divisor, divisorData);
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
return CalculateCycleLength(gpu, divisor, divisorData);
|
||||
#else
|
||||
return CalculateCycleLength(divisor, divisorData);
|
||||
#endif
|
||||
}
|
||||
|
||||
public ulong GetCycleGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
// Fast-path: in-memory array for small divisors
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var arr = _smallCycles;
|
||||
if (arr is not null)
|
||||
{
|
||||
ulong cached = arr[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search for divisor in sorted _table
|
||||
int mid, right, left = 0;
|
||||
ulong d;
|
||||
List<(ulong divisor, ulong cycleLength)> cycles = _table;
|
||||
|
||||
right = cycles.Count - 1;
|
||||
while (left <= right)
|
||||
{
|
||||
mid = left + ((right - left) >> 1);
|
||||
d = cycles[mid].divisor;
|
||||
|
||||
if (d == divisor)
|
||||
{
|
||||
return cycles[mid].cycleLength;
|
||||
}
|
||||
|
||||
if (d < divisor)
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
// configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot
|
||||
// so we respect the large-p memory limits and avoid extra cache locks.
|
||||
return CalculateCycleLengthGpu(gpu, divisor, divisorData);
|
||||
}
|
||||
|
||||
public ulong GetCycleHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData)
|
||||
{
|
||||
// Fast-path: in-memory array for small divisors
|
||||
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||
{
|
||||
var arr = _smallCycles;
|
||||
if (arr is not null)
|
||||
{
|
||||
ulong cached = arr[(int)divisor];
|
||||
if (cached != 0UL)
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Binary search for divisor in sorted _table
|
||||
int mid, right, left = 0;
|
||||
ulong d;
|
||||
List<(ulong divisor, ulong cycleLength)> cycles = _table;
|
||||
|
||||
right = cycles.Count - 1;
|
||||
while (left <= right)
|
||||
{
|
||||
mid = left + ((right - left) >> 1);
|
||||
d = cycles[mid].divisor;
|
||||
|
||||
if (d == divisor)
|
||||
{
|
||||
return cycles[mid].cycleLength;
|
||||
}
|
||||
|
||||
if (d < divisor)
|
||||
{
|
||||
left = mid + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
right = mid - 1;
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Route this miss to an ephemeral single-cycle computation on the device selected by the current
|
||||
// configuration (and skip persisting the result) when the divisor falls outside the in-memory snapshot
|
||||
// so we respect the large-p memory limits and avoid extra cache locks.
|
||||
return CalculateCycleLengthHybrid(gpu, divisor, divisorData);
|
||||
}
|
||||
|
||||
public static UInt128 GetCycleCpu(UInt128 divisor)
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
public static UInt128 GetCycle(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor)
|
||||
#else
|
||||
public static UInt128 GetCycle(UInt128 divisor)
|
||||
#endif
|
||||
{
|
||||
// For divisor = 2^k, cycle is 1
|
||||
if ((divisor & (divisor - UInt128.One)) == UInt128.Zero)
|
||||
@@ -395,106 +250,32 @@ public class MersenneDivisorCycles
|
||||
{
|
||||
ulong divisor64 = (ulong)divisor;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
|
||||
ulong cycle = CalculateCycleLengthCpu(divisor64, divisorData);
|
||||
return (UInt128)cycle;
|
||||
}
|
||||
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (wideOrder != UInt128.Zero)
|
||||
{
|
||||
return wideOrder;
|
||||
}
|
||||
|
||||
// Otherwise, find order of 2 mod divisor
|
||||
// TODO: Port this UInt128 path to the unrolled-hex cycle calculator so wide
|
||||
// divisors stop relying on the slow shift-and-subtract loop measured in the
|
||||
// GPU benchmarks.
|
||||
UInt128 order = UInt128.One;
|
||||
UInt128 pow = UInt128Numbers.Two;
|
||||
while (pow != UInt128.One)
|
||||
{
|
||||
pow <<= 1;
|
||||
if (pow >= divisor)
|
||||
{
|
||||
pow -= divisor;
|
||||
}
|
||||
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static UInt128 GetCycleHybrid(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor)
|
||||
{
|
||||
// For divisor = 2^k, cycle is 1
|
||||
if ((divisor & (divisor - UInt128.One)) == UInt128.Zero)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (divisor <= ulong.MaxValue)
|
||||
{
|
||||
ulong divisor64 = (ulong)divisor;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
|
||||
ulong cycle = CalculateCycleLengthHybrid(gpu, divisor64, divisorData);
|
||||
return (UInt128)cycle;
|
||||
}
|
||||
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (wideOrder != UInt128.Zero)
|
||||
{
|
||||
return wideOrder;
|
||||
}
|
||||
|
||||
// Otherwise, find order of 2 mod divisor
|
||||
// TODO: Port this UInt128 path to the unrolled-hex cycle calculator so wide
|
||||
// divisors stop relying on the slow shift-and-subtract loop measured in the
|
||||
// GPU benchmarks.
|
||||
UInt128 order = UInt128.One;
|
||||
UInt128 pow = UInt128Numbers.Two;
|
||||
while (pow != UInt128.One)
|
||||
{
|
||||
pow <<= 1;
|
||||
if (pow >= divisor)
|
||||
{
|
||||
pow -= divisor;
|
||||
}
|
||||
|
||||
order++;
|
||||
}
|
||||
|
||||
return order;
|
||||
}
|
||||
|
||||
public static UInt128 GetCycleGpu(PrimeOrderCalculatorAccelerator gpu, UInt128 divisor)
|
||||
{
|
||||
// For divisor = 2^k, cycle is 1
|
||||
if ((divisor & (divisor - UInt128.One)) == UInt128.Zero)
|
||||
{
|
||||
return UInt128.One;
|
||||
}
|
||||
|
||||
if (divisor <= ulong.MaxValue)
|
||||
{
|
||||
ulong divisor64 = (ulong)divisor;
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor64);
|
||||
ulong cycle = CalculateCycleLengthGpu(gpu, divisor64, divisorData);
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
ulong cycle = CalculateCycleLength(gpu, divisor64, divisorData);
|
||||
#else
|
||||
ulong cycle = CalculateCycleLength(divisor64, divisorData);
|
||||
#endif
|
||||
return (UInt128)cycle;
|
||||
}
|
||||
|
||||
#if DEVICE_GPU
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
#elif DEVICE_HYBRID
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
#else
|
||||
UInt128 wideOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
#endif
|
||||
if (wideOrder != UInt128.Zero)
|
||||
{
|
||||
return wideOrder;
|
||||
@@ -520,176 +301,10 @@ public class MersenneDivisorCycles
|
||||
return order;
|
||||
}
|
||||
|
||||
public static bool TryCalculateCycleLengthForExponentCpu(
|
||||
ulong divisor,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed)
|
||||
{
|
||||
cycleLength = 0UL;
|
||||
primeOrderFailed = false;
|
||||
|
||||
// EvenPerfectBitScanner never feeds powers of two into this helper, so leave the branch commented out to avoid redundant work.
|
||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
// {
|
||||
// cycleLength = 1UL;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// The scanner only calls this path with prime exponents greater than one and odd divisors above three, keeping the small-parameter guard unreachable.
|
||||
// if (divisor <= 3UL || exponent <= 1UL)
|
||||
// {
|
||||
// cycleLength = CalculateCycleLength(divisor, divisorData);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
|
||||
primeOrderFailed = true;
|
||||
|
||||
ulong phi = divisor - 1UL;
|
||||
// A zero totient would imply divisor == 0, which never occurs on the tested path. Keep the guard documented without
|
||||
// branching in the hot loop.
|
||||
// if (phi == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||
ulong reducedPhi = phi >> twoCount;
|
||||
if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong k = reducedPhi / exponent;
|
||||
// k == 0 would indicate reducedPhi < exponent, which the EvenPerfectBitScanner call flow prevents. Document the
|
||||
// assumption instead of branching.
|
||||
// if (k == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
Dictionary<ulong, int> factorCounts = ThreadStaticPools.RentFactorCountDictionary();
|
||||
bool success = false;
|
||||
|
||||
if (twoCount > 0)
|
||||
{
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
|
||||
bool exponentAccumulated = AccumulateFactors(exponent, factorCounts);
|
||||
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCounts);
|
||||
|
||||
if (factorsAccumulated)
|
||||
{
|
||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||
success = true;
|
||||
primeOrderFailed = false;
|
||||
}
|
||||
|
||||
factorCounts.Clear();
|
||||
ThreadStaticPools.ReturnFactorCountDictionary(factorCounts);
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool TryCalculateCycleLengthForExponentHybrid(
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
ulong divisor,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed)
|
||||
{
|
||||
cycleLength = 0UL;
|
||||
primeOrderFailed = false;
|
||||
|
||||
// EvenPerfectBitScanner never feeds powers of two into this helper, so leave the branch commented out to avoid redundant work.
|
||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
// {
|
||||
// cycleLength = 1UL;
|
||||
// return true;
|
||||
// }
|
||||
|
||||
// The scanner only calls this path with prime exponents greater than one and odd divisors above three, keeping the small-parameter guard unreachable.
|
||||
// if (divisor <= 3UL || exponent <= 1UL)
|
||||
// {
|
||||
// cycleLength = CalculateCycleLength(divisor, divisorData);
|
||||
// return true;
|
||||
// }
|
||||
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
|
||||
primeOrderFailed = true;
|
||||
|
||||
ulong phi = divisor - 1UL;
|
||||
// A zero totient would imply divisor == 0, which never occurs on the tested path. Keep the guard documented without
|
||||
// branching in the hot loop.
|
||||
// if (phi == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||
ulong reducedPhi = phi >> twoCount;
|
||||
if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
ulong k = reducedPhi / exponent;
|
||||
// k == 0 would indicate reducedPhi < exponent, which the EvenPerfectBitScanner call flow prevents. Document the
|
||||
// assumption instead of branching.
|
||||
// if (k == 0UL)
|
||||
// {
|
||||
// return false;
|
||||
// }
|
||||
|
||||
Dictionary<ulong, int> factorCounts = ThreadStaticPools.RentFactorCountDictionary();
|
||||
bool success = false;
|
||||
|
||||
if (twoCount > 0)
|
||||
{
|
||||
factorCounts[2UL] = twoCount;
|
||||
}
|
||||
|
||||
bool exponentAccumulated = AccumulateFactors(exponent, factorCounts);
|
||||
bool factorsAccumulated = exponentAccumulated && AccumulateFactors(k, factorCounts);
|
||||
|
||||
if (factorsAccumulated)
|
||||
{
|
||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||
success = true;
|
||||
primeOrderFailed = false;
|
||||
}
|
||||
|
||||
factorCounts.Clear();
|
||||
ThreadStaticPools.ReturnFactorCountDictionary(factorCounts);
|
||||
return success;
|
||||
}
|
||||
|
||||
public static bool TryCalculateCycleLengthForExponentGpu(
|
||||
public static bool TryCalculateCycleLengthForExponent(
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
#endif
|
||||
ulong divisor,
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
@@ -713,12 +328,27 @@ public class MersenneDivisorCycles
|
||||
// return true;
|
||||
// }
|
||||
|
||||
#if DEVICE_GPU
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateGpu(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
#elif DEVICE_HYBRID
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
#else
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
#endif
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
@@ -773,6 +403,8 @@ public class MersenneDivisorCycles
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private static bool AccumulateFactors(
|
||||
ulong value,
|
||||
Dictionary<ulong, int> counts)
|
||||
@@ -805,6 +437,7 @@ public class MersenneDivisorCycles
|
||||
// private static ulong _tryFactorIntoCountsInternalHits;
|
||||
// private static ulong _tryCalculateCycleLengthHeuristicHits;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
||||
{
|
||||
// The EvenPerfectBitScanner only requests factors for values >= 2 here. Leave the guard documented without executing it.
|
||||
@@ -873,18 +506,21 @@ public class MersenneDivisorCycles
|
||||
return true;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void AddFactor(Dictionary<ulong, int> counts, ulong factor)
|
||||
{
|
||||
ref int entry = ref CollectionsMarshal.GetValueRefOrAddDefault(counts, factor, out bool exists);
|
||||
entry = exists ? entry + 1 : 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static void AddFactor(Dictionary<ulong, int> counts, ulong factor, int multiplicity)
|
||||
{
|
||||
ref int entry = ref CollectionsMarshal.GetValueRefOrAddDefault(counts, factor, out bool exists);
|
||||
entry = exists ? entry + multiplicity : multiplicity;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
|
||||
{
|
||||
if (factorCounts.Count == 0)
|
||||
@@ -1007,6 +643,7 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private static ulong PollardRho64(ulong n)
|
||||
{
|
||||
// The EvenPerfectBitScanner path removes all factors of two before invoking Pollard-Rho, so this branch never executes
|
||||
@@ -1045,13 +682,14 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static ulong AdvancePolynomial(ulong x, ulong c, ulong modulus)
|
||||
{
|
||||
UInt128 value = (UInt128)x * x + c;
|
||||
return (ulong)(value % modulus);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
|
||||
private static ulong BinaryGcd(ulong a, ulong b)
|
||||
{
|
||||
if (a == 0UL)
|
||||
@@ -1083,6 +721,7 @@ public class MersenneDivisorCycles
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static ulong NextRandomUInt64()
|
||||
{
|
||||
Span<byte> buffer = stackalloc byte[8];
|
||||
@@ -1090,30 +729,6 @@ public class MersenneDivisorCycles
|
||||
return BinaryPrimitives.ReadUInt64LittleEndian(buffer);
|
||||
}
|
||||
|
||||
private sealed class FactorPairValueComparer : IComparer<KeyValuePair<ulong, int>>
|
||||
{
|
||||
public static readonly FactorPairValueComparer Instance = new FactorPairValueComparer();
|
||||
|
||||
private FactorPairValueComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public int Compare(KeyValuePair<ulong, int> x, KeyValuePair<ulong, int> y)
|
||||
{
|
||||
if (x.Key < y.Key)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (x.Key > y.Key)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static (long nextPosition, long completeCount) FindLast(string path)
|
||||
{
|
||||
using Stream outputStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, BufferSize10M, useAsync: false);
|
||||
@@ -1122,6 +737,7 @@ public class MersenneDivisorCycles
|
||||
{
|
||||
count++;
|
||||
}
|
||||
|
||||
return (outputStream.Position, count);
|
||||
}
|
||||
|
||||
@@ -1182,7 +798,11 @@ public class MersenneDivisorCycles
|
||||
// unrolled-hex calculator so the CPU generator matches
|
||||
// the GPU benchmark leader for large divisors.
|
||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||
ulong cycle = CalculateCycleLengthGpu(gpu, divisor, divisorData);
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
ulong cycle = CalculateCycleLength(gpu, divisor, divisorData);
|
||||
#else
|
||||
ulong cycle = CalculateCycleLength(divisor, divisorData);
|
||||
#endif
|
||||
localCycles[localCycleIndex++] = (divisor, cycle);
|
||||
}
|
||||
|
||||
@@ -1347,98 +967,36 @@ public class MersenneDivisorCycles
|
||||
return loaded;
|
||||
}
|
||||
|
||||
public static ulong CalculateCycleLengthGpu(ulong divisor) => DivisorCycleKernels.CalculateCycleLengthGpu(divisor);
|
||||
#if DEVICE_GPU
|
||||
public static ulong CalculateCycleLength(ulong divisor) => DivisorCycleKernels.CalculateCycleLengthGpu(divisor);
|
||||
#endif
|
||||
|
||||
public static ulong CalculateCycleLengthCpu(ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
public static ulong CalculateCycleLength(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
#else
|
||||
public static ulong CalculateCycleLength(ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
#endif
|
||||
{
|
||||
if (TryCalculateCycleLengthHeuristicCpu(divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
|
||||
if (TryCalculateCycleLengthHeuristic(
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
gpu,
|
||||
#endif
|
||||
divisor,
|
||||
divisorData,
|
||||
out ulong cycleLength,
|
||||
skipPrimeOrderHeuristic))
|
||||
{
|
||||
return cycleLength;
|
||||
}
|
||||
|
||||
return CalculateCycleLengthFallback(divisor);
|
||||
}
|
||||
|
||||
public static ulong CalculateCycleLengthGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (TryCalculateCycleLengthHeuristicGpu(gpu, divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
|
||||
return cycleLength;
|
||||
|
||||
return CalculateCycleLengthFallback(divisor);
|
||||
}
|
||||
|
||||
public static ulong CalculateCycleLengthHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if (TryCalculateCycleLengthHeuristicHybrid(gpu, divisor, divisorData, out ulong cycleLength, skipPrimeOrderHeuristic))
|
||||
return cycleLength;
|
||||
|
||||
return CalculateCycleLengthFallback(divisor);
|
||||
}
|
||||
|
||||
internal static bool TryCalculateCycleLengthHeuristicCpu(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
{
|
||||
cycleLength = 1UL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisor <= 3UL)
|
||||
{
|
||||
cycleLength = CalculateCycleLengthFallback(divisor);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeCpu(divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
cycleLength = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryCalculateCycleLengthHeuristicHybrid(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
{
|
||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
{
|
||||
cycleLength = 1UL;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (divisor <= 3UL)
|
||||
{
|
||||
cycleLength = CalculateCycleLengthFallback(divisor);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeCpu(divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
cycleLength = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
internal static bool TryCalculateCycleLengthHeuristicGpu(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
#if DEVICE_GPU || DEVICE_HYBRID
|
||||
internal static bool TryCalculateCycleLengthHeuristic(PrimeOrderCalculatorAccelerator gpu, ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
#else
|
||||
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength, bool skipPrimeOrderHeuristic = false)
|
||||
#endif
|
||||
{
|
||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||
{
|
||||
@@ -1452,6 +1010,7 @@ public class MersenneDivisorCycles
|
||||
return true;
|
||||
}
|
||||
|
||||
#if DEVICE_GPU
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeGpu(gpu, divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateGpu(
|
||||
@@ -1466,16 +1025,45 @@ public class MersenneDivisorCycles
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#elif DEVICE_HYBRID
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeCpu(divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateHybrid(
|
||||
gpu,
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (!skipPrimeOrderHeuristic && HeuristicPrimeTester.IsPrimeCpu(divisor))
|
||||
{
|
||||
ulong computedOrder = PrimeOrderCalculator.CalculateCpu(
|
||||
divisor,
|
||||
previousOrder: null,
|
||||
divisorData,
|
||||
PrimeOrderCalculator.PrimeOrderCalculatorConfig.HeuristicDefault);
|
||||
if (computedOrder != 0UL)
|
||||
{
|
||||
cycleLength = computedOrder;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
cycleLength = 0UL;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static ulong CalculateCycleLengthFallback(ulong divisor)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
|
||||
private static ulong CalculateCycleLengthFallback(ulong divisor) =>
|
||||
// Otherwise, find order of 2 mod divisor using the shared unrolled-hex stepping sequence.
|
||||
return divisor.CalculateMersenneDivisorCycleLengthUnrolledHexCpu();
|
||||
}
|
||||
divisor.CalculateMersenneDivisorCycleLengthUnrolledHexCpu();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void EnsureSmallBuffer()
|
||||
@@ -270,7 +270,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
|
||||
out bool primeOrderFailed) => MersenneDivisorCyclesCpu.TryCalculateCycleLengthForExponent(
|
||||
divisor,
|
||||
exponent,
|
||||
divisorData,
|
||||
@@ -284,7 +284,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentHybrid(
|
||||
out bool primeOrderFailed) => MersenneDivisorCyclesHybrid.TryCalculateCycleLengthForExponent(
|
||||
gpu,
|
||||
divisor,
|
||||
exponent,
|
||||
@@ -299,7 +299,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
ulong exponent,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
out ulong cycleLength,
|
||||
out bool primeOrderFailed) => MersenneDivisorCycles.TryCalculateCycleLengthForExponentGpu(
|
||||
out bool primeOrderFailed) => MersenneDivisorCyclesGpu.TryCalculateCycleLengthForExponent(
|
||||
gpu,
|
||||
divisor,
|
||||
exponent,
|
||||
@@ -311,7 +311,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
public static ulong CalculateCycleLengthCpu(
|
||||
ulong divisor,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthCpu(
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCyclesCpu.CalculateCycleLength(
|
||||
divisor,
|
||||
divisorData,
|
||||
skipPrimeOrderHeuristic);
|
||||
@@ -321,7 +321,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthHybrid(
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCyclesHybrid.CalculateCycleLength(
|
||||
gpu,
|
||||
divisor,
|
||||
divisorData,
|
||||
@@ -332,7 +332,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
ulong divisor,
|
||||
PrimeOrderCalculatorAccelerator gpu,
|
||||
in MontgomeryDivisorData divisorData,
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCycles.CalculateCycleLengthGpu(
|
||||
bool skipPrimeOrderHeuristic) => MersenneDivisorCyclesGpu.CalculateCycleLength(
|
||||
gpu,
|
||||
divisor,
|
||||
divisorData,
|
||||
@@ -1232,3 +1232,4 @@ public static class MersenneNumberDivisorByDivisorTester
|
||||
startGate.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -583,7 +583,7 @@ public static class UInt128Extensions
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 cycle = MersenneDivisorCycles.GetCycleCpu(q);
|
||||
UInt128 cycle = MersenneDivisorCyclesCpu.GetCycle(q);
|
||||
|
||||
// TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
// (GPU by default) to compute it on the fly and skip inserting it into the cache so
|
||||
@@ -634,7 +634,7 @@ public static class UInt128Extensions
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 cycle = MersenneDivisorCycles.GetCycleGpu(gpu, q);
|
||||
UInt128 cycle = MersenneDivisorCyclesGpu.GetCycle(gpu, q);
|
||||
// TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
// (GPU by default) to compute it on the fly and skip inserting it into the cache so
|
||||
// wide-order factoring can still leverage cycle stepping without breaching the
|
||||
@@ -684,7 +684,7 @@ public static class UInt128Extensions
|
||||
ulong[] smallPrimesPow2 = PrimesGenerator.SmallPrimesPow2;
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 cycle = MersenneDivisorCycles.GetCycleHybrid(gpu, q);
|
||||
UInt128 cycle = MersenneDivisorCyclesHybrid.GetCycle(gpu, q);
|
||||
// TODO: If the cache lacks this cycle, immediately schedule the configured device
|
||||
// (GPU by default) to compute it on the fly and skip inserting it into the cache so
|
||||
// wide-order factoring can still leverage cycle stepping without breaching the
|
||||
@@ -1013,3 +1013,4 @@ public static class UInt128Extensions
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ public static partial class ULongExtensions
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 q128 = q,
|
||||
cycle = MersenneDivisorCycles.GetCycleCpu(q128);
|
||||
cycle = MersenneDivisorCyclesCpu.GetCycle(q128);
|
||||
// TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
// GPU computation (respecting the configured device) without promoting the result into
|
||||
// the cache so the order calculator still benefits from cycle stepping while keeping the
|
||||
@@ -158,7 +158,7 @@ public static partial class ULongExtensions
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 q128 = q,
|
||||
cycle = MersenneDivisorCycles.GetCycleGpu(gpu, q128);
|
||||
cycle = MersenneDivisorCyclesGpu.GetCycle(gpu, q128);
|
||||
// TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
// GPU computation (respecting the configured device) without promoting the result into
|
||||
// the cache so the order calculator still benefits from cycle stepping while keeping the
|
||||
@@ -205,7 +205,7 @@ public static partial class ULongExtensions
|
||||
|
||||
int i = 0, primesLength = smallPrimes.Length;
|
||||
UInt128 q128 = q,
|
||||
cycle = MersenneDivisorCycles.GetCycleHybrid(gpu, q128);
|
||||
cycle = MersenneDivisorCyclesHybrid.GetCycle(gpu, q128);
|
||||
// TODO: When the shared cycle snapshot cannot serve this divisor, trigger an on-demand
|
||||
// GPU computation (respecting the configured device) without promoting the result into
|
||||
// the cache so the order calculator still benefits from cycle stepping while keeping the
|
||||
@@ -986,7 +986,7 @@ public static partial class ULongExtensions
|
||||
|
||||
for (; modulusCandidate <= modulus; modulusCandidate++)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.GetCycleCpu(modulusCandidate);
|
||||
cycle = MersenneDivisorCyclesCpu.GetCycle(modulusCandidate);
|
||||
remainderForCandidate = cycle > zero
|
||||
? PowModWithCycle(exponent, modulusCandidate, cycle)
|
||||
: PowMod(exponent, modulusCandidate);
|
||||
@@ -1030,7 +1030,7 @@ public static partial class ULongExtensions
|
||||
|
||||
for (; modulusCandidate <= modulus; modulusCandidate++)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.GetCycleGpu(gpu, modulusCandidate);
|
||||
cycle = MersenneDivisorCyclesGpu.GetCycle(gpu, modulusCandidate);
|
||||
remainderForCandidate = cycle > zero
|
||||
? PowModWithCycle(exponent, modulusCandidate, cycle)
|
||||
: PowMod(exponent, modulusCandidate);
|
||||
@@ -1074,7 +1074,7 @@ public static partial class ULongExtensions
|
||||
|
||||
for (; modulusCandidate <= modulus; modulusCandidate++)
|
||||
{
|
||||
cycle = MersenneDivisorCycles.GetCycleHybrid(gpu, modulusCandidate);
|
||||
cycle = MersenneDivisorCyclesHybrid.GetCycle(gpu, modulusCandidate);
|
||||
remainderForCandidate = cycle > zero
|
||||
? PowModWithCycle(exponent, modulusCandidate, cycle)
|
||||
: PowMod(exponent, modulusCandidate);
|
||||
@@ -1310,3 +1310,4 @@ public static partial class ULongExtensions
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user