Compare commits
64
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b07bf769c9 | ||
|
|
7ef0906a9b | ||
|
|
f9d51efe94 | ||
|
|
b2b71f705a | ||
|
|
9e06a21718 | ||
|
|
483584b5d4 | ||
|
|
ea29035c55 | ||
|
|
cd47b122a5 | ||
|
|
cfedfd12c8 | ||
|
|
41e6703926 | ||
|
|
0902b18721 | ||
|
|
67dcf3ac7b | ||
|
|
681e9a4a5e | ||
|
|
77ef93eb91 | ||
|
|
fcfb6f6434 | ||
|
|
8c0320d837 | ||
|
|
0ff93e1d09 | ||
|
|
6c4fa7fcf6 | ||
|
|
9e5ee36e6e | ||
|
|
e5486af49d | ||
|
|
67d9f4be55 | ||
|
|
d555e157f6 | ||
|
|
7562f14b1c | ||
|
|
cc50e7376f | ||
|
|
808bf64fab | ||
|
|
86989d0502 | ||
|
|
e5dc688865 | ||
|
|
c64e672552 | ||
|
|
a1848e4f0b | ||
|
|
be4ed3db34 | ||
|
|
dde01cf8b9 | ||
|
|
4838abc6c0 | ||
|
|
9c378db18f | ||
|
|
4f73d89716 | ||
|
|
421cb8ae7d | ||
|
|
17f1ea73a7 | ||
|
|
60c8dbf6d6 | ||
|
|
9d62278bb3 | ||
|
|
2da549fe8c | ||
|
|
b33158390f | ||
|
|
6a1925dd47 | ||
|
|
b47e758235 | ||
|
|
2801832af9 | ||
|
|
ec265bde8b | ||
|
|
ad22329814 | ||
|
|
8e9390bc5e | ||
|
|
cd69c08fb4 | ||
|
|
fca650b285 | ||
|
|
c4858d2829 | ||
|
|
d476748216 | ||
|
|
3e2d2b05b1 | ||
|
|
3c926e4909 | ||
|
|
e1be80048f | ||
|
|
99c98cd017 | ||
|
|
8e7df60dbf | ||
|
|
3c1519874c | ||
|
|
8fb7f937c0 | ||
|
|
fa74667f89 | ||
|
|
45183acd9b | ||
|
|
4f5ae7b674 | ||
|
|
12140bca0a | ||
|
|
1cbf49c8b5 | ||
|
|
74fb7f1535 | ||
|
|
9181959390 |
No files matched your search
@@ -15,4 +15,7 @@
|
|||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove=".temp.cs" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
|
|
||||||
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
|
[ShortRunJob(RuntimeMoniker.Net80)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MutableUInt128AddBenchmarks
|
||||||
|
{
|
||||||
|
private static readonly BenchmarkInput[] Inputs =
|
||||||
|
[
|
||||||
|
new(Create(0UL, 0x0000_0000_0000_0007UL), Create(0, 0x3UL), "TinyOperands"),
|
||||||
|
new(Create(0x1234_5678_9ABC_DEF0UL, 0x0FED_CBA9_8765_4321UL), Create(0xFFFF_FFFF, 0x0000_0001UL), "MixedOperands"),
|
||||||
|
new(Create(0xFFFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFFFUL), Create(0xFFFF_FFFF, 0xFFFF_FFFBUL), "NearMaxOperands"),
|
||||||
|
];
|
||||||
|
|
||||||
|
[ParamsSource(nameof(GetInputs))]
|
||||||
|
public BenchmarkInput Input { get; set; }
|
||||||
|
|
||||||
|
public static IEnumerable<BenchmarkInput> GetInputs() => Inputs;
|
||||||
|
|
||||||
|
private static MutableUInt128 Create(ulong high, ulong low) => new(high, low);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void StandardInternal(ref MutableUInt128 source, MutableUInt128 value)
|
||||||
|
{
|
||||||
|
ulong temp = source.Low + value.Low;
|
||||||
|
source.Low = temp;
|
||||||
|
temp = temp < source.Low ? 1UL : 0UL;
|
||||||
|
temp += source.High;
|
||||||
|
temp += value.High;
|
||||||
|
source.High = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline MultiplyAdd implementation that chains Multiply and Add; measured 3.370 ns with mixed operands,
|
||||||
|
/// 3.361 ns when both limbs and inputs are near their maxima, and 3.368 ns with tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 3.370 ns, NearMaxOperands 3.361 ns, TinyOperands 3.368 ns.
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public MutableUInt128 Standard()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = Input.InitialValue;
|
||||||
|
StandardInternal(ref value, Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fully inlined MultiplyAdd implementation that kept all arithmetic inside the method; landed at 3.175 ns on mixed operands,
|
||||||
|
/// 3.177 ns on near-max inputs, and 3.178 ns on tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 3.175 ns, NearMaxOperands 3.177 ns, TinyOperands 3.178 ns.
|
||||||
|
/// Status: Implemented
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark]
|
||||||
|
public MutableUInt128 Optimized()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = Input.InitialValue;
|
||||||
|
value.Add(Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct BenchmarkInput(MutableUInt128 InitialValue, MutableUInt128 Addend, string Name)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
|
|
||||||
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
|
[ShortRunJob(RuntimeMoniker.Net80)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MutableUInt128Mul64Benchmarks
|
||||||
|
{
|
||||||
|
private static readonly BenchmarkInput[] Inputs =
|
||||||
|
[
|
||||||
|
new(Create(0UL, 0x0000_0000_0000_0007UL), 0x3UL, "TinyOperands"),
|
||||||
|
new(Create(0x1234_5678_9ABC_DEF0UL, 0x0FED_CBA9_8765_4321UL), 0xFFFF_FFFF_0000_0001UL, "MixedOperands"),
|
||||||
|
new(Create(0xFFFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFFFUL), 0xFFFF_FFFF_FFFF_FFFBUL, "NearMaxOperands"),
|
||||||
|
];
|
||||||
|
|
||||||
|
[ParamsSource(nameof(GetInputs))]
|
||||||
|
public BenchmarkInput Input { get; set; }
|
||||||
|
|
||||||
|
public static IEnumerable<BenchmarkInput> GetInputs() => Inputs;
|
||||||
|
|
||||||
|
private static MutableUInt128 Create(ulong high, ulong low) => new(high, low);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static ulong Mul64(ulong left, ulong right, out ulong low)
|
||||||
|
{
|
||||||
|
const ulong Mask32 = 0xFFFF_FFFFUL;
|
||||||
|
|
||||||
|
ulong leftLow = left & Mask32;
|
||||||
|
ulong leftHigh = left >> 32;
|
||||||
|
ulong rightLow = right & Mask32;
|
||||||
|
ulong rightHigh = right >> 32;
|
||||||
|
|
||||||
|
ulong lowLow = leftLow * rightLow;
|
||||||
|
ulong lowHigh = leftLow * rightHigh;
|
||||||
|
ulong highLow = leftHigh * rightLow;
|
||||||
|
ulong highHigh = leftHigh * rightHigh;
|
||||||
|
|
||||||
|
ulong carry = (lowLow >> 32) + (lowHigh & Mask32) + (highLow & Mask32);
|
||||||
|
ulong high = highHigh + (lowHigh >> 32) + (highLow >> 32) + (carry >> 32);
|
||||||
|
|
||||||
|
low = ((carry & Mask32) << 32) | (lowLow & Mask32);
|
||||||
|
return high;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void StandardInternal(ref MutableUInt128 source, ulong value)
|
||||||
|
{
|
||||||
|
ulong currentHigh = source.High;
|
||||||
|
ulong currentLow = source.Low;
|
||||||
|
|
||||||
|
ulong carry = Mul64(currentLow, value, out ulong newLow);
|
||||||
|
Mul64(currentHigh, value, out ulong shiftedHigh);
|
||||||
|
ulong newHigh = shiftedHigh + carry;
|
||||||
|
|
||||||
|
source.Low = newLow;
|
||||||
|
source.High = newHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline MultiplyAdd implementation that chains Multiply and Add; measured 3.113 ns with mixed operands,
|
||||||
|
/// 3.128 ns when both limbs and inputs are near their maxima, and 3.131 ns with tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 3.113 ns, NearMaxOperands 3.128 ns, TinyOperands 3.131 ns.
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public MutableUInt128 Standard()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = Input.InitialValue;
|
||||||
|
StandardInternal(ref value, Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fully inlined MultiplyAdd implementation that kept all arithmetic inside the method; landed at 2.969 ns on mixed operands,
|
||||||
|
/// 2.912 ns on near-max inputs, and 2.935 ns on tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 2.969 ns, NearMaxOperands 2.912 ns, TinyOperands 2.935 ns.
|
||||||
|
/// Status: Implemented
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark]
|
||||||
|
public MutableUInt128 Optimized()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = Input.InitialValue;
|
||||||
|
value.Mul(Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct BenchmarkInput(MutableUInt128 InitialValue, ulong Addend, string Name)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
|
|
||||||
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
|
[ShortRunJob(RuntimeMoniker.Net80)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MutableUInt128MultiplyAddBenchmarks
|
||||||
|
{
|
||||||
|
private static readonly MultiplyAddInput[] Inputs =
|
||||||
|
[
|
||||||
|
new(Create(0UL, 0x0000_0000_0000_0007UL), 0x3UL, 0x5UL, "TinyOperands"),
|
||||||
|
new(Create(0x1234_5678_9ABC_DEF0UL, 0x0FED_CBA9_8765_4321UL), 0xFFFF_FFFF_0000_0001UL, 0x1357_9BDF_2468_ACEEUL, "MixedOperands"),
|
||||||
|
new(Create(0xFFFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFFFUL), 0xFFFF_FFFF_FFFF_FFFBUL, 0xFFFF_FFFF_FFFF_FFF7UL, "NearMaxOperands"),
|
||||||
|
];
|
||||||
|
|
||||||
|
[ParamsSource(nameof(GetInputs))]
|
||||||
|
public MultiplyAddInput Input { get; set; }
|
||||||
|
|
||||||
|
public static IEnumerable<MultiplyAddInput> GetInputs() => Inputs;
|
||||||
|
|
||||||
|
private static UInt128 Create(ulong high, ulong low)
|
||||||
|
{
|
||||||
|
return ((UInt128)high << 64) | low;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void MultiplyAdd(ref MutableUInt128 source, ulong multiplier, ulong addend)
|
||||||
|
{
|
||||||
|
source.Mul(multiplier);
|
||||||
|
source.Add(addend);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline MultiplyAdd implementation that chains Multiply and Add; measured 3.370 ns with mixed operands,
|
||||||
|
/// 3.361 ns when both limbs and inputs are near their maxima, and 3.368 ns with tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 3.370 ns, NearMaxOperands 3.361 ns, TinyOperands 3.368 ns.
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public MutableUInt128 MultiplyAddChained()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = new(Input.InitialValue);
|
||||||
|
MultiplyAdd(ref value, Input.Multiplier, Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fully inlined MultiplyAdd implementation that kept all arithmetic inside the method; landed at 3.175 ns on mixed operands,
|
||||||
|
/// 3.177 ns on near-max inputs, and 3.178 ns on tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 3.175 ns, NearMaxOperands 3.177 ns, TinyOperands 3.178 ns.
|
||||||
|
/// Status: Implemented
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark]
|
||||||
|
public MutableUInt128 MultiplyAddFullyInlined()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = new(Input.InitialValue);
|
||||||
|
value.MulAdd(Input.Multiplier, Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct MultiplyAddInput(UInt128 InitialValue, ulong Multiplier, ulong Addend, string Name)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using BenchmarkDotNet.Attributes;
|
||||||
|
using BenchmarkDotNet.Jobs;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
|
|
||||||
|
namespace EvenPerfectBitScanner.Benchmarks;
|
||||||
|
|
||||||
|
[ShortRunJob(RuntimeMoniker.Net80)]
|
||||||
|
[MemoryDiagnoser]
|
||||||
|
public class MutableUInt128SubMutableBenchmarks
|
||||||
|
{
|
||||||
|
private static readonly BenchmarkInput[] Inputs =
|
||||||
|
[
|
||||||
|
new(Create(0UL, 0x0000_0000_0000_0007UL), Create(0, 0x3UL), "TinyOperands"),
|
||||||
|
new(Create(0x1234_5678_9ABC_DEF0UL, 0x0FED_CBA9_8765_4321UL), Create(0xFFFF_FFFF, 0x0000_0001UL), "MixedOperands"),
|
||||||
|
new(Create(0xFFFF_FFFF_FFFF_FFFFUL, 0xFFFF_FFFF_FFFF_FFFFUL), Create(0xFFFF_FFFF, 0xFFFF_FFFBUL), "NearMaxOperands"),
|
||||||
|
];
|
||||||
|
|
||||||
|
[ParamsSource(nameof(GetInputs))]
|
||||||
|
public BenchmarkInput Input { get; set; }
|
||||||
|
|
||||||
|
public static IEnumerable<BenchmarkInput> GetInputs() => Inputs;
|
||||||
|
|
||||||
|
private static MutableUInt128 Create(ulong high, ulong low) => new(high, low);
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void StandardInternal(ref MutableUInt128 source, MutableUInt128 value)
|
||||||
|
{
|
||||||
|
ulong low = source.Low;
|
||||||
|
ulong result = low - value.Low;
|
||||||
|
ulong borrow = low < value.Low ? 1UL : 0UL;
|
||||||
|
|
||||||
|
source.Low = result;
|
||||||
|
source.High = source.High - value.High - borrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Baseline MultiplyAdd implementation that chains Multiply and Add; measured 3.370 ns with mixed operands,
|
||||||
|
/// 3.361 ns when both limbs and inputs are near their maxima, and 3.368 ns with tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 3.370 ns, NearMaxOperands 3.361 ns, TinyOperands 3.368 ns.
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark(Baseline = true)]
|
||||||
|
public MutableUInt128 Standard()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = Input.InitialValue;
|
||||||
|
StandardInternal(ref value, Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Fully inlined MultiplyAdd implementation that kept all arithmetic inside the method; landed at 3.175 ns on mixed operands,
|
||||||
|
/// 3.177 ns on near-max inputs, and 3.178 ns on tiny operands.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: MixedOperands 3.175 ns, NearMaxOperands 3.177 ns, TinyOperands 3.178 ns.
|
||||||
|
/// Status: Implemented
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark]
|
||||||
|
public MutableUInt128 Optimized()
|
||||||
|
{
|
||||||
|
MutableUInt128 value = Input.InitialValue;
|
||||||
|
value.Sub(Input.Addend);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly record struct BenchmarkInput(MutableUInt128 InitialValue, MutableUInt128 Addend, string Name)
|
||||||
|
{
|
||||||
|
public override string ToString()
|
||||||
|
{
|
||||||
|
return Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,16 +8,22 @@ public static class Program
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
if (args.Length > 0)
|
||||||
// BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
{
|
||||||
|
IConfig config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
||||||
|
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
|
||||||
{
|
|
||||||
Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large
|
|
||||||
};
|
|
||||||
|
|
||||||
GpuPrimeWorkLimiter.SetLimit(1);
|
// Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
||||||
benchmarks.Setup();
|
// {
|
||||||
|
// Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large
|
||||||
|
// };
|
||||||
|
|
||||||
|
// GpuPrimeWorkLimiter.SetLimit(1);
|
||||||
|
// benchmarks.Setup();
|
||||||
|
|
||||||
// Console.WriteLine("Press ENTER to run");
|
// Console.WriteLine("Press ENTER to run");
|
||||||
// Console.ReadLine();
|
// Console.ReadLine();
|
||||||
|
|||||||
@@ -11,4 +11,7 @@
|
|||||||
<PackageReference Include="Open.Numeric.Primes" Version="4.0.4" />
|
<PackageReference Include="Open.Numeric.Primes" Version="4.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove=".temp.cs" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -22,4 +22,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../EvenPerfectBitScanner/EvenPerfectBitScanner.csproj" />
|
<ProjectReference Include="../EvenPerfectBitScanner/EvenPerfectBitScanner.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove=".temp.cs" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -16,4 +16,7 @@
|
|||||||
<PackageReference Include="Open.Numeric" Version="3.0.1" />
|
<PackageReference Include="Open.Numeric" Version="3.0.1" />
|
||||||
<PackageReference Include="Open.Numeric.Primes" Version="4.0.4" />
|
<PackageReference Include="Open.Numeric.Primes" Version="4.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove=".temp.cs" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -44,6 +44,7 @@ internal static class Program
|
|||||||
private static string? _resultsPrefix;
|
private static string? _resultsPrefix;
|
||||||
private static readonly Optimized PrimeIterator = new();
|
private static readonly Optimized PrimeIterator = new();
|
||||||
private static ulong _byDivisorStartPrime;
|
private static ulong _byDivisorStartPrime;
|
||||||
|
private static TimeSpan? _primeTestLimit;
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static bool _lastCompositeP;
|
private static bool _lastCompositeP;
|
||||||
@@ -74,6 +75,8 @@ internal static class Program
|
|||||||
bool useResidue = false; // M_p test via residue divisors (replaces LL/incremental)
|
bool useResidue = false; // M_p test via residue divisors (replaces LL/incremental)
|
||||||
bool useDivisor = false; // M_p divisibility by specific divisor
|
bool useDivisor = false; // M_p divisibility by specific divisor
|
||||||
bool useByDivisor = false; // Iterative divisor scan across primes UInt128 divisor = UInt128.Zero;
|
bool useByDivisor = false; // Iterative divisor scan across primes UInt128 divisor = UInt128.Zero;
|
||||||
|
ByDivisorDeltasDevice byDivisorDeltasDevice = ByDivisorDeltasDevice.Cpu;
|
||||||
|
ByDivisorMontgomeryDevice byDivisorMontgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
||||||
// Device routing
|
// Device routing
|
||||||
bool useGpuCycles = true;
|
bool useGpuCycles = true;
|
||||||
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
|
bool mersenneOnGpu = true; // controls Lucas/incremental/pow2mod device
|
||||||
@@ -181,6 +184,51 @@ internal static class Program
|
|||||||
{
|
{
|
||||||
mersenneOnGpu = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
mersenneOnGpu = !arg.AsSpan(arg.IndexOf('=') + 1).Equals("cpu", StringComparison.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
else if (arg.StartsWith("--bydivisor-deltas-device=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> deviceValue = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||||
|
if (deviceValue.Equals("gpu", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
byDivisorDeltasDevice = ByDivisorDeltasDevice.Gpu;
|
||||||
|
}
|
||||||
|
else if (deviceValue.Equals("cpu", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
byDivisorDeltasDevice = ByDivisorDeltasDevice.Cpu;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Invalid --bydivisor-deltas-device value. Expected cpu or gpu.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg.StartsWith("--bydivisor-montgomery-device=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> deviceValue = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||||
|
if (deviceValue.Equals("gpu", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
byDivisorMontgomeryDevice = ByDivisorMontgomeryDevice.Gpu;
|
||||||
|
}
|
||||||
|
else if (deviceValue.Equals("cpu", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
byDivisorMontgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("Invalid --bydivisor-montgomery-device value. Expected cpu or gpu.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (arg.StartsWith("--prime-test-limit=", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
ReadOnlySpan<char> value = arg.AsSpan(arg.IndexOf('=') + 1);
|
||||||
|
if (!TryParsePrimeTestLimit(value, out TimeSpan parsedLimitValue))
|
||||||
|
{
|
||||||
|
Console.WriteLine("Failed to parse --prime-test-limit value.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_primeTestLimit = parsedLimitValue;
|
||||||
|
}
|
||||||
else if (arg.Equals("--workaround-mod", StringComparison.OrdinalIgnoreCase))
|
else if (arg.Equals("--workaround-mod", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
useModuloWorkaround = true;
|
useModuloWorkaround = true;
|
||||||
@@ -377,6 +425,8 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GpuKernelPool.CpuCycleUsesGpu = orderOnGpu;
|
||||||
|
|
||||||
if (useByDivisor)
|
if (useByDivisor)
|
||||||
{
|
{
|
||||||
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
|
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
|
||||||
@@ -517,6 +567,11 @@ internal static class Program
|
|||||||
? new MersenneNumberDivisorByDivisorGpuTester()
|
? new MersenneNumberDivisorByDivisorGpuTester()
|
||||||
: new MersenneNumberDivisorByDivisorCpuTester();
|
: new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
_byDivisorTester.BatchSize = scanBatchSize;
|
_byDivisorTester.BatchSize = scanBatchSize;
|
||||||
|
if (_byDivisorTester is MersenneNumberDivisorByDivisorCpuTester cpuByDivisorTester)
|
||||||
|
{
|
||||||
|
cpuByDivisorTester.DeltasDevice = byDivisorDeltasDevice;
|
||||||
|
cpuByDivisorTester.MontgomeryDevice = byDivisorMontgomeryDevice;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load RLE blacklist (optional)
|
// Load RLE blacklist (optional)
|
||||||
@@ -552,18 +607,18 @@ internal static class Program
|
|||||||
useDivisor,
|
useDivisor,
|
||||||
useByDivisor,
|
useByDivisor,
|
||||||
mersenneOnGpu,
|
mersenneOnGpu,
|
||||||
useOrder,
|
useOrder,
|
||||||
useModuloWorkaround,
|
useModuloWorkaround,
|
||||||
_useGcdFilter,
|
_useGcdFilter,
|
||||||
NttGpuMath.GpuTransformBackend,
|
NttGpuMath.GpuTransformBackend,
|
||||||
gpuPrimeThreads,
|
gpuPrimeThreads,
|
||||||
sliceSize,
|
sliceSize,
|
||||||
scanBatchSize,
|
scanBatchSize,
|
||||||
_orderWarmupLimitOverride ?? 5_000_000UL,
|
_orderWarmupLimitOverride ?? 5_000_000UL,
|
||||||
NttGpuMath.ReductionMode,
|
NttGpuMath.ReductionMode,
|
||||||
mersenneOnGpu ? "gpu" : "cpu",
|
mersenneOnGpu ? "gpu" : "cpu",
|
||||||
(GpuContextPool.ForceCpu ? "cpu" : "gpu"),
|
(GpuContextPool.ForceCpu ? "cpu" : "gpu"),
|
||||||
orderOnGpu ? "gpu" : "cpu");
|
orderOnGpu ? "gpu" : "cpu");
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(_resultsPrefix))
|
if (!string.IsNullOrEmpty(_resultsPrefix))
|
||||||
{
|
{
|
||||||
@@ -665,7 +720,7 @@ internal static class Program
|
|||||||
|
|
||||||
if (_useByDivisorMode)
|
if (_useByDivisorMode)
|
||||||
{
|
{
|
||||||
RunByDivisorMode(byDivisorCandidates, threadCount);
|
RunByDivisorMode(byDivisorCandidates, threadCount, _primeTestLimit);
|
||||||
FlushBuffer();
|
FlushBuffer();
|
||||||
StringBuilderPool.Return(_outputBuilder!);
|
StringBuilderPool.Return(_outputBuilder!);
|
||||||
return;
|
return;
|
||||||
@@ -806,7 +861,7 @@ internal static class Program
|
|||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RunByDivisorMode(List<ulong> candidates, int threadCount)
|
private static void RunByDivisorMode(List<ulong> candidates, int threadCount, TimeSpan? primeTestLimit)
|
||||||
{
|
{
|
||||||
if (_byDivisorTester is null)
|
if (_byDivisorTester is null)
|
||||||
{
|
{
|
||||||
@@ -823,7 +878,8 @@ internal static class Program
|
|||||||
static () => _lastCompositeP = true,
|
static () => _lastCompositeP = true,
|
||||||
static () => _lastCompositeP = false,
|
static () => _lastCompositeP = false,
|
||||||
PrintResult,
|
PrintResult,
|
||||||
threadCount);
|
threadCount,
|
||||||
|
primeTestLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction)
|
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction)
|
||||||
@@ -890,6 +946,51 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryParsePrimeTestLimit(ReadOnlySpan<char> value, out TimeSpan result)
|
||||||
|
{
|
||||||
|
result = TimeSpan.Zero;
|
||||||
|
if (value.IsEmpty)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<char> magnitudeSpan = value;
|
||||||
|
double magnitude;
|
||||||
|
|
||||||
|
if (value.Length >= 2 && value.EndsWith("ms", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
magnitudeSpan = value[..^2];
|
||||||
|
if (!double.TryParse(magnitudeSpan, NumberStyles.Float, CultureInfo.InvariantCulture, out magnitude))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = TimeSpan.FromMilliseconds(magnitude);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
char suffix = char.ToLowerInvariant(value[^1]);
|
||||||
|
if (suffix == 's' || suffix == 'm')
|
||||||
|
{
|
||||||
|
magnitudeSpan = value[..^1];
|
||||||
|
if (!double.TryParse(magnitudeSpan, NumberStyles.Float, CultureInfo.InvariantCulture, out magnitude))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = suffix == 's' ? TimeSpan.FromSeconds(magnitude) : TimeSpan.FromMinutes(magnitude);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out magnitude))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = TimeSpan.FromMilliseconds(magnitude);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static void PrintHelp()
|
private static void PrintHelp()
|
||||||
{
|
{
|
||||||
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
|
Console.WriteLine("Usage: EvenPerfectBitScanner [options]");
|
||||||
@@ -923,6 +1024,9 @@ internal static class Program
|
|||||||
Console.WriteLine(" --divisor-cycles-batch=<value> batch size for cycles generation (default 512)");
|
Console.WriteLine(" --divisor-cycles-batch=<value> batch size for cycles generation (default 512)");
|
||||||
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
|
Console.WriteLine(" --divisor-cycles-continue continue divisor cycles generation");
|
||||||
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
|
Console.WriteLine(" --divisor-cycles-limit=<value> cycle search iterations when --mersenne=divisor");
|
||||||
|
Console.WriteLine(" --prime-test-limit=<value> Time limit for --mersenne=bydivisor prime checks (e.g. 5s, 500ms)");
|
||||||
|
Console.WriteLine(" --bydivisor-deltas-device=cpu|gpu Device for --mersenne=bydivisor residue batches (default: cpu)");
|
||||||
|
Console.WriteLine(" --bydivisor-montgomery-device=cpu|gpu Device for Montgomery data when using --mersenne=bydivisor (default: cpu)");
|
||||||
Console.WriteLine(" --use-order test primality via q order");
|
Console.WriteLine(" --use-order test primality via q order");
|
||||||
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
|
Console.WriteLine(" --workaround-mod avoid '%' operator on the GPU");
|
||||||
// mod-automaton removed
|
// mod-automaton removed
|
||||||
|
|||||||
@@ -26,7 +26,8 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
markComposite: () => compositeMarks++,
|
markComposite: () => compositeMarks++,
|
||||||
clearComposite: () => clearedMarks++,
|
clearComposite: () => clearedMarks++,
|
||||||
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
|
printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
|
||||||
threadCount: 1);
|
threadCount: 1,
|
||||||
|
primeTestLimit: null);
|
||||||
|
|
||||||
tester.ConfiguredMaxPrime.Should().Be(13UL);
|
tester.ConfiguredMaxPrime.Should().Be(13UL);
|
||||||
compositeMarks.Should().Be(2);
|
compositeMarks.Should().Be(2);
|
||||||
@@ -59,7 +60,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
return DivisorLimit;
|
return DivisorLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
||||||
|
|||||||
@@ -7,78 +7,88 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class MersenneNumberDivisorCpuTesterTests
|
public class MersenneNumberDivisorCpuTesterTests
|
||||||
{
|
{
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_tester_tracks_divisors_across_primes()
|
public void ByDivisor_tester_tracks_divisors_across_primes()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
tester.ConfigureFromMaxPrime(11UL);
|
tester.ConfigureFromMaxPrime(11UL);
|
||||||
|
|
||||||
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
|
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
|
||||||
divisorsExhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
|
|
||||||
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
|
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
|
||||||
divisorsExhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
|
|
||||||
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
|
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
|
||||||
divisorsExhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_session_checks_divisors_across_primes()
|
public void ByDivisor_prime_test_limit_disables_divisor_exhaustion()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
tester.ConfigureFromMaxPrime(13UL);
|
tester.ConfigureFromMaxPrime(11UL);
|
||||||
|
|
||||||
using var session = tester.CreateDivisorSession();
|
tester.IsPrime(5UL, out bool divisorsExhausted, TimeSpan.Zero).Should().BeTrue();
|
||||||
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
divisorsExhausted.Should().BeFalse();
|
||||||
byte[] hits = new byte[primes.Length];
|
}
|
||||||
|
|
||||||
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
[Fact]
|
||||||
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
[Trait("Category", "Fast")]
|
||||||
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
public void ByDivisor_session_checks_divisors_across_primes()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(13UL);
|
||||||
|
|
||||||
Array.Fill(hits, (byte)0);
|
using var session = tester.CreateDivisorSession();
|
||||||
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||||
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
byte[] hits = new byte[primes.Length];
|
||||||
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL));
|
||||||
[Trait("Category", "Fast")]
|
session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits);
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
|
||||||
tester.ConfigureFromMaxPrime(43UL);
|
|
||||||
|
|
||||||
using var session = tester.CreateDivisorSession();
|
Array.Fill(hits, (byte)0);
|
||||||
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||||
byte[] hits = new byte[exponents.Length];
|
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
||||||
|
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||||
|
}
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
[Fact]
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
[Trait("Category", "Fast")]
|
||||||
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
using var session = tester.CreateDivisorSession();
|
||||||
}
|
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||||
|
byte[] hits = new byte[exponents.Length];
|
||||||
|
|
||||||
[Fact]
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||||
[Trait("Category", "Fast")]
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
|
||||||
tester.ConfigureFromMaxPrime(61UL);
|
|
||||||
|
|
||||||
using var session = tester.CreateDivisorSession();
|
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
}
|
||||||
byte[] hits = new byte[exponents.Length];
|
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
[Fact]
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
[Trait("Category", "Fast")]
|
||||||
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(61UL);
|
||||||
|
|
||||||
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
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 });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +86,17 @@ public class MersenneNumberDivisorGpuTesterTests
|
|||||||
exhausted.Should().BeTrue();
|
exhausted.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Category", "Fast")]
|
||||||
|
public void ByDivisor_gpu_prime_test_limit_stops_scan_immediately()
|
||||||
|
{
|
||||||
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
|
tester.IsPrime(31UL, out bool divisorsExhausted, TimeSpan.Zero).Should().BeTrue();
|
||||||
|
divisorsExhausted.Should().BeFalse();
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_tester_requires_configuration()
|
public void ByDivisor_tester_requires_configuration()
|
||||||
|
|||||||
@@ -22,4 +22,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../PerfectNumbers.Core/PerfectNumbers.Core.csproj" />
|
<ProjectReference Include="../PerfectNumbers.Core/PerfectNumbers.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove=".temp.cs" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -7,6 +7,9 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class PrimeOrderCalculatorTests
|
public class PrimeOrderCalculatorTests
|
||||||
{
|
{
|
||||||
|
// EvenPerfectBitScanner never exercises the GPU heuristic device or the small-prime guards that the legacy tests cover,
|
||||||
|
// so those scenarios are commented out because they are unreachable in the production pipeline.
|
||||||
|
/*
|
||||||
public static IEnumerable<object[]> SmallPrimes()
|
public static IEnumerable<object[]> SmallPrimes()
|
||||||
{
|
{
|
||||||
yield return new object[] { 3UL };
|
yield return new object[] { 3UL };
|
||||||
@@ -197,4 +200,5 @@ public class PrimeOrderCalculatorTests
|
|||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
public enum ByDivisorDeltasDevice
|
||||||
|
{
|
||||||
|
Cpu,
|
||||||
|
Gpu
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
public enum ByDivisorMontgomeryDevice
|
||||||
|
{
|
||||||
|
Cpu = 0,
|
||||||
|
Gpu = 1,
|
||||||
|
}
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
|
using PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Cpu;
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
@@ -15,17 +17,49 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
private bool _isConfigured;
|
private bool _isConfigured;
|
||||||
private int _batchSize = 1_024;
|
private int _batchSize = 1_024;
|
||||||
|
|
||||||
|
private ulong _configuredMaxPrime;
|
||||||
|
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
|
||||||
|
private ByDivisorMontgomeryDevice _montgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
||||||
|
|
||||||
public int BatchSize
|
public int BatchSize
|
||||||
{
|
{
|
||||||
get => _batchSize;
|
get => _batchSize;
|
||||||
set => _batchSize = Math.Max(1, value);
|
set
|
||||||
|
{
|
||||||
|
int sanitized = Math.Max(1, value);
|
||||||
|
_batchSize = sanitized;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByDivisorDeltasDevice DeltasDevice
|
||||||
|
{
|
||||||
|
get => _deltasDevice;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_deltasDevice = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByDivisorMontgomeryDevice MontgomeryDevice
|
||||||
|
{
|
||||||
|
get => _montgomeryDevice;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_montgomeryDevice = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
|
_configuredMaxPrime = maxPrime;
|
||||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||||
_lastStatusDivisor = 0UL;
|
_lastStatusDivisor = 0UL;
|
||||||
_isConfigured = true;
|
_isConfigured = true;
|
||||||
@@ -61,7 +95,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
||||||
{
|
{
|
||||||
ulong allowedMax;
|
ulong allowedMax;
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
@@ -89,7 +123,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
allowedMax,
|
allowedMax,
|
||||||
out lastProcessed,
|
out lastProcessed,
|
||||||
out processedAll,
|
out processedAll,
|
||||||
out processedCount);
|
out processedCount,
|
||||||
|
timeLimit);
|
||||||
|
|
||||||
if (processedCount > 0UL)
|
if (processedCount > 0UL)
|
||||||
{
|
{
|
||||||
@@ -163,12 +198,38 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
ulong allowedMax,
|
ulong allowedMax,
|
||||||
out ulong lastProcessed,
|
out ulong lastProcessed,
|
||||||
out bool processedAll,
|
out bool processedAll,
|
||||||
out ulong processedCount)
|
out ulong processedCount,
|
||||||
|
TimeSpan? timeLimit)
|
||||||
|
{
|
||||||
|
if (_deltasDevice == ByDivisorDeltasDevice.Cpu)
|
||||||
|
{
|
||||||
|
return CheckDivisorsCpu(prime, allowedMax, out lastProcessed, out processedAll, out processedCount, timeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CheckDivisorsGpu(prime, allowedMax, out lastProcessed, out processedAll, out processedCount, timeLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool CheckDivisorsCpu(
|
||||||
|
ulong prime,
|
||||||
|
ulong allowedMax,
|
||||||
|
out ulong lastProcessed,
|
||||||
|
out bool processedAll,
|
||||||
|
out ulong processedCount,
|
||||||
|
TimeSpan? timeLimit)
|
||||||
{
|
{
|
||||||
lastProcessed = 0UL;
|
lastProcessed = 0UL;
|
||||||
processedCount = 0UL;
|
processedCount = 0UL;
|
||||||
processedAll = false;
|
processedAll = false;
|
||||||
|
|
||||||
|
PrimeTestTimeLimit limitGuard;
|
||||||
|
if (!PrimeTestTimeLimit.TryCreate(timeLimit, out limitGuard))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enforceLimit = limitGuard.IsActive;
|
||||||
|
ulong iterationCounter = 0UL;
|
||||||
|
|
||||||
if (allowedMax < 3UL)
|
if (allowedMax < 3UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
@@ -212,6 +273,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
while (divisor <= limit)
|
while (divisor <= limit)
|
||||||
{
|
{
|
||||||
|
if (enforceLimit && (iterationCounter == 0UL || (iterationCounter & 63UL) == 0UL) && limitGuard.HasExpired())
|
||||||
|
{
|
||||||
|
processedAll = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterationCounter++;
|
||||||
ulong candidate = (ulong)divisor;
|
ulong candidate = (ulong)divisor;
|
||||||
processedCount++;
|
processedCount++;
|
||||||
lastProcessed = candidate;
|
lastProcessed = candidate;
|
||||||
@@ -222,7 +290,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
|
if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
|
||||||
{
|
{
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
|
// Each divisor appears only once in this monotonically increasing sequence, so
|
||||||
|
// rebuilding the Montgomery data per candidate avoids caching entries that we
|
||||||
|
// would never hit again.
|
||||||
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||||
ulong divisorCycle;
|
ulong divisorCycle;
|
||||||
|
|
||||||
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||||
@@ -232,6 +303,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
factorCache ??= new Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>(8);
|
||||||
|
// EvenPerfectBitScanner reaches this path only for divisors above the precomputed cycle snapshot.
|
||||||
|
// Those candidates follow q = 2 * prime * k + 1 with prime >= 5, so the downstream order calculator
|
||||||
|
// can rely on an odd divisor and a prime exponent when deriving the cycle length.
|
||||||
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponent(candidate, prime, divisorData, factorCache, out divisorCycle) || divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
divisorCycle = cycleCache.GetCycleLength(candidate);
|
divisorCycle = cycleCache.GetCycleLength(candidate);
|
||||||
@@ -266,6 +340,245 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private bool CheckDivisorsGpu(
|
||||||
|
ulong prime,
|
||||||
|
ulong allowedMax,
|
||||||
|
out ulong lastProcessed,
|
||||||
|
out bool processedAll,
|
||||||
|
out ulong processedCount,
|
||||||
|
TimeSpan? timeLimit)
|
||||||
|
{
|
||||||
|
lastProcessed = 0UL;
|
||||||
|
processedCount = 0UL;
|
||||||
|
processedAll = false;
|
||||||
|
|
||||||
|
PrimeTestTimeLimit limitGuard;
|
||||||
|
if (!PrimeTestTimeLimit.TryCreate(timeLimit, out limitGuard))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enforceLimit = limitGuard.IsActive;
|
||||||
|
ulong iterationCounter = 0UL;
|
||||||
|
|
||||||
|
if (allowedMax < 3UL)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GpuUInt128 step128 = new GpuUInt128(prime);
|
||||||
|
step128.ShiftLeft(1);
|
||||||
|
if (step128.IsZero)
|
||||||
|
{
|
||||||
|
processedAll = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GpuUInt128 limit128 = new GpuUInt128(allowedMax);
|
||||||
|
GpuUInt128 divisor128 = step128;
|
||||||
|
divisor128.Add(1UL);
|
||||||
|
if (divisor128.CompareTo(limit128) > 0)
|
||||||
|
{
|
||||||
|
processedAll = true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong step = step128.Low;
|
||||||
|
ulong limit64 = allowedMax;
|
||||||
|
|
||||||
|
int batchCapacity = Math.Max(1, _batchSize);
|
||||||
|
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
|
||||||
|
int preferredCycleBatch = cycleCache.PreferredBatchSize;
|
||||||
|
if (preferredCycleBatch > 0 && preferredCycleBatch < batchCapacity)
|
||||||
|
{
|
||||||
|
batchCapacity = preferredCycleBatch;
|
||||||
|
}
|
||||||
|
|
||||||
|
var candidateEvaluator = new MersenneNumberDivisorCandidateGpuEvaluator(batchCapacity);
|
||||||
|
|
||||||
|
ulong[] candidateBuffer = ArrayPool<ulong>.Shared.Rent(batchCapacity);
|
||||||
|
byte[] maskBuffer = ArrayPool<byte>.Shared.Rent(batchCapacity);
|
||||||
|
|
||||||
|
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
|
||||||
|
|
||||||
|
const int RemainderTableSize = 6;
|
||||||
|
byte[] remainderBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize);
|
||||||
|
byte[] stepBuffer = ArrayPool<byte>.Shared.Rent(RemainderTableSize);
|
||||||
|
Span<byte> remainderSpan = remainderBuffer.AsSpan(0, RemainderTableSize);
|
||||||
|
Span<byte> stepSpan = stepBuffer.AsSpan(0, RemainderTableSize);
|
||||||
|
// The remainder and step tables follow the fixed modulus order: 10, 8, 5, 3, 7, 11.
|
||||||
|
|
||||||
|
ulong startDivisor64 = divisor128.Low;
|
||||||
|
remainderSpan[0] = (byte)(startDivisor64 % 10UL);
|
||||||
|
remainderSpan[1] = (byte)(startDivisor64 % 8UL);
|
||||||
|
remainderSpan[2] = (byte)(startDivisor64 % 5UL);
|
||||||
|
remainderSpan[3] = (byte)(startDivisor64 % 3UL);
|
||||||
|
remainderSpan[4] = (byte)(startDivisor64 % 7UL);
|
||||||
|
remainderSpan[5] = (byte)(startDivisor64 % 11UL);
|
||||||
|
|
||||||
|
stepSpan[0] = (byte)(step % 10UL);
|
||||||
|
stepSpan[1] = (byte)(step % 8UL);
|
||||||
|
stepSpan[2] = (byte)(step % 5UL);
|
||||||
|
stepSpan[3] = (byte)(step % 3UL);
|
||||||
|
stepSpan[4] = (byte)(step % 7UL);
|
||||||
|
stepSpan[5] = (byte)(step % 11UL);
|
||||||
|
|
||||||
|
bool lastIsSeven = (prime & 3UL) == 3UL;
|
||||||
|
|
||||||
|
MersenneNumberDivisorRemainderGpuStepper remainderStepper = new(stepSpan);
|
||||||
|
MersenneNumberDivisorMontgomeryGpuBuilder? montgomeryBuilder = _montgomeryDevice == ByDivisorMontgomeryDevice.Gpu
|
||||||
|
? new MersenneNumberDivisorMontgomeryGpuBuilder(batchCapacity)
|
||||||
|
: null;
|
||||||
|
MontgomeryDivisorData[]? montgomeryBuffer = montgomeryBuilder != null
|
||||||
|
? ArrayPool<MontgomeryDivisorData>.Shared.Rent(batchCapacity)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
ulong remainingCandidates = ComputeRemainingCandidateCount(startDivisor64, limit64, step);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (remainingCandidates > 0UL && divisor128.CompareTo(limit128) <= 0)
|
||||||
|
{
|
||||||
|
if (divisor128.High != 0UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong currentDivisor = divisor128.Low;
|
||||||
|
int chunkCount = remainingCandidates > (ulong)batchCapacity
|
||||||
|
? batchCapacity
|
||||||
|
: (int)remainingCandidates;
|
||||||
|
if (chunkCount <= 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<ulong> candidateSpan = candidateBuffer.AsSpan(0, chunkCount);
|
||||||
|
Span<byte> maskSpan = maskBuffer.AsSpan(0, chunkCount);
|
||||||
|
|
||||||
|
candidateEvaluator.EvaluateCandidates(
|
||||||
|
currentDivisor,
|
||||||
|
step,
|
||||||
|
limit64,
|
||||||
|
remainderSpan,
|
||||||
|
stepSpan,
|
||||||
|
lastIsSeven,
|
||||||
|
candidateSpan,
|
||||||
|
maskSpan);
|
||||||
|
|
||||||
|
Span<MontgomeryDivisorData> montgomerySpan = default;
|
||||||
|
if (montgomeryBuilder != null && montgomeryBuffer != null)
|
||||||
|
{
|
||||||
|
montgomerySpan = montgomeryBuffer.AsSpan(0, chunkCount);
|
||||||
|
// Every candidate divisor is unique across the scan, so build Montgomery data
|
||||||
|
// for the current batch and drop it immediately instead of maintaining a cache.
|
||||||
|
montgomeryBuilder.Build(candidateSpan, montgomerySpan);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < chunkCount; i++)
|
||||||
|
{
|
||||||
|
if (enforceLimit && (iterationCounter == 0UL || (iterationCounter & 63UL) == 0UL) && limitGuard.HasExpired())
|
||||||
|
{
|
||||||
|
processedAll = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterationCounter++;
|
||||||
|
|
||||||
|
ulong candidate = candidateSpan[i];
|
||||||
|
processedCount++;
|
||||||
|
lastProcessed = candidate;
|
||||||
|
|
||||||
|
if (maskSpan[i] == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MontgomeryDivisorData divisorData;
|
||||||
|
if (!montgomerySpan.IsEmpty)
|
||||||
|
{
|
||||||
|
divisorData = montgomerySpan[i];
|
||||||
|
if (divisorData.Modulus != candidate)
|
||||||
|
{
|
||||||
|
// The GPU builder might skip entries when masks zero them out; recompute on the CPU
|
||||||
|
// without caching because divisors never repeat within a session.
|
||||||
|
divisorData = MontgomeryDivisorData.FromModulus(candidate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Candidates remain unique even when GPU Montgomery support is disabled, so rebuild
|
||||||
|
// the divisor data inline instead of persisting unused cache entries.
|
||||||
|
divisorData = MontgomeryDivisorData.FromModulus(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 == prime)
|
||||||
|
{
|
||||||
|
processedAll = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (divisorCycle == 0UL)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GpuUInt128 increment = step128;
|
||||||
|
increment.Mul((ulong)chunkCount);
|
||||||
|
divisor128.Add(increment);
|
||||||
|
|
||||||
|
remainderStepper.Advance(chunkCount, remainderSpan);
|
||||||
|
remainingCandidates -= (ulong)chunkCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
montgomeryBuilder?.Dispose();
|
||||||
|
if (montgomeryBuffer != null)
|
||||||
|
{
|
||||||
|
ArrayPool<MontgomeryDivisorData>.Shared.Return(montgomeryBuffer, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
remainderStepper.Dispose();
|
||||||
|
ArrayPool<byte>.Shared.Return(remainderBuffer, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(stepBuffer, clearArray: false);
|
||||||
|
ArrayPool<ulong>.Shared.Return(candidateBuffer, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(maskBuffer, clearArray: false);
|
||||||
|
candidateEvaluator.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
processedAll = remainingCandidates == 0UL && divisor128.CompareTo(limit128) > 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong ComputeRemainingCandidateCount(ulong currentDivisor, ulong limit, ulong step)
|
||||||
|
{
|
||||||
|
if (step == 0UL || currentDivisor > limit)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong distance = limit - currentDivisor;
|
||||||
|
ulong stepsAvailable = distance / step;
|
||||||
|
return stepsAvailable + 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
|
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
|
||||||
@@ -337,6 +650,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
private ulong[]? _primeDeltas;
|
||||||
|
private ulong[]? _cycleRemainders;
|
||||||
|
private ulong[]? _residues;
|
||||||
|
private int _bufferCapacity;
|
||||||
|
private MersenneNumberDivisorResidueGpuEvaluator? _gpuResidueEvaluator;
|
||||||
|
|
||||||
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
|
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
|
||||||
{
|
{
|
||||||
@@ -361,10 +679,19 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
MontgomeryDivisorData cachedData = divisorData;
|
MontgomeryDivisorData effectiveDivisorData = divisorData;
|
||||||
if (cachedData.Modulus != divisor)
|
if (effectiveDivisorData.Modulus != divisor)
|
||||||
{
|
{
|
||||||
cachedData = MontgomeryDivisorDataCache.Get(divisor);
|
// Each divisor surfaces only once across the monotonic scan, so rebuild the Montgomery data
|
||||||
|
// instead of retaining a cache entry that would never be reused within future checks.
|
||||||
|
effectiveDivisorData = MontgomeryDivisorData.FromModulus(divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong modulus = effectiveDivisorData.Modulus;
|
||||||
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
hits.Clear();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (divisorCycle == 0UL)
|
if (divisorCycle == 0UL)
|
||||||
@@ -377,9 +704,24 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_owner._deltasDevice == ByDivisorDeltasDevice.Cpu)
|
||||||
|
{
|
||||||
|
CheckDivisorCpu(effectiveDivisorData, divisorCycle, primes, hits);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CheckDivisorGpu(effectiveDivisorData, divisorCycle, primes, hits, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CheckDivisorCpu(
|
||||||
|
in MontgomeryDivisorData divisorData,
|
||||||
|
ulong divisorCycle,
|
||||||
|
ReadOnlySpan<ulong> primes,
|
||||||
|
Span<byte> hits)
|
||||||
|
{
|
||||||
// Keep these remainder steppers in place so future updates continue reusing the previously computed residues.
|
// 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.
|
// They are critical for avoiding repeated full Montgomery exponentiation work when scanning divisors.
|
||||||
var exponentStepper = new ExponentRemainderStepper(cachedData);
|
var exponentStepper = new ExponentRemainderStepper(divisorData);
|
||||||
if (!exponentStepper.IsValidModulus)
|
if (!exponentStepper.IsValidModulus)
|
||||||
{
|
{
|
||||||
hits.Clear();
|
hits.Clear();
|
||||||
@@ -393,6 +735,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
? (exponentStepper.ComputeNextIsUnity(primes[0]) ? (byte)1 : (byte)0)
|
? (exponentStepper.ComputeNextIsUnity(primes[0]) ? (byte)1 : (byte)0)
|
||||||
: (byte)0;
|
: (byte)0;
|
||||||
|
|
||||||
|
int length = primes.Length;
|
||||||
for (int i = 1; i < length; i++)
|
for (int i = 1; i < length; i++)
|
||||||
{
|
{
|
||||||
remainder = cycleStepper.ComputeNext(primes[i]);
|
remainder = cycleStepper.ComputeNext(primes[i]);
|
||||||
@@ -406,6 +749,171 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CheckDivisorGpu(
|
||||||
|
in MontgomeryDivisorData divisorData,
|
||||||
|
ulong divisorCycle,
|
||||||
|
ReadOnlySpan<ulong> primes,
|
||||||
|
Span<byte> hits,
|
||||||
|
int length)
|
||||||
|
{
|
||||||
|
EnsureCapacity(length);
|
||||||
|
|
||||||
|
Span<ulong> deltaSpan = _primeDeltas!.AsSpan(0, length);
|
||||||
|
ComputePrimeDeltas(primes, deltaSpan);
|
||||||
|
|
||||||
|
Span<ulong> remainderSpan = _cycleRemainders!.AsSpan(0, length);
|
||||||
|
ComputeCycleRemainders(divisorCycle, primes, deltaSpan, remainderSpan);
|
||||||
|
|
||||||
|
EnsureGpuEvaluator();
|
||||||
|
Span<ulong> residueSpan = _residues!.AsSpan(0, length);
|
||||||
|
_gpuResidueEvaluator!.ComputeResidues(primes, divisorData, residueSpan);
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
hits[i] = remainderSpan[i] == 0UL && residueSpan[i] == 1UL ? (byte)1 : (byte)0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureGpuEvaluator()
|
||||||
|
{
|
||||||
|
if (_gpuResidueEvaluator is null)
|
||||||
|
{
|
||||||
|
_gpuResidueEvaluator = new MersenneNumberDivisorResidueGpuEvaluator(_owner._batchSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReleaseGpuEvaluator()
|
||||||
|
{
|
||||||
|
if (_gpuResidueEvaluator is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_gpuResidueEvaluator.Dispose();
|
||||||
|
_gpuResidueEvaluator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int requiredLength)
|
||||||
|
{
|
||||||
|
if (requiredLength <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (requiredLength <= _bufferCapacity && _primeDeltas is not null && _cycleRemainders is not null && _residues is not null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newCapacity = _bufferCapacity;
|
||||||
|
if (newCapacity == 0)
|
||||||
|
{
|
||||||
|
newCapacity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (newCapacity < requiredLength)
|
||||||
|
{
|
||||||
|
newCapacity <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReturnBuffers();
|
||||||
|
|
||||||
|
_primeDeltas = ArrayPool<ulong>.Shared.Rent(newCapacity);
|
||||||
|
_cycleRemainders = ArrayPool<ulong>.Shared.Rent(newCapacity);
|
||||||
|
_residues = ArrayPool<ulong>.Shared.Rent(newCapacity);
|
||||||
|
_bufferCapacity = newCapacity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReturnBuffers()
|
||||||
|
{
|
||||||
|
if (_primeDeltas is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(_primeDeltas, clearArray: false);
|
||||||
|
_primeDeltas = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_cycleRemainders is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(_cycleRemainders, clearArray: false);
|
||||||
|
_cycleRemainders = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_residues is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<ulong>.Shared.Return(_residues, clearArray: false);
|
||||||
|
_residues = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_bufferCapacity = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComputePrimeDeltas(ReadOnlySpan<ulong> primes, Span<ulong> deltas)
|
||||||
|
{
|
||||||
|
if (primes.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong previous = primes[0];
|
||||||
|
deltas[0] = previous;
|
||||||
|
|
||||||
|
for (int i = 1; i < primes.Length; i++)
|
||||||
|
{
|
||||||
|
ulong current = primes[i];
|
||||||
|
if (current <= previous)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException(nameof(primes), "Primes must be strictly increasing.");
|
||||||
|
}
|
||||||
|
|
||||||
|
deltas[i] = current - previous;
|
||||||
|
previous = current;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComputeCycleRemainders(ulong cycleLength, ReadOnlySpan<ulong> primes, ReadOnlySpan<ulong> deltas, Span<ulong> remainders)
|
||||||
|
{
|
||||||
|
if (cycleLength == 0UL || primes.Length == 0)
|
||||||
|
{
|
||||||
|
remainders.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong remainder = primes[0] % cycleLength;
|
||||||
|
remainders[0] = remainder;
|
||||||
|
|
||||||
|
for (int i = 1; i < primes.Length; i++)
|
||||||
|
{
|
||||||
|
remainder = AddMod(remainder, deltas[i], cycleLength);
|
||||||
|
remainders[i] = remainder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong AddMod(ulong value, ulong delta, ulong modulus)
|
||||||
|
{
|
||||||
|
if (modulus == 0UL)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ulong.MaxValue - value < delta)
|
||||||
|
{
|
||||||
|
UInt128 extended = (UInt128)value + delta;
|
||||||
|
return (ulong)(extended % modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong sum = value + delta;
|
||||||
|
if (sum >= modulus)
|
||||||
|
{
|
||||||
|
sum -= modulus;
|
||||||
|
if (sum >= modulus)
|
||||||
|
{
|
||||||
|
sum %= modulus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_disposed)
|
if (_disposed)
|
||||||
@@ -414,6 +922,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
|
ReleaseGpuEvaluator();
|
||||||
_owner.ReturnSession(this);
|
_owner.ReturnSession(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ public sealed class DivisorCycleCache
|
|||||||
int[]? rentedMissing = null;
|
int[]? rentedMissing = null;
|
||||||
Span<int> missingBuffer = length <= StackBufferThreshold
|
Span<int> missingBuffer = length <= StackBufferThreshold
|
||||||
? stackalloc int[length]
|
? stackalloc int[length]
|
||||||
: new Span<int>(rentedMissing = ArrayPool<int>.Shared.Rent(length), 0, length);
|
: length < PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? new Span<int>(new int[length])
|
||||||
|
: new Span<int>(rentedMissing = ThreadLocalArrayPool<int>.Shared.Rent(length), 0, length);
|
||||||
|
|
||||||
int missingCount = 0;
|
int missingCount = 0;
|
||||||
|
|
||||||
@@ -106,7 +108,7 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
if (rentedMissing is not null)
|
if (rentedMissing is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
|
ThreadLocalArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,12 +130,16 @@ public sealed class DivisorCycleCache
|
|||||||
ulong[]? rentedDivisors = null;
|
ulong[]? rentedDivisors = null;
|
||||||
Span<ulong> workDivisors = count <= StackBufferThreshold
|
Span<ulong> workDivisors = count <= StackBufferThreshold
|
||||||
? stackalloc ulong[count]
|
? stackalloc ulong[count]
|
||||||
: new Span<ulong>(rentedDivisors = ArrayPool<ulong>.Shared.Rent(count), 0, count);
|
: count < PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? new Span<ulong>(new ulong[count])
|
||||||
|
: new Span<ulong>(rentedDivisors = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count);
|
||||||
|
|
||||||
ulong[]? rentedResults = null;
|
ulong[]? rentedResults = null;
|
||||||
Span<ulong> workResults = count <= StackBufferThreshold
|
Span<ulong> workResults = count <= StackBufferThreshold
|
||||||
? stackalloc ulong[count]
|
? stackalloc ulong[count]
|
||||||
: new Span<ulong>(rentedResults = ArrayPool<ulong>.Shared.Rent(count), 0, count);
|
: count < PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? new Span<ulong>(new ulong[count])
|
||||||
|
: new Span<ulong>(rentedResults = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count);
|
||||||
|
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
@@ -149,8 +155,12 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
if (rentedDivisors is not null)
|
if (rentedDivisors is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
|
ThreadLocalArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
|
||||||
ArrayPool<ulong>.Shared.Return(rentedResults!, clearArray: false);
|
}
|
||||||
|
|
||||||
|
if (rentedResults is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResults, clearArray: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,22 +182,30 @@ public sealed class DivisorCycleCache
|
|||||||
ulong[]? rentedPow = null;
|
ulong[]? rentedPow = null;
|
||||||
Span<ulong> powSpan = length <= StackBufferThreshold
|
Span<ulong> powSpan = length <= StackBufferThreshold
|
||||||
? stackalloc ulong[length]
|
? stackalloc ulong[length]
|
||||||
: new Span<ulong>(rentedPow = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
: length < PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? new Span<ulong>(new ulong[length])
|
||||||
|
: new Span<ulong>(rentedPow = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||||
|
|
||||||
ulong[]? rentedOrder = null;
|
ulong[]? rentedOrder = null;
|
||||||
Span<ulong> orderSpan = length <= StackBufferThreshold
|
Span<ulong> orderSpan = length <= StackBufferThreshold
|
||||||
? stackalloc ulong[length]
|
? stackalloc ulong[length]
|
||||||
: new Span<ulong>(rentedOrder = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
: length < PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? new Span<ulong>(new ulong[length])
|
||||||
|
: new Span<ulong>(rentedOrder = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||||
|
|
||||||
ulong[]? rentedResult = null;
|
ulong[]? rentedResult = null;
|
||||||
Span<ulong> resultSpan = length <= StackBufferThreshold
|
Span<ulong> resultSpan = length <= StackBufferThreshold
|
||||||
? stackalloc ulong[length]
|
? stackalloc ulong[length]
|
||||||
: new Span<ulong>(rentedResult = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
: length < PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? new Span<ulong>(new ulong[length])
|
||||||
|
: new Span<ulong>(rentedResult = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||||
|
|
||||||
byte[]? rentedStatus = null;
|
byte[]? rentedStatus = null;
|
||||||
Span<byte> statusSpan = length <= StackBufferThreshold
|
Span<byte> statusSpan = length <= StackBufferThreshold
|
||||||
? stackalloc byte[length]
|
? stackalloc byte[length]
|
||||||
: new Span<byte>(rentedStatus = ArrayPool<byte>.Shared.Rent(length), 0, length);
|
: length < PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? new Span<byte>(new byte[length])
|
||||||
|
: new Span<byte>(rentedStatus = ThreadLocalArrayPool<byte>.Shared.Rent(length), 0, length);
|
||||||
|
|
||||||
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
|
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
|
||||||
divisorBuffer.View.CopyFromCPU(ref divisorRef, length);
|
divisorBuffer.View.CopyFromCPU(ref divisorRef, length);
|
||||||
@@ -234,10 +252,22 @@ public sealed class DivisorCycleCache
|
|||||||
resultSpan.CopyTo(destination);
|
resultSpan.CopyTo(destination);
|
||||||
if (rentedPow is not null)
|
if (rentedPow is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
|
ThreadLocalArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
|
||||||
ArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false);
|
}
|
||||||
ArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false);
|
|
||||||
ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false);
|
if (rentedOrder is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<ulong>.Shared.Return(rentedOrder, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rentedResult is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResult, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rentedStatus is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<byte>.Shared.Return(rentedStatus, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
execution.Dispose();
|
execution.Dispose();
|
||||||
|
|||||||
@@ -1,8 +1,19 @@
|
|||||||
namespace PerfectNumbers.Core
|
namespace PerfectNumbers.Core
|
||||||
{
|
{
|
||||||
public readonly struct FactorEntry(ulong value, int exponent)
|
public readonly struct FactorEntry
|
||||||
{
|
{
|
||||||
public readonly ulong Value = value;
|
public readonly ulong Value;
|
||||||
public readonly int Exponent = exponent;
|
public readonly int Exponent = 1;
|
||||||
}
|
|
||||||
|
public FactorEntry(ulong value, int exponent)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Exponent = exponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public FactorEntry(ulong value)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,156 +6,159 @@ namespace PerfectNumbers.Core.Gpu;
|
|||||||
|
|
||||||
public static class GpuContextPool
|
public static class GpuContextPool
|
||||||
{
|
{
|
||||||
private static readonly bool PoolingEnabled = true;
|
private static readonly bool PoolingEnabled = true;
|
||||||
// TODO: Introduce accelerator-specific warmup so pooled contexts precompile the ProcessEightBitWindows kernels and load
|
// TODO: Introduce accelerator-specific warmup so pooled contexts precompile the ProcessEightBitWindows kernels and load
|
||||||
// divisor-cycle data before the first scan begins.
|
// divisor-cycle data before the first scan begins.
|
||||||
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
|
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
|
||||||
public static bool ForceCpu { get; set; } = false;
|
public static bool ForceCpu { get; set; } = false;
|
||||||
|
|
||||||
internal sealed class PooledContext
|
internal sealed class PooledContext
|
||||||
|
{
|
||||||
|
private bool disposed;
|
||||||
|
|
||||||
|
public Context Context { get; }
|
||||||
|
public Accelerator Accelerator { get; }
|
||||||
|
public bool IsCpu { get; }
|
||||||
|
public object ExecutionLock { get; } = new();
|
||||||
|
|
||||||
|
public PooledContext(bool preferCpu)
|
||||||
{
|
{
|
||||||
private bool disposed;
|
Context = Context.CreateDefault();
|
||||||
|
Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context);
|
||||||
public Context Context { get; }
|
IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU;
|
||||||
public Accelerator Accelerator { get; }
|
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
|
||||||
public bool IsCpu { get; }
|
// CL stream/queue creation during accelerator construction.
|
||||||
public object ExecutionLock { get; } = new();
|
// Some OpenCL drivers are fragile when a queue is created immediately
|
||||||
|
// after device init. Real kernels will be JIT-loaded at first use.
|
||||||
public PooledContext(bool preferCpu)
|
|
||||||
{
|
|
||||||
Context = Context.CreateDefault();
|
|
||||||
Accelerator = Context.GetPreferredDevice(preferCpu).CreateAccelerator(Context);
|
|
||||||
IsCpu = Accelerator.AcceleratorType == AcceleratorType.CPU;
|
|
||||||
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
|
|
||||||
// CL stream/queue creation during accelerator construction.
|
|
||||||
// Some OpenCL drivers are fragile when a queue is created immediately
|
|
||||||
// after device init. Real kernels will be JIT-loaded at first use.
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (disposed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure all cached GPU buffers for this accelerator are released.
|
|
||||||
NttGpuMath.ClearCaches(Accelerator);
|
|
||||||
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
|
|
||||||
// Clear any per-accelerator cached resources to avoid releasing
|
|
||||||
// after the accelerator is destroyed.
|
|
||||||
PrimeTester.ClearGpuCaches(Accelerator);
|
|
||||||
|
|
||||||
Accelerator.Dispose();
|
|
||||||
Context.Dispose();
|
|
||||||
disposed = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static readonly ConcurrentQueue<PooledContext> CpuPool = new();
|
|
||||||
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
|
|
||||||
private static readonly object CreationLock = new();
|
|
||||||
|
|
||||||
public static GpuContextLease Rent()
|
|
||||||
{
|
|
||||||
return RentPreferred(ForceCpu);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
|
|
||||||
public static GpuContextLease RentPreferred(bool preferCpu)
|
|
||||||
{
|
|
||||||
if (PoolingEnabled)
|
|
||||||
{
|
|
||||||
if (preferCpu && CpuPool.TryDequeue(out var cpu))
|
|
||||||
{
|
|
||||||
return new GpuContextLease(cpu, CreationLock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!preferCpu && GpuPool.TryDequeue(out var gpu))
|
|
||||||
{
|
|
||||||
return new GpuContextLease(gpu, CreationLock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Serialize accelerator creation to avoid concurrent CL device/stream setup across threads.
|
|
||||||
lock (CreationLock)
|
|
||||||
{
|
|
||||||
return new GpuContextLease(new PooledContext(preferCpu), CreationLock);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void DisposeAll()
|
|
||||||
{
|
|
||||||
if (!PoolingEnabled)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (CpuPool.TryDequeue(out var cpu))
|
|
||||||
{
|
|
||||||
cpu.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
while (GpuPool.TryDequeue(out var gpu))
|
|
||||||
{
|
|
||||||
gpu.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void Return(PooledContext ctx)
|
|
||||||
{
|
|
||||||
if (PoolingEnabled)
|
|
||||||
{
|
|
||||||
ctx.Accelerator.Synchronize();
|
|
||||||
if (ctx.IsCpu)
|
|
||||||
{
|
|
||||||
CpuPool.Enqueue(ctx);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GpuPool.Enqueue(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else
|
|
||||||
{
|
|
||||||
lock (CreationLock)
|
|
||||||
{
|
|
||||||
ctx.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure all cached GPU buffers for this accelerator are released.
|
||||||
|
NttGpuMath.ClearCaches(Accelerator);
|
||||||
|
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
|
||||||
|
// Clear any per-accelerator cached resources to avoid releasing
|
||||||
|
// after the accelerator is destroyed.
|
||||||
|
PrimeTester.ClearGpuCaches(Accelerator);
|
||||||
|
|
||||||
|
Accelerator.Dispose();
|
||||||
|
Context.Dispose();
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly ConcurrentQueue<PooledContext> CpuPool = new();
|
||||||
|
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
|
||||||
|
private static readonly object CreationLock = new();
|
||||||
|
|
||||||
|
public static GpuContextLease Rent()
|
||||||
|
{
|
||||||
|
return RentPreferred(ForceCpu);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
|
||||||
|
public static GpuContextLease RentPreferred(bool preferCpu)
|
||||||
|
{
|
||||||
|
if (PoolingEnabled)
|
||||||
|
{
|
||||||
|
if (preferCpu && CpuPool.TryDequeue(out var cpu))
|
||||||
|
{
|
||||||
|
return new GpuContextLease(cpu, CreationLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preferCpu && GpuPool.TryDequeue(out var gpu))
|
||||||
|
{
|
||||||
|
return new GpuContextLease(gpu, CreationLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize accelerator creation to avoid concurrent CL device/stream setup across threads.
|
||||||
|
lock (CreationLock)
|
||||||
|
{
|
||||||
|
return new GpuContextLease(new PooledContext(preferCpu), CreationLock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DisposeAll()
|
||||||
|
{
|
||||||
|
if (!PoolingEnabled)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (CpuPool.TryDequeue(out var cpu))
|
||||||
|
{
|
||||||
|
cpu.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
while (GpuPool.TryDequeue(out var gpu))
|
||||||
|
{
|
||||||
|
gpu.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void Return(PooledContext ctx)
|
||||||
|
{
|
||||||
|
if (PoolingEnabled)
|
||||||
|
{
|
||||||
|
ctx.Accelerator.Synchronize();
|
||||||
|
if (ctx.IsCpu)
|
||||||
|
{
|
||||||
|
CpuPool.Enqueue(ctx);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GpuPool.Enqueue(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (CreationLock)
|
||||||
|
{
|
||||||
|
ctx.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public struct GpuContextLease
|
public struct GpuContextLease
|
||||||
{
|
{
|
||||||
private readonly PooledContext _ctx;
|
private readonly PooledContext _ctx;
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
|
|
||||||
internal GpuContextLease(PooledContext ctx, object kernelInitLock)
|
internal GpuContextLease(PooledContext ctx, object kernelInitLock)
|
||||||
{
|
{
|
||||||
_ctx = ctx;
|
_ctx = ctx;
|
||||||
KernelInitLock = kernelInitLock;
|
KernelInitLock = kernelInitLock;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context Context => _ctx.Context;
|
public Context Context => _ctx.Context;
|
||||||
|
|
||||||
public Accelerator Accelerator => _ctx.Accelerator;
|
public Accelerator Accelerator => _ctx.Accelerator;
|
||||||
|
|
||||||
public object KernelInitLock { get; }
|
public object KernelInitLock { get; }
|
||||||
|
|
||||||
public object ExecutionLock => _ctx.ExecutionLock;
|
public object ExecutionLock => _ctx.ExecutionLock;
|
||||||
|
|
||||||
|
internal void ReturnToPool()
|
||||||
|
{
|
||||||
|
if (disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
Return(_ctx);
|
||||||
{
|
disposed = true;
|
||||||
if (disposed)
|
}
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Return(_ctx);
|
public void Dispose()
|
||||||
disposed = true;
|
{
|
||||||
|
ReturnToPool();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,6 +37,7 @@ public sealed class KernelContainer
|
|||||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
|
||||||
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
|
||||||
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
|
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
|
||||||
|
public Action<AcceleratorStream, Index1D, ArrayView1D<MontgomeryDivisorData, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int>? MontgomeryOddPowers;
|
||||||
|
|
||||||
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
|
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
|
||||||
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
|
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
|
||||||
@@ -109,15 +110,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var accel = Accelerator; // avoid capturing 'this' in lambda
|
var accel = Accelerator; // avoid capturing 'this' in lambda
|
||||||
lock (_gpu!.Value.KernelInitLock)
|
// lock (_gpu!.Value.KernelInitLock)
|
||||||
{
|
// {
|
||||||
return KernelContainer.InitOnce(ref _kernels.Order, () =>
|
return KernelContainer.InitOnce(ref _kernels.Order, () =>
|
||||||
{
|
{
|
||||||
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernelScan);
|
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernelScan);
|
||||||
var kernel = KernelUtil.GetKernel(loaded);
|
var kernel = KernelUtil.GetKernel(loaded);
|
||||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
|
||||||
});
|
});
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,15 +127,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var accel = Accelerator;
|
var accel = Accelerator;
|
||||||
lock (_gpu!.Value.KernelInitLock)
|
// lock (_gpu!.Value.KernelInitLock)
|
||||||
{
|
// {
|
||||||
return KernelContainer.InitOnce(ref _kernels.Pow2Mod, () =>
|
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 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);
|
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>>>();
|
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>>>();
|
||||||
});
|
});
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,15 +144,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var accel = Accelerator;
|
var accel = Accelerator;
|
||||||
lock (_gpu!.Value.KernelInitLock)
|
// lock (_gpu!.Value.KernelInitLock)
|
||||||
{
|
// {
|
||||||
return KernelContainer.InitOnce(ref _kernels.Incremental, () =>
|
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 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);
|
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>>>();
|
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||||
});
|
});
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,15 +161,15 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var accel = Accelerator;
|
var accel = Accelerator;
|
||||||
lock (_gpu!.Value.KernelInitLock)
|
// lock (_gpu!.Value.KernelInitLock)
|
||||||
{
|
// {
|
||||||
return KernelContainer.InitOnce(ref _kernels.IncrementalOrder, () =>
|
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 loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalOrderKernelScan);
|
||||||
var kernel = KernelUtil.GetKernel(loaded);
|
var kernel = KernelUtil.GetKernel(loaded);
|
||||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||||
});
|
});
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,15 +178,66 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
get
|
get
|
||||||
{
|
{
|
||||||
var accel = Accelerator;
|
var accel = Accelerator;
|
||||||
lock (_gpu!.Value.KernelInitLock)
|
// lock (_gpu!.Value.KernelInitLock)
|
||||||
{
|
// {
|
||||||
return KernelContainer.InitOnce(ref _kernels.Pow2ModOrder, () =>
|
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 loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModOrderKernelScan);
|
||||||
var kernel = KernelUtil.GetKernel(loaded);
|
var kernel = KernelUtil.GetKernel(loaded);
|
||||||
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
|
||||||
});
|
});
|
||||||
}
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<MontgomeryDivisorData, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int> MontgomeryOddPowerKernel
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var accel = Accelerator;
|
||||||
|
// lock (_gpu!.Value.KernelInitLock)
|
||||||
|
// {
|
||||||
|
return KernelContainer.InitOnce(ref _kernels.MontgomeryOddPowers, () =>
|
||||||
|
{
|
||||||
|
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<MontgomeryDivisorData, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int>(MontgomeryOddPowerKernelScan);
|
||||||
|
var kernel = KernelUtil.GetKernel(loaded);
|
||||||
|
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<MontgomeryDivisorData, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int>>();
|
||||||
|
});
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void MontgomeryOddPowerKernelScan(
|
||||||
|
Index1D index,
|
||||||
|
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> divisors,
|
||||||
|
ArrayView1D<int, Stride1D.Dense> counts,
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> results,
|
||||||
|
int stride)
|
||||||
|
{
|
||||||
|
int tableIndex = index.X;
|
||||||
|
int baseOffset = tableIndex * stride;
|
||||||
|
MontgomeryDivisorData divisor = divisors[tableIndex];
|
||||||
|
int oddPowerCount = counts[tableIndex];
|
||||||
|
if (oddPowerCount <= 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong montgomeryTwo = divisor.MontgomeryTwo;
|
||||||
|
results[baseOffset] = montgomeryTwo;
|
||||||
|
if (oddPowerCount == 1)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong modulus = divisor.Modulus;
|
||||||
|
ulong nPrime = divisor.NPrime;
|
||||||
|
ulong square = divisor.MontgomeryTwoSquared;
|
||||||
|
ulong current = montgomeryTwo;
|
||||||
|
for (int i = 1; i < oddPowerCount; i++)
|
||||||
|
{
|
||||||
|
current = ULongExtensions.MontgomeryMultiply(current, square, modulus, nPrime);
|
||||||
|
results[baseOffset + i] = current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -344,7 +396,10 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
_stream?.Dispose();
|
_stream?.Dispose();
|
||||||
_stream = null;
|
_stream = null;
|
||||||
|
|
||||||
_gpu?.Dispose();
|
if (_gpu.HasValue)
|
||||||
|
{
|
||||||
|
_gpu.Value.ReturnToPool();
|
||||||
|
}
|
||||||
_gpu = null;
|
_gpu = null;
|
||||||
|
|
||||||
_executionLock = null;
|
_executionLock = null;
|
||||||
@@ -684,18 +739,18 @@ public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, Kerne
|
|||||||
public ExecutionScope(object? sync)
|
public ExecutionScope(object? sync)
|
||||||
{
|
{
|
||||||
_lock = sync;
|
_lock = sync;
|
||||||
if (_lock is not null)
|
// if (_lock is not null)
|
||||||
{
|
// {
|
||||||
Monitor.Enter(_lock);
|
// Monitor.Enter(_lock);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_lock is not null)
|
// if (_lock is not null)
|
||||||
{
|
// {
|
||||||
Monitor.Exit(_lock);
|
// Monitor.Exit(_lock);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -704,6 +759,8 @@ public class GpuKernelPool
|
|||||||
{
|
{
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
public static bool CpuCycleUsesGpu { get; set; }
|
||||||
|
|
||||||
private static KernelContainer GetKernels(Accelerator accelerator)
|
private static KernelContainer GetKernels(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer());
|
return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer());
|
||||||
@@ -720,8 +777,8 @@ public class GpuKernelPool
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure single upload per accelerator even if multiple threads race here.
|
// Ensure single upload per accelerator even if multiple threads race here.
|
||||||
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.
|
// 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.
|
||||||
{
|
// {
|
||||||
if (kernels.SmallCycles is { } existing)
|
if (kernels.SmallCycles is { } existing)
|
||||||
{
|
{
|
||||||
return existing.View;
|
return existing.View;
|
||||||
@@ -732,7 +789,7 @@ public class GpuKernelPool
|
|||||||
device.View.CopyFromCPU(host);
|
device.View.CopyFromCPU(host);
|
||||||
kernels.SmallCycles = device;
|
kernels.SmallCycles = device;
|
||||||
return device.View;
|
return device.View;
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator)
|
public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator)
|
||||||
@@ -746,8 +803,8 @@ public class GpuKernelPool
|
|||||||
return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
|
return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// 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.
|
||||||
{
|
// {
|
||||||
if (kernels.SmallPrimesLastOne is { } existingLastOne &&
|
if (kernels.SmallPrimesLastOne is { } existingLastOne &&
|
||||||
kernels.SmallPrimesLastSeven is { } existingLastSeven &&
|
kernels.SmallPrimesLastSeven is { } existingLastSeven &&
|
||||||
kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
|
kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
|
||||||
@@ -776,7 +833,7 @@ public class GpuKernelPool
|
|||||||
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
|
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
|
||||||
|
|
||||||
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
|
return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
public static GpuKernelLease GetKernel(bool useGpuOrder)
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ using System.Numerics;
|
|||||||
using ILGPU;
|
using ILGPU;
|
||||||
using ILGPU.Runtime;
|
using ILGPU.Runtime;
|
||||||
using MontgomeryDivisorData = PerfectNumbers.Core.MontgomeryDivisorData;
|
using MontgomeryDivisorData = PerfectNumbers.Core.MontgomeryDivisorData;
|
||||||
using MontgomeryDivisorDataCache = PerfectNumbers.Core.MontgomeryDivisorDataCache;
|
|
||||||
using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
|
using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Gpu;
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
@@ -36,7 +35,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>(CheckKernel));
|
_kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>>(CheckKernel));
|
||||||
|
|
||||||
private Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> GetKernelByPrimeExponent(Accelerator accelerator) =>
|
private Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> GetKernelByPrimeExponent(Accelerator accelerator) =>
|
||||||
_kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputeMontgomeryExponentKernel));
|
_kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputeExponentKernel));
|
||||||
|
|
||||||
private Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> GetRemainderDeltaKernel(Accelerator accelerator) =>
|
private Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> GetRemainderDeltaKernel(Accelerator accelerator) =>
|
||||||
_remainderDeltaKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>, byte>(ComputeRemainderDeltasKernel));
|
_remainderDeltaKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>, byte>(ComputeRemainderDeltasKernel));
|
||||||
@@ -72,7 +71,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
||||||
{
|
{
|
||||||
ulong allowedMax;
|
ulong allowedMax;
|
||||||
int batchCapacity;
|
int batchCapacity;
|
||||||
@@ -109,6 +108,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
composite = CheckDivisors(
|
composite = CheckDivisors(
|
||||||
prime,
|
prime,
|
||||||
allowedMax,
|
allowedMax,
|
||||||
|
timeLimit,
|
||||||
accelerator,
|
accelerator,
|
||||||
kernel,
|
kernel,
|
||||||
resources.DivisorsBuffer,
|
resources.DivisorsBuffer,
|
||||||
@@ -176,6 +176,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
private static bool CheckDivisors(
|
private static bool CheckDivisors(
|
||||||
ulong prime,
|
ulong prime,
|
||||||
ulong allowedMax,
|
ulong allowedMax,
|
||||||
|
TimeSpan? timeLimit,
|
||||||
Accelerator accelerator,
|
Accelerator accelerator,
|
||||||
Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> kernel,
|
Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> kernel,
|
||||||
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
||||||
@@ -203,6 +204,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
bool processedAll = false;
|
bool processedAll = false;
|
||||||
processedCount = 0UL;
|
processedCount = 0UL;
|
||||||
lastProcessed = 0UL;
|
lastProcessed = 0UL;
|
||||||
|
coveredRange = false;
|
||||||
|
|
||||||
|
PrimeTestTimeLimit limitGuard;
|
||||||
|
if (!PrimeTestTimeLimit.TryCreate(timeLimit, out limitGuard))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enforceLimit = limitGuard.IsActive;
|
||||||
|
|
||||||
Span<MontgomeryDivisorData> divisorDataSpan;
|
Span<MontgomeryDivisorData> divisorDataSpan;
|
||||||
Span<ulong> divisorSpan;
|
Span<ulong> divisorSpan;
|
||||||
@@ -278,6 +288,12 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
while (currentK <= maxK)
|
while (currentK <= maxK)
|
||||||
{
|
{
|
||||||
|
if (enforceLimit && limitGuard.HasExpired())
|
||||||
|
{
|
||||||
|
processedAll = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int chunkCount = Math.Min(chunkCapacity, batchCapacity);
|
int chunkCount = Math.Min(chunkCapacity, batchCapacity);
|
||||||
ulong remainingK = maxK - currentK + 1UL;
|
ulong remainingK = maxK - currentK + 1UL;
|
||||||
if ((ulong)chunkCount > remainingK)
|
if ((ulong)chunkCount > remainingK)
|
||||||
@@ -396,6 +412,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ulong divisorValue = filteredDivisorsSpan[i];
|
ulong divisorValue = filteredDivisorsSpan[i];
|
||||||
divisorSpan[i] = divisorValue;
|
divisorSpan[i] = divisorValue;
|
||||||
exponentSpan[i] = prime;
|
exponentSpan[i] = prime;
|
||||||
|
// Divisors never repeat while scanning the monotonically increasing sequence, so regenerate
|
||||||
|
// the Montgomery data for each entry rather than storing cache lines that no future batch would hit.
|
||||||
divisorDataSpan[i] = MontgomeryDivisorData.FromModulus(divisorValue);
|
divisorDataSpan[i] = MontgomeryDivisorData.FromModulus(divisorValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -551,7 +569,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
hits[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
hits[index] = exponent.Pow2ModBinaryGpu(modulus) == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
@@ -759,7 +777,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
mask[globalIndex] = 1;
|
mask[globalIndex] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ComputeMontgomeryExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
private static void ComputeExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
@@ -770,7 +788,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
|
|
||||||
results[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: true);
|
results[index] = exponent.Pow2ModBinaryGpu(modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
|
|||||||
@@ -0,0 +1,262 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable
|
||||||
|
{
|
||||||
|
private const int RemainderCount = 6;
|
||||||
|
|
||||||
|
private readonly GpuContextPool.GpuContextLease _lease;
|
||||||
|
private readonly Accelerator _accelerator;
|
||||||
|
private readonly Action<Index1D, CandidateFilterArgs, ArrayView<ulong>, ArrayView<byte>> _kernel;
|
||||||
|
private MemoryBuffer1D<ulong, Stride1D.Dense> _candidateBuffer;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _maskBuffer;
|
||||||
|
private ulong[] _hostCandidates;
|
||||||
|
private byte[] _hostMask;
|
||||||
|
private int _capacity;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal MersenneNumberDivisorCandidateGpuEvaluator(int batchSize)
|
||||||
|
{
|
||||||
|
_lease = GpuContextPool.RentPreferred(preferCpu: false);
|
||||||
|
_accelerator = _lease.Accelerator;
|
||||||
|
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, CandidateFilterArgs, ArrayView<ulong>, ArrayView<byte>>(EvaluateCandidatesKernel);
|
||||||
|
_capacity = Math.Max(1, batchSize);
|
||||||
|
_candidateBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_maskBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
_hostCandidates = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
_hostMask = ArrayPool<byte>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
_candidateBuffer.Dispose();
|
||||||
|
_maskBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostCandidates, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(_hostMask, clearArray: false);
|
||||||
|
_lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void EvaluateCandidates(
|
||||||
|
ulong startDivisor,
|
||||||
|
ulong step,
|
||||||
|
ulong limit,
|
||||||
|
ReadOnlySpan<byte> remainders,
|
||||||
|
ReadOnlySpan<byte> steps,
|
||||||
|
bool lastIsSeven,
|
||||||
|
Span<ulong> candidates,
|
||||||
|
Span<byte> mask)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MersenneNumberDivisorCandidateGpuEvaluator));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainders.Length != RemainderCount || steps.Length != RemainderCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Remainder and step tables must contain exactly six entries.");
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = candidates.Length;
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCapacity(count);
|
||||||
|
|
||||||
|
Span<ulong> hostCandidateSpan = _hostCandidates.AsSpan(0, count);
|
||||||
|
Span<byte> hostMaskSpan = _hostMask.AsSpan(0, count);
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> candidateView = _candidateBuffer.View.SubView(0, count);
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> maskView = _maskBuffer.View.SubView(0, count);
|
||||||
|
|
||||||
|
var args = new CandidateFilterArgs(
|
||||||
|
startDivisor,
|
||||||
|
step,
|
||||||
|
limit,
|
||||||
|
remainders[0],
|
||||||
|
remainders[1],
|
||||||
|
remainders[2],
|
||||||
|
remainders[3],
|
||||||
|
remainders[4],
|
||||||
|
remainders[5],
|
||||||
|
steps[0],
|
||||||
|
steps[1],
|
||||||
|
steps[2],
|
||||||
|
steps[3],
|
||||||
|
steps[4],
|
||||||
|
steps[5],
|
||||||
|
lastIsSeven ? (byte)1 : (byte)0);
|
||||||
|
|
||||||
|
_kernel(count, args, candidateView, maskView);
|
||||||
|
|
||||||
|
candidateView.CopyToCPU(ref MemoryMarshal.GetReference(hostCandidateSpan), count);
|
||||||
|
maskView.CopyToCPU(ref MemoryMarshal.GetReference(hostMaskSpan), count);
|
||||||
|
|
||||||
|
hostCandidateSpan.CopyTo(candidates);
|
||||||
|
hostMaskSpan.CopyTo(mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int required)
|
||||||
|
{
|
||||||
|
if (required <= _capacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newCapacity = _capacity == 0 ? 1 : _capacity;
|
||||||
|
while (newCapacity < required)
|
||||||
|
{
|
||||||
|
newCapacity <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_candidateBuffer.Dispose();
|
||||||
|
_maskBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostCandidates, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(_hostMask, clearArray: false);
|
||||||
|
|
||||||
|
_capacity = newCapacity;
|
||||||
|
_candidateBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_maskBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
_hostCandidates = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
_hostMask = ArrayPool<byte>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void EvaluateCandidatesKernel(
|
||||||
|
Index1D index,
|
||||||
|
CandidateFilterArgs args,
|
||||||
|
ArrayView<ulong> candidates,
|
||||||
|
ArrayView<byte> mask)
|
||||||
|
{
|
||||||
|
int idx = index;
|
||||||
|
if (idx >= mask.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong offset = (ulong)idx;
|
||||||
|
ulong candidate = args.StartDivisor + args.Step * offset;
|
||||||
|
candidates[idx] = candidate;
|
||||||
|
|
||||||
|
if (candidate > args.Limit || (args.Step != 0UL && candidate < args.StartDivisor))
|
||||||
|
{
|
||||||
|
mask[idx] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainderValue10 = ComputeRemainder(args.Remainder10, args.Step10, idx, 10);
|
||||||
|
int remainderValue8 = ComputeRemainder(args.Remainder8, args.Step8, idx, 8);
|
||||||
|
int remainderValue5 = ComputeRemainder(args.Remainder5, args.Step5, idx, 5);
|
||||||
|
int remainderValue3 = ComputeRemainder(args.Remainder3, args.Step3, idx, 3);
|
||||||
|
int remainderValue7 = ComputeRemainder(args.Remainder7, args.Step7, idx, 7);
|
||||||
|
int remainderValue11 = ComputeRemainder(args.Remainder11, args.Step11, idx, 11);
|
||||||
|
|
||||||
|
bool lastIsSeven = args.LastIsSevenFlag != 0;
|
||||||
|
bool admissible = lastIsSeven
|
||||||
|
? (remainderValue10 == 3 || remainderValue10 == 7 || remainderValue10 == 9)
|
||||||
|
: (remainderValue10 == 1 || remainderValue10 == 3 || remainderValue10 == 9);
|
||||||
|
|
||||||
|
if (!admissible)
|
||||||
|
{
|
||||||
|
mask[idx] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((remainderValue8 != 1 && remainderValue8 != 7)
|
||||||
|
|| remainderValue3 == 0
|
||||||
|
|| remainderValue5 == 0
|
||||||
|
|| remainderValue7 == 0
|
||||||
|
|| remainderValue11 == 0)
|
||||||
|
{
|
||||||
|
mask[idx] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mask[idx] = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int ComputeRemainder(byte remainder, byte step, int index, int modulus)
|
||||||
|
{
|
||||||
|
if (modulus == 0)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
long value = remainder;
|
||||||
|
value += (long)step * index;
|
||||||
|
int result = (int)(value % modulus);
|
||||||
|
if (result < 0)
|
||||||
|
{
|
||||||
|
result += modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly struct CandidateFilterArgs
|
||||||
|
{
|
||||||
|
public CandidateFilterArgs(
|
||||||
|
ulong startDivisor,
|
||||||
|
ulong step,
|
||||||
|
ulong limit,
|
||||||
|
byte remainder10,
|
||||||
|
byte remainder8,
|
||||||
|
byte remainder5,
|
||||||
|
byte remainder3,
|
||||||
|
byte remainder7,
|
||||||
|
byte remainder11,
|
||||||
|
byte step10,
|
||||||
|
byte step8,
|
||||||
|
byte step5,
|
||||||
|
byte step3,
|
||||||
|
byte step7,
|
||||||
|
byte step11,
|
||||||
|
byte lastIsSevenFlag)
|
||||||
|
{
|
||||||
|
StartDivisor = startDivisor;
|
||||||
|
Step = step;
|
||||||
|
Limit = limit;
|
||||||
|
Remainder10 = remainder10;
|
||||||
|
Remainder8 = remainder8;
|
||||||
|
Remainder5 = remainder5;
|
||||||
|
Remainder3 = remainder3;
|
||||||
|
Remainder7 = remainder7;
|
||||||
|
Remainder11 = remainder11;
|
||||||
|
Step10 = step10;
|
||||||
|
Step8 = step8;
|
||||||
|
Step5 = step5;
|
||||||
|
Step3 = step3;
|
||||||
|
Step7 = step7;
|
||||||
|
Step11 = step11;
|
||||||
|
LastIsSevenFlag = lastIsSevenFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ulong StartDivisor { get; }
|
||||||
|
public ulong Step { get; }
|
||||||
|
public ulong Limit { get; }
|
||||||
|
public byte Remainder10 { get; }
|
||||||
|
public byte Remainder8 { get; }
|
||||||
|
public byte Remainder5 { get; }
|
||||||
|
public byte Remainder3 { get; }
|
||||||
|
public byte Remainder7 { get; }
|
||||||
|
public byte Remainder11 { get; }
|
||||||
|
public byte Step10 { get; }
|
||||||
|
public byte Step8 { get; }
|
||||||
|
public byte Step5 { get; }
|
||||||
|
public byte Step3 { get; }
|
||||||
|
public byte Step7 { get; }
|
||||||
|
public byte Step11 { get; }
|
||||||
|
public byte LastIsSevenFlag { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable
|
||||||
|
{
|
||||||
|
private readonly GpuContextPool.GpuContextLease _lease;
|
||||||
|
private readonly Accelerator _accelerator;
|
||||||
|
private readonly Action<Index1D, ArrayView<ulong>, ArrayView<MontgomeryDivisorData>> _kernel;
|
||||||
|
private MemoryBuffer1D<ulong, Stride1D.Dense> _modulusBuffer;
|
||||||
|
private MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> _resultBuffer;
|
||||||
|
private ulong[] _hostModulusBuffer;
|
||||||
|
private MontgomeryDivisorData[] _hostResultBuffer;
|
||||||
|
private int _capacity;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal MersenneNumberDivisorMontgomeryGpuBuilder(int batchSize)
|
||||||
|
{
|
||||||
|
_lease = GpuContextPool.RentPreferred(preferCpu: false);
|
||||||
|
_accelerator = _lease.Accelerator;
|
||||||
|
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<MontgomeryDivisorData>>(BuildKernel);
|
||||||
|
_capacity = Math.Max(1, batchSize);
|
||||||
|
_modulusBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<MontgomeryDivisorData>(_capacity);
|
||||||
|
_hostModulusBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
_hostResultBuffer = ArrayPool<MontgomeryDivisorData>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
_modulusBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostModulusBuffer, clearArray: false);
|
||||||
|
ArrayPool<MontgomeryDivisorData>.Shared.Return(_hostResultBuffer, clearArray: false);
|
||||||
|
_lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Build(ReadOnlySpan<ulong> moduli, Span<MontgomeryDivisorData> destination)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MersenneNumberDivisorMontgomeryGpuBuilder));
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = moduli.Length;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCapacity(length);
|
||||||
|
|
||||||
|
Span<ulong> hostModulusSpan = _hostModulusBuffer.AsSpan(0, length);
|
||||||
|
moduli.CopyTo(hostModulusSpan);
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> modulusView = _modulusBuffer.View.SubView(0, length);
|
||||||
|
modulusView.CopyFromCPU(ref MemoryMarshal.GetReference(hostModulusSpan), length);
|
||||||
|
|
||||||
|
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, length);
|
||||||
|
_kernel(length, modulusView, resultView);
|
||||||
|
|
||||||
|
Span<MontgomeryDivisorData> hostResultSpan = _hostResultBuffer.AsSpan(0, length);
|
||||||
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostResultSpan), length);
|
||||||
|
|
||||||
|
hostResultSpan.CopyTo(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int required)
|
||||||
|
{
|
||||||
|
if (required <= _capacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newCapacity = _capacity == 0 ? 1 : _capacity;
|
||||||
|
while (newCapacity < required)
|
||||||
|
{
|
||||||
|
newCapacity <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_modulusBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostModulusBuffer, clearArray: false);
|
||||||
|
ArrayPool<MontgomeryDivisorData>.Shared.Return(_hostResultBuffer, clearArray: false);
|
||||||
|
|
||||||
|
_capacity = newCapacity;
|
||||||
|
_modulusBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<MontgomeryDivisorData>(_capacity);
|
||||||
|
_hostModulusBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
_hostResultBuffer = ArrayPool<MontgomeryDivisorData>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void BuildKernel(Index1D index, ArrayView<ulong> moduli, ArrayView<MontgomeryDivisorData> destination)
|
||||||
|
{
|
||||||
|
int idx = index;
|
||||||
|
if (idx >= moduli.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong modulus = moduli[idx];
|
||||||
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
destination[idx] = default;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
||||||
|
ulong montgomeryOne = ComputeMontgomeryResidue64(modulus);
|
||||||
|
ulong montgomeryTwo = montgomeryOne.MulMod64GpuCompatible(2UL, modulus);
|
||||||
|
ulong montgomeryTwoSquared = MontgomeryMultiplyGpu(montgomeryTwo, montgomeryTwo, modulus, nPrime);
|
||||||
|
|
||||||
|
destination[idx] = new MontgomeryDivisorData(
|
||||||
|
modulus,
|
||||||
|
nPrime,
|
||||||
|
montgomeryOne,
|
||||||
|
montgomeryTwo,
|
||||||
|
montgomeryTwoSquared);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong ComputeMontgomeryResidue64(ulong modulus)
|
||||||
|
{
|
||||||
|
ulong remainder = ulong.MaxValue % modulus;
|
||||||
|
remainder++;
|
||||||
|
if (remainder >= modulus)
|
||||||
|
{
|
||||||
|
remainder -= modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return remainder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong MontgomeryMultiplyGpu(ulong a, ulong b, ulong modulus, ulong nPrime)
|
||||||
|
{
|
||||||
|
GpuUInt128 product = new(a);
|
||||||
|
product.Mul64(new GpuUInt128(b));
|
||||||
|
ulong tLow = product.Low;
|
||||||
|
ulong tHigh = product.High;
|
||||||
|
|
||||||
|
ulong m = unchecked(tLow * nPrime);
|
||||||
|
GpuUInt128 mProduct = new(m);
|
||||||
|
mProduct.Mul64(new GpuUInt128(modulus));
|
||||||
|
ulong mLow = mProduct.Low;
|
||||||
|
ulong mHigh = mProduct.High;
|
||||||
|
|
||||||
|
ulong carry = unchecked((tLow + mLow) < tLow ? 1UL : 0UL);
|
||||||
|
ulong result = tHigh + mHigh + carry;
|
||||||
|
if (result >= modulus)
|
||||||
|
{
|
||||||
|
result -= modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||||||
|
{
|
||||||
|
ulong inv = modulus;
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
inv *= unchecked(2UL - modulus * inv);
|
||||||
|
return unchecked(0UL - inv);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,140 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable
|
||||||
|
{
|
||||||
|
private const int RemainderCount = 6;
|
||||||
|
|
||||||
|
private readonly GpuContextPool.GpuContextLease _lease;
|
||||||
|
private readonly Accelerator _accelerator;
|
||||||
|
private readonly Action<Index1D, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>> _kernel;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _remainderBuffer;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _stepBuffer;
|
||||||
|
private MemoryBuffer1D<byte, Stride1D.Dense> _resultBuffer;
|
||||||
|
private byte[] _hostRemainders;
|
||||||
|
private byte[] _hostSteps;
|
||||||
|
private readonly int _length;
|
||||||
|
private int _capacity;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal MersenneNumberDivisorRemainderGpuStepper(ReadOnlySpan<byte> steps)
|
||||||
|
{
|
||||||
|
if (steps.Length != RemainderCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Step table must contain exactly six entries.");
|
||||||
|
}
|
||||||
|
|
||||||
|
_lease = GpuContextPool.RentPreferred(preferCpu: false);
|
||||||
|
_accelerator = _lease.Accelerator;
|
||||||
|
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>>(AdvanceRemaindersKernel);
|
||||||
|
|
||||||
|
_length = steps.Length;
|
||||||
|
_capacity = Math.Max(1, _length);
|
||||||
|
_remainderBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
_stepBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<byte>(_capacity);
|
||||||
|
|
||||||
|
_hostRemainders = ArrayPool<byte>.Shared.Rent(_capacity);
|
||||||
|
_hostSteps = ArrayPool<byte>.Shared.Rent(_capacity);
|
||||||
|
|
||||||
|
steps.CopyTo(_hostSteps.AsSpan(0, _length));
|
||||||
|
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> stepView = _stepBuffer.View.SubView(0, _length);
|
||||||
|
stepView.CopyFromCPU(ref MemoryMarshal.GetReference(_hostSteps.AsSpan(0, _length)), _length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
_remainderBuffer.Dispose();
|
||||||
|
_stepBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<byte>.Shared.Return(_hostRemainders, clearArray: false);
|
||||||
|
ArrayPool<byte>.Shared.Return(_hostSteps, clearArray: false);
|
||||||
|
_lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Advance(int advanceCount, Span<byte> remainders)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MersenneNumberDivisorRemainderGpuStepper));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (advanceCount <= 0 || remainders.Length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainders.Length != _length)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Remainder buffer length must match the configured length.");
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> hostRemainderSpan = _hostRemainders.AsSpan(0, _length);
|
||||||
|
remainders.CopyTo(hostRemainderSpan);
|
||||||
|
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> remainderView = _remainderBuffer.View.SubView(0, _length);
|
||||||
|
remainderView.CopyFromCPU(ref MemoryMarshal.GetReference(hostRemainderSpan), _length);
|
||||||
|
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> stepView = _stepBuffer.View.SubView(0, _length);
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, _length);
|
||||||
|
|
||||||
|
_kernel(_length, advanceCount, remainderView, stepView, resultView);
|
||||||
|
|
||||||
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostRemainderSpan), _length);
|
||||||
|
hostRemainderSpan.CopyTo(remainders);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AdvanceRemaindersKernel(Index1D index, int advanceCount, ArrayView<byte> remainders, ArrayView<byte> steps, ArrayView<byte> results)
|
||||||
|
{
|
||||||
|
int idx = index;
|
||||||
|
if (idx >= remainders.Length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int modulus = GetModulus(idx);
|
||||||
|
if (modulus == 0)
|
||||||
|
{
|
||||||
|
results[idx] = remainders[idx];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int remainder = remainders[idx] % modulus;
|
||||||
|
int step = steps[idx] % modulus;
|
||||||
|
|
||||||
|
long delta = (long)step * advanceCount;
|
||||||
|
int adjusted = (int)((remainder + delta) % modulus);
|
||||||
|
if (adjusted < 0)
|
||||||
|
{
|
||||||
|
adjusted += modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
results[idx] = (byte)adjusted;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int GetModulus(int index)
|
||||||
|
{
|
||||||
|
return index switch
|
||||||
|
{
|
||||||
|
0 => 10,
|
||||||
|
1 => 8,
|
||||||
|
2 => 5,
|
||||||
|
3 => 3,
|
||||||
|
4 => 7,
|
||||||
|
5 => 11,
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using PerfectNumbers.Core;
|
||||||
|
using ILGPU;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable
|
||||||
|
{
|
||||||
|
private readonly GpuContextPool.GpuContextLease _lease;
|
||||||
|
private readonly Accelerator _accelerator;
|
||||||
|
private readonly Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> _kernel;
|
||||||
|
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentBuffer;
|
||||||
|
private MemoryBuffer1D<ulong, Stride1D.Dense> _resultBuffer;
|
||||||
|
private ulong[] _hostBuffer;
|
||||||
|
private int _capacity;
|
||||||
|
private bool _disposed;
|
||||||
|
|
||||||
|
internal MersenneNumberDivisorResidueGpuEvaluator(int batchSize)
|
||||||
|
{
|
||||||
|
_lease = GpuContextPool.RentPreferred(preferCpu: false);
|
||||||
|
_accelerator = _lease.Accelerator;
|
||||||
|
_kernel = _accelerator.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputeResiduesKernel);
|
||||||
|
_capacity = Math.Max(1, batchSize);
|
||||||
|
_exponentBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_disposed = true;
|
||||||
|
_exponentBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostBuffer, clearArray: false);
|
||||||
|
_lease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ComputeResidues(ReadOnlySpan<ulong> exponents, in MontgomeryDivisorData divisorData, Span<ulong> destination)
|
||||||
|
{
|
||||||
|
if (_disposed)
|
||||||
|
{
|
||||||
|
throw new ObjectDisposedException(nameof(MersenneNumberDivisorResidueGpuEvaluator));
|
||||||
|
}
|
||||||
|
|
||||||
|
int length = exponents.Length;
|
||||||
|
if (length == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnsureCapacity(length);
|
||||||
|
|
||||||
|
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, length);
|
||||||
|
exponents.CopyTo(hostSpan);
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> exponentView = _exponentBuffer.View.SubView(0, length);
|
||||||
|
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(hostSpan), length);
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> resultView = _resultBuffer.View.SubView(0, length);
|
||||||
|
_kernel(length, divisorData, exponentView, resultView);
|
||||||
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(hostSpan), length);
|
||||||
|
|
||||||
|
hostSpan.CopyTo(destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int required)
|
||||||
|
{
|
||||||
|
if (required <= _capacity)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int newCapacity = _capacity;
|
||||||
|
if (newCapacity == 0)
|
||||||
|
{
|
||||||
|
newCapacity = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (newCapacity < required)
|
||||||
|
{
|
||||||
|
newCapacity <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
_exponentBuffer.Dispose();
|
||||||
|
_resultBuffer.Dispose();
|
||||||
|
ArrayPool<ulong>.Shared.Return(_hostBuffer, clearArray: false);
|
||||||
|
|
||||||
|
_capacity = newCapacity;
|
||||||
|
_exponentBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_resultBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
|
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ComputeResiduesKernel(Index1D index, MontgomeryDivisorData divisorData, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
||||||
|
{
|
||||||
|
int idx = index;
|
||||||
|
int length = (int)exponents.Length;
|
||||||
|
if (idx >= length)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong exponent = exponents[idx];
|
||||||
|
ulong modulus = divisorData.Modulus;
|
||||||
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
|
{
|
||||||
|
results[idx] = 0UL;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
results[idx] = exponent.Pow2ModBinaryGpu(modulus);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
using ILGPU.Runtime;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core.Gpu;
|
||||||
|
|
||||||
|
internal static class MontgomeryOddPowerGpu
|
||||||
|
{
|
||||||
|
public static bool TryCompute(in MontgomeryDivisorData divisor, int oddPowerCount, Span<ulong> destination)
|
||||||
|
{
|
||||||
|
if (oddPowerCount <= 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<MontgomeryDivisorData> divisorSpan = stackalloc MontgomeryDivisorData[1];
|
||||||
|
divisorSpan[0] = divisor;
|
||||||
|
Span<int> countSpan = stackalloc int[1];
|
||||||
|
countSpan[0] = oddPowerCount;
|
||||||
|
Span<ulong> resultSpan = stackalloc ulong[PerfectNumberConstants.MaxOddPowersCount];
|
||||||
|
|
||||||
|
if (!TryComputeBatch(divisorSpan, countSpan, resultSpan, PerfectNumberConstants.MaxOddPowersCount))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultSpan[..oddPowerCount].CopyTo(destination);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool TryComputeBatch(
|
||||||
|
ReadOnlySpan<MontgomeryDivisorData> divisors,
|
||||||
|
ReadOnlySpan<int> oddPowerCounts,
|
||||||
|
Span<ulong> results,
|
||||||
|
int stride)
|
||||||
|
{
|
||||||
|
int divisorCount = divisors.Length;
|
||||||
|
if (divisorCount == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oddPowerCounts.Length < divisorCount)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"oddPowerCounts span must contain at least as many elements as divisors span.",
|
||||||
|
nameof(oddPowerCounts));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (results.Length < divisorCount * stride)
|
||||||
|
{
|
||||||
|
throw new ArgumentException(
|
||||||
|
"The result span is shorter than the required batched odd power storage.",
|
||||||
|
nameof(results));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GpuContextPool.ForceCpu)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||||
|
var execution = lease.EnterExecutionScope();
|
||||||
|
var accelerator = lease.Accelerator;
|
||||||
|
var stream = lease.Stream;
|
||||||
|
|
||||||
|
using var divisorBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(divisorCount);
|
||||||
|
using var countBuffer = accelerator.Allocate1D<int>(divisorCount);
|
||||||
|
using var resultBuffer = accelerator.Allocate1D<ulong>(divisorCount * stride);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
divisorBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(divisors), divisorCount);
|
||||||
|
countBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(oddPowerCounts), divisorCount);
|
||||||
|
resultBuffer.MemSetToZero();
|
||||||
|
|
||||||
|
var kernel = lease.MontgomeryOddPowerKernel;
|
||||||
|
kernel(stream, divisorCount, divisorBuffer.View, countBuffer.View, resultBuffer.View, stride);
|
||||||
|
stream.Synchronize();
|
||||||
|
|
||||||
|
resultBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), divisorCount * stride);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
execution.Dispose();
|
||||||
|
lease.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -811,7 +811,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong reduced = phi / factor;
|
ulong reduced = phi / factor;
|
||||||
if (reduced.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL)
|
if (reduced.Pow2ModBinaryGpu(divisor.Modulus) == 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1595,7 +1595,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
|
|
||||||
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false) == 1UL;
|
return exponent.Pow2ModBinaryGpu(divisor.Modulus) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong CalculateByDoublingKernel(ulong prime)
|
private static ulong CalculateByDoublingKernel(ulong prime)
|
||||||
@@ -1715,10 +1715,10 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
GpuUInt128[]? rentedExponents = null;
|
GpuUInt128[]? rentedExponents = null;
|
||||||
GpuUInt128[]? rentedResults = null;
|
GpuUInt128[]? rentedResults = null;
|
||||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||||
|
var execution = lease.EnterExecutionScope();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var execution = lease.EnterExecutionScope();
|
|
||||||
Accelerator accelerator = lease.Accelerator;
|
Accelerator accelerator = lease.Accelerator;
|
||||||
AcceleratorStream stream = lease.Stream;
|
AcceleratorStream stream = lease.Stream;
|
||||||
var kernel = GetPow2ModWideKernel(accelerator);
|
var kernel = GetPow2ModWideKernel(accelerator);
|
||||||
@@ -1755,7 +1755,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
|
|
||||||
exponentBuffer.Dispose();
|
exponentBuffer.Dispose();
|
||||||
remainderBuffer.Dispose();
|
remainderBuffer.Dispose();
|
||||||
execution.Dispose();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -1775,6 +1774,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
ArrayPool<GpuUInt128>.Shared.Return(rentedResults, clearArray: false);
|
ArrayPool<GpuUInt128>.Shared.Return(rentedResults, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
execution.Dispose();
|
||||||
lease.Dispose();
|
lease.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1811,7 +1811,57 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
private static void Pow2ModKernel(Index1D index, ArrayView1D<ulong, Stride1D.Dense> exponents, MontgomeryDivisorData divisor, ArrayView1D<ulong, Stride1D.Dense> remainders)
|
||||||
{
|
{
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
remainders[index] = exponent.Pow2MontgomeryModWindowedGpu(divisor, keepMontgomery: false);
|
ulong modulus = divisor.Modulus;
|
||||||
|
// ulong nPrime = divisor.NPrime;
|
||||||
|
// ulong result = divisor.MontgomeryOne;
|
||||||
|
// ulong baseValue = divisor.MontgomeryTwo;
|
||||||
|
// ulong remainingExponent = exponent;
|
||||||
|
|
||||||
|
// if (remainingExponent == 0UL)
|
||||||
|
// {
|
||||||
|
// remainders[index] = result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// while (remainingExponent != 0UL)
|
||||||
|
// {
|
||||||
|
// if ((remainingExponent & 1UL) != 0UL)
|
||||||
|
// {
|
||||||
|
// result = result.MontgomeryMultiply(baseValue, modulus, nPrime);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// remainingExponent >>= 1;
|
||||||
|
// if (remainingExponent == 0UL)
|
||||||
|
// {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// // Reusing baseValue to hold base^2 so we avoid allocating another register for the squared term.
|
||||||
|
// baseValue = baseValue.MontgomeryMultiply(baseValue, modulus, nPrime);
|
||||||
|
// }
|
||||||
|
|
||||||
|
// remainders[index] = result.MontgomeryMultiply(1UL, modulus, nPrime);
|
||||||
|
|
||||||
|
// Plain modular exponentiation without Montgomery reduction kept for reference.
|
||||||
|
ulong plainResult = 1UL;// % modulus;
|
||||||
|
ulong plainBase = 2UL;// % modulus;
|
||||||
|
while (exponent != 0UL)
|
||||||
|
{
|
||||||
|
if ((exponent & 1UL) != 0UL)
|
||||||
|
{
|
||||||
|
plainResult = (plainResult * plainBase) % modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
exponent >>= 1;
|
||||||
|
if (exponent == 0UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
plainBase = (plainBase * plainBase) % modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
remainders[index] = plainResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> GetPow2ModWideKernel(Accelerator accelerator)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ public interface IMersenneNumberDivisorByDivisorTester
|
|||||||
|
|
||||||
ulong GetAllowedMaxDivisor(ulong prime);
|
ulong GetAllowedMaxDivisor(ulong prime);
|
||||||
|
|
||||||
bool IsPrime(ulong prime, out bool divisorsExhausted);
|
bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null);
|
||||||
|
|
||||||
IDivisorScanSession CreateDivisorSession();
|
IDivisorScanSession CreateDivisorSession();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -18,6 +19,12 @@ public class MersenneDivisorCycles
|
|||||||
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
// Lightweight read-mostly cache for small divisors (<= 4,000,000). 0 => unknown
|
||||||
private ulong[]? _smallCycles;
|
private ulong[]? _smallCycles;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, int>? s_factorCountsPool;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static Dictionary<ulong, int>? s_factorScratchPool;
|
||||||
|
|
||||||
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
public static MersenneDivisorCycles Shared { get; } = new MersenneDivisorCycles();
|
||||||
|
|
||||||
public MersenneDivisorCycles()
|
public MersenneDivisorCycles()
|
||||||
@@ -273,17 +280,24 @@ public class MersenneDivisorCycles
|
|||||||
{
|
{
|
||||||
cycleLength = 0UL;
|
cycleLength = 0UL;
|
||||||
|
|
||||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
// The by-divisor scanner only supplies odd candidates of the form 2*k*exponent + 1,
|
||||||
{
|
// so this power-of-two fast-path never triggers on that call path.
|
||||||
cycleLength = 1UL;
|
// The historical fast-path is left here for reference but commented out because it is unreachable
|
||||||
return true;
|
// on the EvenPerfectBitScanner pipeline.
|
||||||
}
|
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||||
|
// {
|
||||||
|
// cycleLength = 1UL;
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
if (divisor <= 3UL || exponent <= 1UL)
|
// EvenPerfectBitScanner filters exponents <= 3 before invoking this helper and each
|
||||||
{
|
// divisor candidate is at least 2 * exponent + 1, so this guard never matches.
|
||||||
cycleLength = CalculateCycleLength(divisor, divisorData);
|
// The fallback calculation is therefore unreachable on the active call path.
|
||||||
return true;
|
// if (divisor <= 3UL || exponent <= 1UL)
|
||||||
}
|
// {
|
||||||
|
// cycleLength = CalculateCycleLength(divisor, divisorData);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
@@ -298,37 +312,53 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong phi = divisor - 1UL;
|
ulong phi = divisor - 1UL;
|
||||||
if (phi == 0UL)
|
// Divisors reach this helper only when they exceed the cached table, which means
|
||||||
{
|
// divisor >= 2 * exponent + 1 and phi cannot underflow to zero here.
|
||||||
return false;
|
// The defensive underflow guard is kept for posterity but disabled for the EvenPerfectBitScanner path.
|
||||||
}
|
// if (phi == 0UL)
|
||||||
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||||
ulong reducedPhi = phi >> twoCount;
|
ulong reducedPhi = phi >> twoCount;
|
||||||
if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
// Every divisor candidate generated by the by-divisor scan satisfies q ≡ 1 (mod 2 * exponent),
|
||||||
{
|
// ensuring reducedPhi keeps a factor of exponent and remains non-zero in this context.
|
||||||
return false;
|
// The divisibility check is therefore unreachable for the current caller and is commented out.
|
||||||
}
|
// if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
||||||
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong k = reducedPhi / exponent;
|
ulong k = reducedPhi / exponent;
|
||||||
if (k == 0UL)
|
// reducedPhi always contains at least one factor of exponent, so dividing by exponent leaves k >= 1.
|
||||||
{
|
// if (k == 0UL)
|
||||||
return false;
|
// {
|
||||||
}
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
Dictionary<ulong, int> factorCounts = new(8);
|
Dictionary<ulong, int> factorCounts = AcquireFactorCountDictionary(8);
|
||||||
if (twoCount > 0)
|
try
|
||||||
{
|
{
|
||||||
|
// The divisor candidates are odd, so phi is even and this branch always records at least one factor of two.
|
||||||
|
// if (twoCount > 0)
|
||||||
|
// {
|
||||||
|
// factorCounts[2UL] = twoCount;
|
||||||
|
// }
|
||||||
factorCounts[2UL] = twoCount;
|
factorCounts[2UL] = twoCount;
|
||||||
}
|
|
||||||
|
|
||||||
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
if (!AccumulateFactors(exponent, factorCache, factorCounts, cacheResult: true) || !AccumulateFactors(k, factorCache, factorCounts, cacheResult: false))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
cycleLength = ReduceOrder(divisorData, phi, factorCounts, exponent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
finally
|
||||||
{
|
{
|
||||||
return false;
|
ReleaseFactorCountDictionary(factorCounts);
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool AccumulateFactors(
|
private static bool AccumulateFactors(
|
||||||
@@ -342,61 +372,119 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization))
|
FactorCacheEntry cachedFactorization;
|
||||||
|
PrimeFactorPartialResult? lease = null;
|
||||||
|
bool fromCache;
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return false;
|
if (!TryGetFactorization(value, cache, cacheResult, out cachedFactorization, out lease, out fromCache))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fromCache)
|
||||||
|
{
|
||||||
|
ulong[] primes = cachedFactorization.Primes;
|
||||||
|
byte[] exponents = cachedFactorization.Exponents;
|
||||||
|
int length = cachedFactorization.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
AddFactor(counts, primes[i], exponents[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lease is null || lease.Count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ReadOnlySpan<ulong> primesSpan = lease.Primes;
|
||||||
|
ReadOnlySpan<byte> exponentsSpan = lease.Exponents;
|
||||||
|
int spanLength = lease.Count;
|
||||||
|
|
||||||
|
for (int i = 0; i < spanLength; i++)
|
||||||
|
{
|
||||||
|
AddFactor(counts, primesSpan[i], exponentsSpan[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
ulong[] primes = factorization.Primes;
|
|
||||||
byte[] exponents = factorization.Exponents;
|
|
||||||
int length = factorization.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
|
||||||
{
|
{
|
||||||
AddFactor(counts, primes[i], exponents[i]);
|
lease?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryGetFactorization(
|
private static bool TryGetFactorization(
|
||||||
ulong value,
|
ulong value,
|
||||||
Dictionary<ulong, FactorCacheEntry>? cache,
|
Dictionary<ulong, FactorCacheEntry>? cache,
|
||||||
bool cacheResult,
|
bool cacheResult,
|
||||||
out FactorCacheEntry factorization)
|
out FactorCacheEntry factorization,
|
||||||
|
out PrimeFactorPartialResult? lease,
|
||||||
|
out bool fromCache)
|
||||||
{
|
{
|
||||||
if (cache is not null && cache.TryGetValue(value, out factorization))
|
factorization = default;
|
||||||
|
lease = null;
|
||||||
|
fromCache = false;
|
||||||
|
|
||||||
|
if (value <= 1UL)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<ulong, int> scratch = new(8);
|
if (cache is not null && cache.TryGetValue(value, out factorization))
|
||||||
if (!TryFactorIntoCountsInternal(value, scratch))
|
|
||||||
{
|
{
|
||||||
factorization = default!;
|
fromCache = true;
|
||||||
return false;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = scratch.Count;
|
Dictionary<ulong, int> scratch = AcquireFactorScratchDictionary(8);
|
||||||
ulong[] primes = new ulong[count];
|
try
|
||||||
byte[] exponents = new byte[count];
|
|
||||||
int index = 0;
|
|
||||||
foreach (KeyValuePair<ulong, int> entry in scratch)
|
|
||||||
{
|
{
|
||||||
primes[index] = entry.Key;
|
if (!TryFactorIntoCountsInternal(value, scratch))
|
||||||
exponents[index] = checked((byte)entry.Value);
|
{
|
||||||
index++;
|
factorization = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = scratch.Count;
|
||||||
|
if (count == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrimeFactorPartialResult result = PrimeFactorPartialResult.Rent(count);
|
||||||
|
lease = result;
|
||||||
|
|
||||||
|
Span<ulong> primes = result.PrimeWriteSpan;
|
||||||
|
Span<byte> exponents = result.ExponentWriteSpan;
|
||||||
|
int index = 0;
|
||||||
|
foreach (KeyValuePair<ulong, int> entry in scratch)
|
||||||
|
{
|
||||||
|
primes[index] = entry.Key;
|
||||||
|
exponents[index] = checked((byte)entry.Value);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.CommitCount(index);
|
||||||
|
result.Sort();
|
||||||
|
|
||||||
|
if (cache is not null && cacheResult)
|
||||||
|
{
|
||||||
|
FactorCacheEntry cacheEntry = result.ToCacheEntry();
|
||||||
|
cache[value] = cacheEntry;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
Array.Sort(primes, exponents);
|
|
||||||
factorization = new FactorCacheEntry(primes, exponents, count);
|
|
||||||
|
|
||||||
if (cache is not null && cacheResult)
|
|
||||||
{
|
{
|
||||||
cache[value] = factorization;
|
ReleaseFactorScratchDictionary(scratch);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
||||||
@@ -471,15 +559,22 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
|
private static ulong ReduceOrder(
|
||||||
|
in MontgomeryDivisorData divisorData,
|
||||||
|
ulong initialOrder,
|
||||||
|
Dictionary<ulong, int> factorCounts,
|
||||||
|
ulong targetOrder)
|
||||||
{
|
{
|
||||||
if (factorCounts.Count == 0)
|
if (factorCounts.Count == 0 || initialOrder == targetOrder)
|
||||||
{
|
{
|
||||||
return initialOrder;
|
return initialOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = factorCounts.Count;
|
int count = factorCounts.Count;
|
||||||
ulong[] primes = ArrayPool<ulong>.Shared.Rent(count);
|
bool rentFromPool = count >= PerfectNumberConstants.PooledArrayThreshold;
|
||||||
|
ulong[] primes = rentFromPool
|
||||||
|
? ThreadLocalArrayPool<ulong>.Shared.Rent(count)
|
||||||
|
: new ulong[count];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@@ -508,6 +603,13 @@ public class MersenneDivisorCycles
|
|||||||
if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL)
|
if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
order = candidate;
|
order = candidate;
|
||||||
|
if (order == targetOrder)
|
||||||
|
{
|
||||||
|
// Early-exit once the order matches the tested exponent;
|
||||||
|
// further factor processing would not change the outcome for the by-divisor scan.
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -519,7 +621,10 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ArrayPool<ulong>.Shared.Return(primes, clearArray: false);
|
if (rentFromPool)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<ulong>.Shared.Return(primes, clearArray: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -592,6 +697,53 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Thread-local dictionary pools keep the factoring helpers reusable so the GC stays out of the by-divisor CPU loop.
|
||||||
|
private static Dictionary<ulong, int> AcquireFactorCountDictionary(int capacityHint)
|
||||||
|
{
|
||||||
|
Dictionary<ulong, int>? dictionary = s_factorCountsPool;
|
||||||
|
if (dictionary is null)
|
||||||
|
{
|
||||||
|
return new Dictionary<ulong, int>(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_factorCountsPool = null;
|
||||||
|
dictionary.Clear();
|
||||||
|
dictionary.EnsureCapacity(capacityHint);
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReleaseFactorCountDictionary(Dictionary<ulong, int> dictionary)
|
||||||
|
{
|
||||||
|
dictionary.Clear();
|
||||||
|
if (s_factorCountsPool is null)
|
||||||
|
{
|
||||||
|
s_factorCountsPool = dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Dictionary<ulong, int> AcquireFactorScratchDictionary(int capacityHint)
|
||||||
|
{
|
||||||
|
Dictionary<ulong, int>? dictionary = s_factorScratchPool;
|
||||||
|
if (dictionary is null)
|
||||||
|
{
|
||||||
|
return new Dictionary<ulong, int>(capacityHint);
|
||||||
|
}
|
||||||
|
|
||||||
|
s_factorScratchPool = null;
|
||||||
|
dictionary.Clear();
|
||||||
|
dictionary.EnsureCapacity(capacityHint);
|
||||||
|
return dictionary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReleaseFactorScratchDictionary(Dictionary<ulong, int> dictionary)
|
||||||
|
{
|
||||||
|
dictionary.Clear();
|
||||||
|
if (s_factorScratchPool is null)
|
||||||
|
{
|
||||||
|
s_factorScratchPool = dictionary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static ulong NextRandomUInt64()
|
private static ulong NextRandomUInt64()
|
||||||
{
|
{
|
||||||
Span<byte> buffer = stackalloc byte[8];
|
Span<byte> buffer = stackalloc byte[8];
|
||||||
@@ -658,7 +810,11 @@ public class MersenneDivisorCycles
|
|||||||
tasks[taskIndex] = Task.Run(() =>
|
tasks[taskIndex] = Task.Run(() =>
|
||||||
{
|
{
|
||||||
ulong rangeLength = localEnd - localStart + 1UL;
|
ulong rangeLength = localEnd - localStart + 1UL;
|
||||||
(ulong divisor, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent(checked((int)rangeLength));
|
int localLength = checked((int)rangeLength);
|
||||||
|
bool rentLocalCycles = localLength >= PerfectNumberConstants.PooledArrayThreshold;
|
||||||
|
(ulong divisor, ulong cycle)[] localCycles = rentLocalCycles
|
||||||
|
? ThreadLocalArrayPool<(ulong, ulong)>.Shared.Rent(localLength)
|
||||||
|
: new (ulong divisor, ulong cycle)[localLength];
|
||||||
int localCycleIndex = 0;
|
int localCycleIndex = 0;
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -706,7 +862,10 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
ArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
|
if (rentLocalCycles)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -721,7 +880,7 @@ public class MersenneDivisorCycles
|
|||||||
ulong start = 3UL;
|
ulong start = 3UL;
|
||||||
var locker = new object();
|
var locker = new object();
|
||||||
|
|
||||||
var pool = ArrayPool<ulong>.Shared;
|
var pool = ThreadLocalArrayPool<ulong>.Shared;
|
||||||
ulong batchSizeUL = (ulong)batchSize, d, end;
|
ulong batchSizeUL = (ulong)batchSize, d, end;
|
||||||
int count = (int)Math.Min(batchSizeUL, maxDivisor), i, idx;
|
int count = (int)Math.Min(batchSizeUL, maxDivisor), i, idx;
|
||||||
ulong[] divisors, outCycles, validDivisors;
|
ulong[] divisors, outCycles, validDivisors;
|
||||||
@@ -926,17 +1085,22 @@ public class MersenneDivisorCycles
|
|||||||
|
|
||||||
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength)
|
internal static bool TryCalculateCycleLengthHeuristic(ulong divisor, in MontgomeryDivisorData divisorData, out ulong cycleLength)
|
||||||
{
|
{
|
||||||
if ((divisor & (divisor - 1UL)) == 0UL)
|
// The by-divisor scanner only supplies odd candidates of the form 2*k*exponent + 1,
|
||||||
{
|
// so this power-of-two fast-path never triggers on that call path.
|
||||||
cycleLength = 1UL;
|
// The early exit is retained for reference but commented out because it is unreachable on the scanner path.
|
||||||
return true;
|
// if ((divisor & (divisor - 1UL)) == 0UL)
|
||||||
}
|
// {
|
||||||
|
// cycleLength = 1UL;
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
if (divisor <= 3UL)
|
// By the divisor-by-divisor pipeline, candidates have the shape 2 * exponent * k + 1 with exponent >= 5,
|
||||||
{
|
// so the divisor never drops below 11 on this call path.
|
||||||
cycleLength = CalculateCycleLengthFallback(divisor);
|
// if (divisor <= 3UL)
|
||||||
return true;
|
// {
|
||||||
}
|
// cycleLength = CalculateCycleLengthFallback(divisor);
|
||||||
|
// return true;
|
||||||
|
// }
|
||||||
|
|
||||||
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
Action markComposite,
|
Action markComposite,
|
||||||
Action clearComposite,
|
Action clearComposite,
|
||||||
Action<ulong, bool, bool, bool> printResult,
|
Action<ulong, bool, bool, bool> printResult,
|
||||||
int threadCount)
|
int threadCount,
|
||||||
|
TimeSpan? primeTestLimit)
|
||||||
{
|
{
|
||||||
if (candidates.Count == 0)
|
if (candidates.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -243,7 +244,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
|
|
||||||
void ProcessPrime(ulong prime)
|
void ProcessPrime(ulong prime)
|
||||||
{
|
{
|
||||||
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
|
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted, primeTestLimit);
|
||||||
|
|
||||||
if (!isPrime)
|
if (!isPrime)
|
||||||
{
|
{
|
||||||
@@ -256,21 +257,52 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
printResult(prime, true, divisorsExhausted, true);
|
printResult(prime, true, divisorsExhausted, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workerCount == 1)
|
if (workerCount > filteredPrimes.Count)
|
||||||
|
{
|
||||||
|
workerCount = filteredPrimes.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workerCount <= 1)
|
||||||
{
|
{
|
||||||
foreach (ulong prime in filteredPrimes)
|
foreach (ulong prime in filteredPrimes)
|
||||||
{
|
{
|
||||||
ProcessPrime(prime);
|
ProcessPrime(prime);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ParallelOptions options = new()
|
|
||||||
{
|
|
||||||
MaxDegreeOfParallelism = workerCount
|
|
||||||
};
|
|
||||||
|
|
||||||
Parallel.ForEach(filteredPrimes, options, ProcessPrime);
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
(int Start, int End)[] workerRanges = new (int, int)[workerCount];
|
||||||
|
int totalPrimeCount = filteredPrimes.Count;
|
||||||
|
int baseGroupSize = totalPrimeCount / workerCount;
|
||||||
|
int remaining = totalPrimeCount % workerCount;
|
||||||
|
int groupStartIndex = 0;
|
||||||
|
|
||||||
|
for (int workerIndex = 0; workerIndex < workerCount; workerIndex++)
|
||||||
|
{
|
||||||
|
int groupSize = baseGroupSize;
|
||||||
|
if (workerIndex < remaining)
|
||||||
|
{
|
||||||
|
groupSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
startIndex = groupStartIndex;
|
||||||
|
int endIndex = startIndex + groupSize;
|
||||||
|
workerRanges[workerIndex] = (startIndex, endIndex);
|
||||||
|
groupStartIndex = endIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
ParallelOptions parallelOptions = new()
|
||||||
|
{
|
||||||
|
MaxDegreeOfParallelism = workerCount
|
||||||
|
};
|
||||||
|
|
||||||
|
Parallel.ForEach(workerRanges, parallelOptions, range =>
|
||||||
|
{
|
||||||
|
for (int candidateIndex = range.Start; candidateIndex < range.End; candidateIndex++)
|
||||||
|
{
|
||||||
|
ProcessPrime(filteredPrimes[candidateIndex]);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -45,32 +45,4 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong
|
|||||||
montgomeryTwo,
|
montgomeryTwo,
|
||||||
montgomeryTwoSquared);
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,243 @@
|
|||||||
|
using System;
|
||||||
|
using System.Numerics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mutable helper for 128-bit arithmetic derived from the .NET runtime UInt128 implementation.
|
||||||
|
/// Portions adapted from https://github.com/dotnet/runtime/blob/v8.0.0/src/libraries/System.Private.CoreLib/src/System/UInt128.cs.
|
||||||
|
/// </summary>
|
||||||
|
public struct MutableUInt128
|
||||||
|
{
|
||||||
|
public ulong High;
|
||||||
|
public ulong Low;
|
||||||
|
|
||||||
|
public MutableUInt128(ulong value)
|
||||||
|
{
|
||||||
|
Low = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableUInt128(ulong high, ulong low)
|
||||||
|
{
|
||||||
|
High = high;
|
||||||
|
Low = low;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MutableUInt128(UInt128 value)
|
||||||
|
{
|
||||||
|
High = (ulong)(value >> 64);
|
||||||
|
Low = (ulong)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Set(ulong value)
|
||||||
|
{
|
||||||
|
High = 0UL;
|
||||||
|
Low = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Set(UInt128 value)
|
||||||
|
{
|
||||||
|
High = (ulong)(value >> 64);
|
||||||
|
Low = (ulong)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Set(ulong high, ulong low)
|
||||||
|
{
|
||||||
|
High = high;
|
||||||
|
Low = low;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly UInt128 ToUInt128() => ((UInt128)High << 64) | Low;
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Add(ulong value)
|
||||||
|
{
|
||||||
|
ulong low = Low + value;
|
||||||
|
if (low < value)
|
||||||
|
{
|
||||||
|
High++;
|
||||||
|
}
|
||||||
|
|
||||||
|
Low = low;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Add(MutableUInt128 value)
|
||||||
|
{
|
||||||
|
ulong temp = Low + value.Low;
|
||||||
|
Low = temp;
|
||||||
|
temp = temp < Low ? 1UL : 0UL;
|
||||||
|
temp += High;
|
||||||
|
temp += value.High;
|
||||||
|
High = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Sub(ulong value)
|
||||||
|
{
|
||||||
|
ulong temp = Low;
|
||||||
|
ulong result = temp - value;
|
||||||
|
temp = result > temp ? 1UL : 0UL;
|
||||||
|
|
||||||
|
Low = result;
|
||||||
|
High -= temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Sub(MutableUInt128 value)
|
||||||
|
{
|
||||||
|
ulong temp = Low;
|
||||||
|
ulong result = temp - value.Low;
|
||||||
|
temp = temp < value.Low ? 1UL : 0UL;
|
||||||
|
|
||||||
|
Low = result;
|
||||||
|
High = High - value.High - temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Mul(ulong value)
|
||||||
|
{
|
||||||
|
ulong currentHigh = High;
|
||||||
|
ulong currentLow = Low;
|
||||||
|
|
||||||
|
ulong temp = Mul64(currentLow, value, out currentLow);
|
||||||
|
Mul64(currentHigh, value, out currentHigh);
|
||||||
|
temp += currentHigh;
|
||||||
|
|
||||||
|
Low = currentLow;
|
||||||
|
High = temp;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Mul64(ulong left, ulong right, out ulong low)
|
||||||
|
{
|
||||||
|
const ulong Mask32 = 0xFFFF_FFFFUL;
|
||||||
|
|
||||||
|
ulong leftHigh = left >> 32;
|
||||||
|
ulong rightLow = right & Mask32;
|
||||||
|
|
||||||
|
ulong leftLow = left & Mask32;
|
||||||
|
ulong lowLow = leftLow * rightLow;
|
||||||
|
ulong temp = right >> 32;
|
||||||
|
leftLow *= temp;
|
||||||
|
rightLow *= leftHigh;
|
||||||
|
leftHigh *= temp;
|
||||||
|
|
||||||
|
temp = (lowLow >> 32) + (leftLow & Mask32) + (rightLow & Mask32);
|
||||||
|
leftHigh += (leftLow >> 32) + (rightLow >> 32) + (temp >> 32);
|
||||||
|
|
||||||
|
low = ((temp & Mask32) << 32) | (lowLow & Mask32);
|
||||||
|
return leftHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void MulAdd(ulong multiplier, ulong addend)
|
||||||
|
{
|
||||||
|
const ulong Mask32 = 0xFFFF_FFFFUL;
|
||||||
|
|
||||||
|
ulong multiplierLow = multiplier & Mask32;
|
||||||
|
ulong multiplierHigh = multiplier >> 32;
|
||||||
|
|
||||||
|
ulong product11 = Low;
|
||||||
|
ulong product01 = product11 & Mask32;
|
||||||
|
product11 >>= 32;
|
||||||
|
|
||||||
|
ulong product00 = product01 * multiplierLow;
|
||||||
|
product01 *= multiplierHigh;
|
||||||
|
ulong product10 = product11 * multiplierLow;
|
||||||
|
product11 *= multiplierHigh;
|
||||||
|
|
||||||
|
ulong high = (product00 >> 32) + (product01 & Mask32) + (product10 & Mask32);
|
||||||
|
product11 = product11 + (product01 >> 32) + (product10 >> 32) + (high >> 32);
|
||||||
|
product10 = ((high & Mask32) << 32) | (product00 & Mask32);
|
||||||
|
|
||||||
|
high = High;
|
||||||
|
product00 = high & Mask32;
|
||||||
|
high >>= 32;
|
||||||
|
|
||||||
|
product01 = product00 * multiplierLow;
|
||||||
|
product00 *= multiplierHigh;
|
||||||
|
high *= multiplierLow;
|
||||||
|
|
||||||
|
high = (product01 >> 32) + (product00 & Mask32) + (high & Mask32);
|
||||||
|
product01 = ((high & Mask32) << 32) | (product01 & Mask32);
|
||||||
|
|
||||||
|
// Calculate low. We're assigning addend to local variable for a tiny bit better performance.
|
||||||
|
product00 = addend;
|
||||||
|
high = product10 + product00;
|
||||||
|
Low = high;
|
||||||
|
|
||||||
|
// Calculate high including carry
|
||||||
|
high = high < product00 ? 1UL : 0UL;
|
||||||
|
high += product11 + product01;
|
||||||
|
|
||||||
|
// Discard overflow above 128 bits to match Multiply's truncation semantics.
|
||||||
|
High = high;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly ulong Mod(ulong modulus)
|
||||||
|
{
|
||||||
|
// This case will never happen in production code.
|
||||||
|
// if (modulus == 0UL)
|
||||||
|
// {
|
||||||
|
// throw new DivideByZeroException();
|
||||||
|
// }
|
||||||
|
|
||||||
|
ulong high = High;
|
||||||
|
if (high == 0UL)
|
||||||
|
{
|
||||||
|
return Low % modulus;
|
||||||
|
}
|
||||||
|
|
||||||
|
MutableUInt128 remainder = default;
|
||||||
|
int highBitLength = 64 - BitOperations.LeadingZeroCount(high);
|
||||||
|
if (highBitLength > 0)
|
||||||
|
{
|
||||||
|
ProcessWord(ref remainder, high, highBitLength, modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
ProcessWord(ref remainder, Low, 64, modulus);
|
||||||
|
return remainder.Low;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static void ProcessWord(ref MutableUInt128 remainder, ulong word, int bitCount, ulong modulus)
|
||||||
|
{
|
||||||
|
for (int bit = bitCount - 1; bit >= 0; bit--)
|
||||||
|
{
|
||||||
|
remainder.ShiftLeft();
|
||||||
|
if (((word >> bit) & 1UL) != 0)
|
||||||
|
{
|
||||||
|
remainder.Add(1UL);
|
||||||
|
}
|
||||||
|
|
||||||
|
remainder.Reduce(modulus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void ShiftLeft()
|
||||||
|
{
|
||||||
|
High = (High << 1) | (Low >> 63);
|
||||||
|
Low <<= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void Reduce(ulong modulus)
|
||||||
|
{
|
||||||
|
if (High != 0UL || Low >= modulus)
|
||||||
|
{
|
||||||
|
Sub(modulus);
|
||||||
|
if (High != 0UL || Low >= modulus)
|
||||||
|
{
|
||||||
|
Sub(modulus);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core
|
namespace PerfectNumbers.Core
|
||||||
{
|
{
|
||||||
@@ -8,12 +7,20 @@ namespace PerfectNumbers.Core
|
|||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static PartialFactorResult? s_poolHead;
|
private static PartialFactorResult? s_poolHead;
|
||||||
|
|
||||||
|
public static readonly PartialFactorResult Empty = new()
|
||||||
|
{
|
||||||
|
Cofactor = 1UL,
|
||||||
|
FullyFactored = true,
|
||||||
|
};
|
||||||
|
|
||||||
public FactorEntry[]? Factors;
|
public FactorEntry[]? Factors;
|
||||||
private PartialFactorResult? _next;
|
|
||||||
public ulong Cofactor;
|
public ulong Cofactor;
|
||||||
public bool FullyFactored;
|
public bool FullyFactored;
|
||||||
public int Count;
|
public int Count;
|
||||||
|
|
||||||
|
private PartialFactorResult? _next;
|
||||||
|
private bool _factorsFromPool;
|
||||||
|
|
||||||
private PartialFactorResult()
|
private PartialFactorResult()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
@@ -30,52 +37,58 @@ namespace PerfectNumbers.Core
|
|||||||
s_poolHead = instance._next;
|
s_poolHead = instance._next;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FactorEntry[]? factorArray = factors;
|
||||||
instance._next = null;
|
instance._next = null;
|
||||||
instance.Factors = factors;
|
instance.Factors = factorArray;
|
||||||
instance.Cofactor = cofactor;
|
instance.Cofactor = cofactor;
|
||||||
instance.FullyFactored = fullyFactored;
|
instance.FullyFactored = fullyFactored;
|
||||||
instance.Count = count;
|
instance.Count = count;
|
||||||
|
instance._factorsFromPool = factorArray is not null && factorArray.Length >= PerfectNumberConstants.PooledArrayThreshold;
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly PartialFactorResult Empty = new PartialFactorResult
|
|
||||||
{
|
|
||||||
Cofactor = 1UL,
|
|
||||||
FullyFactored = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
||||||
{
|
{
|
||||||
FactorEntry[]? source = Factors;
|
FactorEntry[]? source = Factors;
|
||||||
if (source is null || Count == 0)
|
if (source is null || Count == 0)
|
||||||
{
|
{
|
||||||
FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
|
FactorEntry[] single = new FactorEntry[1];
|
||||||
local[0] = new FactorEntry(prime, 1);
|
single[0] = new FactorEntry(prime);
|
||||||
return Rent(local, 1UL, true, 1);
|
return Rent(single, 1UL, true, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
|
int newCount = Count + 1;
|
||||||
|
FactorEntry[] extended = newCount >= PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(newCount)
|
||||||
|
: new FactorEntry[newCount];
|
||||||
|
|
||||||
Array.Copy(source, 0, extended, 0, Count);
|
Array.Copy(source, 0, extended, 0, Count);
|
||||||
extended[Count] = new FactorEntry(prime, 1);
|
extended[Count] = new FactorEntry(prime);
|
||||||
Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
|
Span<FactorEntry> span = extended.AsSpan(0, newCount);
|
||||||
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
return Rent(extended, 1UL, true, Count + 1);
|
return Rent(extended, 1UL, true, newCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (this != Empty)
|
if (this == Empty)
|
||||||
{
|
{
|
||||||
FactorEntry[]? factors = Factors;
|
return;
|
||||||
if (factors is not null)
|
|
||||||
{
|
|
||||||
Factors = null;
|
|
||||||
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
_next = s_poolHead;
|
|
||||||
s_poolHead = this;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FactorEntry[]? factors = Factors;
|
||||||
|
if (factors is not null)
|
||||||
|
{
|
||||||
|
Factors = null;
|
||||||
|
if (_factorsFromPool)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_factorsFromPool = false;
|
||||||
|
_next = s_poolHead;
|
||||||
|
s_poolHead = this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -22,4 +22,7 @@
|
|||||||
<InternalsVisibleTo Include="EvenPerfectBitScanner.Benchmarks" />
|
<InternalsVisibleTo Include="EvenPerfectBitScanner.Benchmarks" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Remove=".temp.cs" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core
|
||||||
|
{
|
||||||
|
internal sealed class PrimeFactorPartialResult : IDisposable
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static PrimeFactorPartialResult? s_poolHead;
|
||||||
|
|
||||||
|
private PrimeFactorPartialResult? _next;
|
||||||
|
private ulong[]? _primes;
|
||||||
|
private byte[]? _exponents;
|
||||||
|
private bool _arraysFromPool;
|
||||||
|
private int _capacity;
|
||||||
|
|
||||||
|
private PrimeFactorPartialResult()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrimeFactorPartialResult Rent(int capacity)
|
||||||
|
{
|
||||||
|
PrimeFactorPartialResult? instance = s_poolHead;
|
||||||
|
if (instance is null)
|
||||||
|
{
|
||||||
|
instance = new PrimeFactorPartialResult();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s_poolHead = instance._next;
|
||||||
|
}
|
||||||
|
|
||||||
|
instance._next = null;
|
||||||
|
instance.EnsureCapacity(capacity);
|
||||||
|
instance.Count = 0;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
internal ReadOnlySpan<ulong> Primes => _primes is null ? ReadOnlySpan<ulong>.Empty : _primes.AsSpan(0, Count);
|
||||||
|
|
||||||
|
internal ReadOnlySpan<byte> Exponents => _exponents is null ? ReadOnlySpan<byte>.Empty : _exponents.AsSpan(0, Count);
|
||||||
|
|
||||||
|
internal Span<ulong> PrimeWriteSpan => _primes is null ? Span<ulong>.Empty : _primes.AsSpan(0, _capacity);
|
||||||
|
|
||||||
|
internal Span<byte> ExponentWriteSpan => _exponents is null ? Span<byte>.Empty : _exponents.AsSpan(0, _capacity);
|
||||||
|
|
||||||
|
internal void CommitCount(int count)
|
||||||
|
{
|
||||||
|
Count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Sort()
|
||||||
|
{
|
||||||
|
if (Count == 0 || _primes is null || _exponents is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.Sort(_primes, _exponents, 0, Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal MersenneDivisorCycles.FactorCacheEntry ToCacheEntry()
|
||||||
|
{
|
||||||
|
if (Count == 0 || _primes is null || _exponents is null)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong[] primes = new ulong[Count];
|
||||||
|
byte[] exponents = new byte[Count];
|
||||||
|
Array.Copy(_primes, 0, primes, 0, Count);
|
||||||
|
Array.Copy(_exponents, 0, exponents, 0, Count);
|
||||||
|
return new MersenneDivisorCycles.FactorCacheEntry(primes, exponents, Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_arraysFromPool)
|
||||||
|
{
|
||||||
|
if (_primes is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_exponents is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_arraysFromPool = false;
|
||||||
|
_primes = null;
|
||||||
|
_exponents = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_capacity = 0;
|
||||||
|
Count = 0;
|
||||||
|
|
||||||
|
_next = s_poolHead;
|
||||||
|
s_poolHead = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EnsureCapacity(int capacity)
|
||||||
|
{
|
||||||
|
if (capacity <= 0)
|
||||||
|
{
|
||||||
|
_capacity = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_primes is not null && _primes.Length >= capacity)
|
||||||
|
{
|
||||||
|
_capacity = capacity;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_arraysFromPool)
|
||||||
|
{
|
||||||
|
if (_primes is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_exponents is not null)
|
||||||
|
{
|
||||||
|
ThreadLocalArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_arraysFromPool = false;
|
||||||
|
_primes = null;
|
||||||
|
_exponents = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (capacity >= PerfectNumberConstants.PooledArrayThreshold)
|
||||||
|
{
|
||||||
|
_primes = ThreadLocalArrayPool<ulong>.Shared.Rent(capacity);
|
||||||
|
_exponents = ThreadLocalArrayPool<byte>.Shared.Rent(capacity);
|
||||||
|
_arraysFromPool = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_primes = new ulong[capacity];
|
||||||
|
_exponents = new byte[capacity];
|
||||||
|
}
|
||||||
|
|
||||||
|
_capacity = capacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -162,7 +162,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
tempArray = ArrayPool<FactorEntry128>.Shared.Rent(length + 1);
|
tempArray = ThreadLocalArrayPool<FactorEntry128>.Shared.Rent(length + 1);
|
||||||
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
|
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
|
||||||
factorSpan.CopyTo(buffer);
|
factorSpan.CopyTo(buffer);
|
||||||
|
|
||||||
@@ -200,7 +200,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (tempArray is not null)
|
if (tempArray is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
|
ThreadLocalArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,7 +664,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return new PartialFactorResult128(null, value, false, 0);
|
return new PartialFactorResult128(null, value, false, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry128[]? rented = counts.Count > 0 ? ArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
|
FactorEntry128[]? rented = counts.Count > 0 ? ThreadLocalArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
if (rented is not null)
|
if (rented is not null)
|
||||||
{
|
{
|
||||||
@@ -682,7 +682,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
resultArray = new FactorEntry128[index];
|
resultArray = new FactorEntry128[index];
|
||||||
span.CopyTo(resultArray);
|
span.CopyTo(resultArray);
|
||||||
ArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
|
ThreadLocalArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fullyFactored = cofactor == UInt128.One;
|
bool fullyFactored = cofactor == UInt128.One;
|
||||||
@@ -1062,15 +1062,20 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
private readonly struct FactorEntry128
|
private readonly struct FactorEntry128
|
||||||
{
|
{
|
||||||
public FactorEntry128(UInt128 value, int exponent)
|
public FactorEntry128(UInt128 value)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Exponent = exponent;
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public UInt128 Value { get; }
|
public FactorEntry128(UInt128 value, int exponent)
|
||||||
|
{
|
||||||
|
Value = value;
|
||||||
|
Exponent = exponent;
|
||||||
|
}
|
||||||
|
|
||||||
public int Exponent { get; }
|
public readonly UInt128 Value;
|
||||||
|
|
||||||
|
public readonly int Exponent = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct PartialFactorResult128
|
private readonly struct PartialFactorResult128
|
||||||
@@ -1083,13 +1088,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
Count = count;
|
Count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FactorEntry128[]? Factors { get; }
|
public readonly FactorEntry128[]? Factors;
|
||||||
|
|
||||||
public UInt128 Cofactor { get; }
|
public readonly UInt128 Cofactor;
|
||||||
|
|
||||||
public bool FullyFactored { get; }
|
public readonly bool FullyFactored;
|
||||||
|
|
||||||
public int Count { get; }
|
public readonly int Count;
|
||||||
|
|
||||||
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
|
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
|
||||||
|
|
||||||
@@ -1097,14 +1102,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (Factors is null)
|
if (Factors is null)
|
||||||
{
|
{
|
||||||
FactorEntry128[] local = new FactorEntry128[1];
|
FactorEntry128[] local = [new FactorEntry128(prime)];
|
||||||
local[0] = new FactorEntry128(prime, 1);
|
return new PartialFactorResult128(local, UInt128.One, true, 1);
|
||||||
return new PartialFactorResult128(local, UInt128.One, true, 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry128[] extended = new FactorEntry128[Count + 1];
|
FactorEntry128[] extended = new FactorEntry128[Count + 1];
|
||||||
Array.Copy(Factors, extended, Count);
|
Array.Copy(Factors, extended, Count);
|
||||||
extended[Count] = new FactorEntry128(prime, 1);
|
extended[Count] = new FactorEntry128(prime);
|
||||||
return new PartialFactorResult128(extended, UInt128.One, true, Count + 1);
|
return new PartialFactorResult128(extended, UInt128.One, true, Count + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
internal readonly struct PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode)
|
internal readonly struct PrimeOrderSearchConfig(uint smallFactorLimit, int pollardRhoMilliseconds, int maxPowChecks, PrimeOrderMode mode)
|
||||||
{
|
{
|
||||||
public static PrimeOrderSearchConfig HeuristicDefault => new(smallFactorLimit: 100_000, pollardRhoMilliseconds: 24, maxPowChecks: 24, PrimeOrderMode.Heuristic);
|
public static PrimeOrderSearchConfig HeuristicDefault => new(smallFactorLimit: 4_000_000, pollardRhoMilliseconds: 24 * 256, maxPowChecks: 24, PrimeOrderMode.Heuristic);
|
||||||
public static PrimeOrderSearchConfig StrictDefault => new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict);
|
public static PrimeOrderSearchConfig StrictDefault => new(smallFactorLimit: 1_000_000, pollardRhoMilliseconds: 0, maxPowChecks: 0, PrimeOrderMode.Strict);
|
||||||
|
|
||||||
public readonly uint SmallFactorLimit = smallFactorLimit;
|
public readonly uint SmallFactorLimit = smallFactorLimit;
|
||||||
@@ -39,8 +39,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static bool s_allowGpuPow2;
|
private static bool s_allowGpuPow2;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static bool s_allowGpuCycle;
|
||||||
|
|
||||||
private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2;
|
private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2;
|
||||||
|
|
||||||
|
private static bool IsGpuCycleAllowed => s_pow2ModeInitialized && s_allowGpuCycle;
|
||||||
|
|
||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static PrimeOrderHeuristicDevice s_deviceMode;
|
private static PrimeOrderHeuristicDevice s_deviceMode;
|
||||||
|
|
||||||
@@ -56,27 +61,49 @@ internal static partial class PrimeOrderCalculator
|
|||||||
[ThreadStatic]
|
[ThreadStatic]
|
||||||
private static Dictionary<ulong, int>? s_partialFactorCounts;
|
private static Dictionary<ulong, int>? s_partialFactorCounts;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ulong s_cachedDivisorCycleModulus;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ulong s_cachedDivisorCycleLength;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static bool s_hasCachedDivisorCycle;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static int s_divisorCycleSuppressionDepth;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static MutableUInt128 s_pollardRhoPolynomialBuffer;
|
||||||
|
|
||||||
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
private static bool IsGpuHeuristicDevice => s_pow2ModeInitialized && s_deviceMode == PrimeOrderHeuristicDevice.Gpu;
|
||||||
|
|
||||||
private readonly struct Pow2ModeScope
|
private readonly struct Pow2ModeScope
|
||||||
{
|
{
|
||||||
private readonly bool _previousInitialized;
|
private readonly bool _previousInitialized;
|
||||||
private readonly bool _previousAllow;
|
private readonly bool _previousAllowPow2;
|
||||||
|
private readonly bool _previousAllowCycle;
|
||||||
private readonly PrimeOrderHeuristicDevice _previousDevice;
|
private readonly PrimeOrderHeuristicDevice _previousDevice;
|
||||||
|
|
||||||
public Pow2ModeScope(PrimeOrderHeuristicDevice device)
|
public Pow2ModeScope(PrimeOrderHeuristicDevice device)
|
||||||
{
|
{
|
||||||
_previousInitialized = s_pow2ModeInitialized;
|
_previousInitialized = s_pow2ModeInitialized;
|
||||||
_previousAllow = s_allowGpuPow2;
|
_previousAllowPow2 = s_allowGpuPow2;
|
||||||
|
_previousAllowCycle = s_allowGpuCycle;
|
||||||
_previousDevice = s_deviceMode;
|
_previousDevice = s_deviceMode;
|
||||||
s_pow2ModeInitialized = true;
|
s_pow2ModeInitialized = true;
|
||||||
s_allowGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu;
|
bool gpuAvailable = !GpuContextPool.ForceCpu;
|
||||||
|
bool allowCpuPow2 = gpuAvailable && device == PrimeOrderHeuristicDevice.Cpu;
|
||||||
|
bool allowCpuCycle = gpuAvailable && device == PrimeOrderHeuristicDevice.Cpu && GpuKernelPool.CpuCycleUsesGpu;
|
||||||
|
s_allowGpuPow2 = device == PrimeOrderHeuristicDevice.Gpu || allowCpuPow2;
|
||||||
|
s_allowGpuCycle = device == PrimeOrderHeuristicDevice.Gpu || allowCpuCycle;
|
||||||
s_deviceMode = device;
|
s_deviceMode = device;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
s_allowGpuPow2 = _previousAllow;
|
s_allowGpuPow2 = _previousAllowPow2;
|
||||||
|
s_allowGpuCycle = _previousAllowCycle;
|
||||||
s_pow2ModeInitialized = _previousInitialized;
|
s_pow2ModeInitialized = _previousInitialized;
|
||||||
s_deviceMode = _previousDevice;
|
s_deviceMode = _previousDevice;
|
||||||
}
|
}
|
||||||
@@ -98,10 +125,45 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsDivisorCycleSuppressed => s_divisorCycleSuppressionDepth > 0;
|
||||||
|
|
||||||
|
private readonly struct DivisorCycleSuppressionScope
|
||||||
|
{
|
||||||
|
private readonly bool _suppress;
|
||||||
|
|
||||||
|
private DivisorCycleSuppressionScope(bool suppress)
|
||||||
|
{
|
||||||
|
_suppress = suppress;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DivisorCycleSuppressionScope Suppressed = new(true);
|
||||||
|
|
||||||
|
public static readonly DivisorCycleSuppressionScope Unsuppressed = new(false);
|
||||||
|
|
||||||
|
public void Enter()
|
||||||
|
{
|
||||||
|
if (_suppress)
|
||||||
|
{
|
||||||
|
s_divisorCycleSuppressionDepth++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Exit()
|
||||||
|
{
|
||||||
|
if (_suppress)
|
||||||
|
{
|
||||||
|
s_divisorCycleSuppressionDepth--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static Pow2ModeScope UsePow2Mode(PrimeOrderHeuristicDevice device) => new(device);
|
private static Pow2ModeScope UsePow2Mode(PrimeOrderHeuristicDevice device) => new(device);
|
||||||
|
|
||||||
private static DebugLoggingScope UseDebugLogging(bool enabled) => new(enabled);
|
private static DebugLoggingScope UseDebugLogging(bool enabled) => new(enabled);
|
||||||
|
|
||||||
|
private static DivisorCycleSuppressionScope SuppressDivisorCycleUsage(bool suppress) =>
|
||||||
|
suppress ? DivisorCycleSuppressionScope.Suppressed : DivisorCycleSuppressionScope.Unsuppressed;
|
||||||
|
|
||||||
[Conditional("DEBUG")]
|
[Conditional("DEBUG")]
|
||||||
private static void DebugLog(string message)
|
private static void DebugLog(string message)
|
||||||
{
|
{
|
||||||
@@ -135,13 +197,16 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
private static ulong CalculateInternal(ulong prime, ulong? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (prime <= 3UL)
|
// EvenPerfectBitScanner only reaches this helper with divisors of the form 2 * exponent * k + 1 where exponent >= 5,
|
||||||
{
|
// so the small-prime return path is unreachable in production.
|
||||||
return prime == 3UL ? 2UL : 1UL;
|
// if (prime <= 3UL)
|
||||||
}
|
// {
|
||||||
|
// return prime == 3UL ? 2UL : 1UL;
|
||||||
|
// }
|
||||||
|
|
||||||
ulong phi = prime - 1UL;
|
ulong phi = prime - 1UL;
|
||||||
|
|
||||||
|
// The by-divisor scanner currently defaults to the CPU heuristic device, but --mersenne-device may request GPU heuristics.
|
||||||
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder, config, divisorData, out ulong gpuOrder))
|
if (IsGpuHeuristicDevice && PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder, config, divisorData, out ulong gpuOrder))
|
||||||
{
|
{
|
||||||
return gpuOrder;
|
return gpuOrder;
|
||||||
@@ -150,12 +215,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
PartialFactorResult phiFactors = PartialFactor(phi, config);
|
PartialFactorResult phiFactors = PartialFactor(phi, config);
|
||||||
|
|
||||||
ulong result;
|
ulong result;
|
||||||
if (phiFactors.Factors is null)
|
// The by-divisor scan always yields phi = prime - 1 with at least the factor 2 captured during small-prime sieving,
|
||||||
{
|
// so PartialFactor never returns a null factor list on this call path.
|
||||||
result = FinishStrictlyCpu(prime, divisorData);
|
// if (phiFactors.Factors is null)
|
||||||
phiFactors.Dispose();
|
// {
|
||||||
return result;
|
// result = CalculateByFactorizationCpu(prime, divisorData, config);
|
||||||
}
|
// phiFactors.Dispose();
|
||||||
|
// return result;
|
||||||
|
// }
|
||||||
|
|
||||||
result = RunHeuristicPipelineCpu(prime, previousOrder, config, divisorData, phi, phiFactors);
|
result = RunHeuristicPipelineCpu(prime, previousOrder, config, divisorData, phi, phiFactors);
|
||||||
phiFactors.Dispose();
|
phiFactors.Dispose();
|
||||||
@@ -170,35 +237,52 @@ internal static partial class PrimeOrderCalculator
|
|||||||
ulong phi,
|
ulong phi,
|
||||||
PartialFactorResult phiFactors)
|
PartialFactorResult phiFactors)
|
||||||
{
|
{
|
||||||
if (phiFactors.FullyFactored && TrySpecialMaxCpu(phi, prime, phiFactors, divisorData))
|
var cycleScope = SuppressDivisorCycleUsage(config.Mode == PrimeOrderMode.Heuristic && !IsGpuHeuristicDevice);
|
||||||
|
cycleScope.Enter();
|
||||||
|
try
|
||||||
{
|
{
|
||||||
return phi;
|
if (phiFactors.FullyFactored && TrySpecialMaxCpu(phi, prime, phiFactors, divisorData))
|
||||||
}
|
{
|
||||||
|
return phi;
|
||||||
|
}
|
||||||
|
|
||||||
ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData);
|
ulong candidateOrder = InitializeStartingOrderCpu(prime, phi, divisorData);
|
||||||
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData);
|
candidateOrder = ExponentLoweringCpu(candidateOrder, prime, phiFactors, divisorData);
|
||||||
|
|
||||||
if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, config))
|
PartialFactorResult orderFactors = PartialFactor(candidateOrder, config);
|
||||||
{
|
if (orderFactors.Factors is null)
|
||||||
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
|
return candidateOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse the computed factorization for both the confirmation check and the heuristic pass
|
||||||
|
// to avoid factoring the same order twice on the by-divisor CPU pipeline.
|
||||||
|
if (TryConfirmOrderCpu(prime, candidateOrder, divisorData, ref orderFactors))
|
||||||
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
|
return candidateOrder;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config.Mode == PrimeOrderMode.Strict)
|
||||||
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
|
return CalculateByFactorizationCpu(prime, divisorData, config);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (TryHeuristicFinishCpu(prime, candidateOrder, previousOrder, divisorData, config, ref orderFactors, phiFactors, out ulong order))
|
||||||
|
{
|
||||||
|
orderFactors.Dispose();
|
||||||
|
return order;
|
||||||
|
}
|
||||||
|
|
||||||
|
orderFactors.Dispose();
|
||||||
return candidateOrder;
|
return candidateOrder;
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
if (config.Mode == PrimeOrderMode.Strict)
|
|
||||||
{
|
{
|
||||||
return FinishStrictlyCpu(prime, divisorData);
|
cycleScope.Exit();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryHeuristicFinishCpu(prime, candidateOrder, previousOrder, divisorData, config, phiFactors, out ulong order))
|
|
||||||
{
|
|
||||||
return order;
|
|
||||||
}
|
|
||||||
|
|
||||||
return candidateOrder;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong FinishStrictlyCpu(ulong prime, in MontgomeryDivisorData divisorData)
|
|
||||||
{
|
|
||||||
return CalculateByFactorizationCpu(prime, divisorData);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
private static bool TrySpecialMaxCpu(ulong phi, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
||||||
@@ -233,31 +317,52 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<FactorEntry>? _factorPool;
|
||||||
|
private static ArrayPool<FactorEntry> FactorPool => _factorPool ??= ArrayPool<FactorEntry>.Create();
|
||||||
|
|
||||||
|
private const int FactorEntryStackThreshold = 32;
|
||||||
|
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<ulong>? _ulongPool;
|
||||||
|
private static ArrayPool<ulong> ULongPool => _ulongPool ??= ArrayPool<ulong>.Create();
|
||||||
|
|
||||||
private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
private static ulong ExponentLoweringCpu(ulong order, ulong prime, PartialFactorResult factors, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
|
ArrayPool<FactorEntry> factorPool = FactorPool;
|
||||||
|
|
||||||
FactorEntry[]? tempArray = null;
|
FactorEntry[]? tempArray = null;
|
||||||
|
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
||||||
|
int length = factors.Count;
|
||||||
|
int capacity = length + 1;
|
||||||
|
|
||||||
|
Span<FactorEntry> buffer = capacity <= FactorEntryStackThreshold ? stackalloc FactorEntry[capacity] : default;
|
||||||
|
if (capacity > FactorEntryStackThreshold)
|
||||||
|
{
|
||||||
|
tempArray = factorPool.Rent(capacity);
|
||||||
|
buffer = tempArray.AsSpan(0, capacity);
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ReadOnlySpan<FactorEntry> factorSpan = factors.Factors;
|
|
||||||
int length = factors.Count;
|
|
||||||
tempArray = ArrayPool<FactorEntry>.Shared.Rent(length + 1);
|
|
||||||
Span<FactorEntry> buffer = tempArray;
|
|
||||||
factorSpan.CopyTo(buffer);
|
factorSpan.CopyTo(buffer);
|
||||||
|
|
||||||
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factors.Cofactor);
|
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factors.Cofactor);
|
||||||
// bool isPrime = PrimeTester.IsPrimeInternal(factors.Cofactor, CancellationToken.None);
|
// bool isPrime = PrimeTester.IsPrimeInternal(factors.Cofactor, CancellationToken.None);
|
||||||
if (!factors.FullyFactored && factors.Cofactor > 1UL && isPrime)
|
if (!factors.FullyFactored && factors.Cofactor > 1UL && isPrime)
|
||||||
{
|
{
|
||||||
buffer[length] = new FactorEntry(factors.Cofactor, 1);
|
buffer[length] = new FactorEntry(factors.Cofactor);
|
||||||
length++;
|
length++;
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Slice(0, length).Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
buffer = buffer.Slice(0, length); // Reuse the same span restricted to populated entries.
|
||||||
|
buffer.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < buffer.Length; i++)
|
||||||
{
|
{
|
||||||
ulong primeFactor = buffer[i].Value;
|
FactorEntry entry = buffer[i];
|
||||||
int exponent = buffer[i].Exponent;
|
ulong primeFactor = entry.Value;
|
||||||
|
int exponent = entry.Exponent;
|
||||||
for (int iteration = 0; iteration < exponent; iteration++)
|
for (int iteration = 0; iteration < exponent; iteration++)
|
||||||
{
|
{
|
||||||
if ((order % primeFactor) != 0UL)
|
if ((order % primeFactor) != 0UL)
|
||||||
@@ -282,17 +387,18 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (tempArray is not null)
|
if (tempArray is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<FactorEntry>.Shared.Return(tempArray, clearArray: false);
|
factorPool.Return(tempArray, clearArray: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryConfirmOrderCpu(ulong prime, ulong order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
private static bool TryConfirmOrderCpu(ulong prime, ulong order, in MontgomeryDivisorData divisorData, ref PartialFactorResult orderFactors)
|
||||||
{
|
{
|
||||||
if (order == 0UL)
|
// The candidate order is always a positive divisor of phi in this flow, so zero never appears here.
|
||||||
{
|
// if (order == 0UL)
|
||||||
return false;
|
// {
|
||||||
}
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
// Calculating `a^order ≡ 1 (mod p)` is a prerequisite for `order` being the actual order of 2 modulo `p`.
|
||||||
// DebugLog("Verifying a^order ≡ 1 (mod p)");
|
// DebugLog("Verifying a^order ≡ 1 (mod p)");
|
||||||
@@ -301,66 +407,57 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLog("Partial factoring order");
|
// PartialFactor returns a null factor span when it cannot peel off any divisors, so keep the guard for composite candidate orders.
|
||||||
|
if (orderFactors.Factors is null)
|
||||||
// TODO: Do we do partial factoring of order multiple times?
|
|
||||||
PartialFactorResult factorization = PartialFactor(order, config);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (factorization.Factors is null)
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!orderFactors.FullyFactored)
|
||||||
|
{
|
||||||
|
// A leftover cofactor of 0 or 1 indicates the partial factorization produced no useful prime divisor.
|
||||||
|
if (orderFactors.Cofactor <= 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!factorization.FullyFactored)
|
// DebugLog("Cofactor > 1. Testing primality of cofactor");
|
||||||
|
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor);
|
||||||
|
// bool isPrime = PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None);
|
||||||
|
if (!isPrime)
|
||||||
{
|
{
|
||||||
if (factorization.Cofactor <= 1UL)
|
return false;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugLog("Cofactor > 1. Testing primality of cofactor");
|
|
||||||
bool isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(factorization.Cofactor);
|
|
||||||
// bool isPrime = PrimeTester.IsPrimeInternal(factorization.Cofactor, CancellationToken.None);
|
|
||||||
if (!isPrime)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// DebugLog("Adding cofactor as prime factor");
|
|
||||||
PartialFactorResult extended = factorization.WithAdditionalPrime(factorization.Cofactor);
|
|
||||||
factorization.Dispose();
|
|
||||||
factorization = extended;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<FactorEntry> span = factorization.Factors;
|
// DebugLog("Adding cofactor as prime factor");
|
||||||
// DebugLog("Verifying prime-power reductions");
|
PartialFactorResult extended = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
||||||
int length = factorization.Count;
|
orderFactors.Dispose();
|
||||||
for (int i = 0; i < length; i++)
|
orderFactors = extended;
|
||||||
{
|
|
||||||
ulong primeFactor = span[i].Value;
|
|
||||||
ulong reduced = order;
|
|
||||||
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
|
|
||||||
{
|
|
||||||
if ((reduced % primeFactor) != 0UL)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
reduced /= primeFactor;
|
|
||||||
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
|
ReadOnlySpan<FactorEntry> span = orderFactors.Factors!;
|
||||||
|
// DebugLog("Verifying prime-power reductions");
|
||||||
|
int length = orderFactors.Count;
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
factorization.Dispose();
|
ulong primeFactor = span[i].Value;
|
||||||
|
ulong reduced = order;
|
||||||
|
for (int iteration = 0; iteration < span[i].Exponent; iteration++)
|
||||||
|
{
|
||||||
|
if ((reduced % primeFactor) != 0UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
reduced /= primeFactor;
|
||||||
|
if (Pow2EqualsOneCpu(reduced, prime, divisorData))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryHeuristicFinishCpu(
|
private static bool TryHeuristicFinishCpu(
|
||||||
@@ -369,169 +466,164 @@ internal static partial class PrimeOrderCalculator
|
|||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
in PrimeOrderSearchConfig config,
|
in PrimeOrderSearchConfig config,
|
||||||
|
ref PartialFactorResult orderFactors,
|
||||||
PartialFactorResult phiFactors,
|
PartialFactorResult phiFactors,
|
||||||
out ulong result)
|
out ulong result)
|
||||||
{
|
{
|
||||||
result = 0UL;
|
result = 0UL;
|
||||||
if (order <= 1UL)
|
// The candidate order stays at least 2 for odd divisors above 3, so this guard never triggers in the EvenPerfectBitScanner flow.
|
||||||
|
// if (order <= 1UL)
|
||||||
|
// {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// PartialFactor returns a null factor span when it cannot peel off any divisors, so the heuristic pass must bail in that case.
|
||||||
|
if (orderFactors.Factors is null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Do we do partial factoring of order multiple times?
|
if (!orderFactors.FullyFactored)
|
||||||
// DebugLog("Trying heuristic. Partial factoring order");
|
|
||||||
PartialFactorResult orderFactors = PartialFactor(order, config);
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
if (orderFactors.Factors is null)
|
// The partial factoring helper reports a cofactor of 0 or 1 when nothing usable remains, so short-circuit the heuristic stage.
|
||||||
|
if (orderFactors.Cofactor <= 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!orderFactors.FullyFactored)
|
var isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor);
|
||||||
{
|
// if (!PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None))
|
||||||
if (orderFactors.Cofactor <= 1UL)
|
if (!isPrime)
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isPrime = Open.Numeric.Primes.Prime.Numbers.IsPrime(orderFactors.Cofactor);
|
|
||||||
// if (!PrimeTester.IsPrimeInternal(orderFactors.Cofactor, CancellationToken.None))
|
|
||||||
if (!isPrime)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
PartialFactorResult extended = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
|
||||||
orderFactors.Dispose();
|
|
||||||
orderFactors = extended;
|
|
||||||
}
|
|
||||||
|
|
||||||
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
|
|
||||||
List<ulong> candidates = new(capacity);
|
|
||||||
FactorEntry[] factorArray = orderFactors.Factors!;
|
|
||||||
// DebugLog("Building candidates list");
|
|
||||||
BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity);
|
|
||||||
if (candidates.Count == 0)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLog("Sorting candidates");
|
PartialFactorResult extended = orderFactors.WithAdditionalPrime(orderFactors.Cofactor);
|
||||||
SortCandidates(prime, previousOrder, candidates);
|
orderFactors.Dispose();
|
||||||
|
orderFactors = extended;
|
||||||
|
}
|
||||||
|
|
||||||
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
int capacity = config.MaxPowChecks <= 0 ? 64 : config.MaxPowChecks * 4;
|
||||||
int powUsed = 0;
|
List<ulong> candidates = new(capacity);
|
||||||
int candidateCount = candidates.Count;
|
FactorEntry[] factorArray = orderFactors.Factors!;
|
||||||
bool allowGpuBatch = true;
|
// DebugLog("Building candidates list");
|
||||||
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
BuildCandidates(order, factorArray, orderFactors.Count, candidates, capacity);
|
||||||
|
if (candidates.Count == 0)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
|
// DebugLog("Sorting candidates");
|
||||||
int index = 0;
|
SortCandidates(prime, previousOrder, candidates);
|
||||||
const int MaxGpuBatchSize = 1024;
|
|
||||||
const int StackGpuBatchSize = 1024;
|
int powBudget = config.MaxPowChecks <= 0 ? candidates.Count : config.MaxPowChecks;
|
||||||
Span<ulong> stackGpuRemainders = stackalloc ulong[StackGpuBatchSize];
|
int powUsed = 0;
|
||||||
while (index < candidateCount && powUsed < powBudget)
|
int candidateCount = candidates.Count;
|
||||||
|
bool allowGpuBatch = true;
|
||||||
|
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
|
||||||
|
|
||||||
|
// DebugLog(() => $"Checking candidates ({candidateCount} candidates, {powBudget} pow budget)");
|
||||||
|
int index = 0;
|
||||||
|
const int MaxGpuBatchSize = 1024;
|
||||||
|
const int StackGpuBatchSize = 1024;
|
||||||
|
Span<ulong> stackGpuRemainders = stackalloc ulong[StackGpuBatchSize];
|
||||||
|
ArrayPool<ulong> uLongPool = ULongPool;
|
||||||
|
while (index < candidateCount && powUsed < powBudget)
|
||||||
|
{
|
||||||
|
int remaining = candidateCount - index;
|
||||||
|
int budgetRemaining = powBudget - powUsed;
|
||||||
|
int batchSize = Math.Min(remaining, Math.Min(budgetRemaining, MaxGpuBatchSize));
|
||||||
|
if (batchSize <= 0)
|
||||||
{
|
{
|
||||||
int remaining = candidateCount - index;
|
break;
|
||||||
int budgetRemaining = powBudget - powUsed;
|
}
|
||||||
int batchSize = Math.Min(remaining, Math.Min(budgetRemaining, MaxGpuBatchSize));
|
|
||||||
if (batchSize <= 0)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
ReadOnlySpan<ulong> batch = candidateSpan.Slice(index, batchSize);
|
ReadOnlySpan<ulong> batch = candidateSpan.Slice(index, batchSize);
|
||||||
ulong[]? gpuPool = null;
|
ulong[]? gpuPool = null;
|
||||||
Span<ulong> pooledGpuRemainders = default;
|
Span<ulong> pooledGpuRemainders = default;
|
||||||
bool gpuSuccess = false;
|
bool gpuSuccess = false;
|
||||||
bool gpuStackRemainders = false;
|
bool gpuStackRemainders = false;
|
||||||
GpuPow2ModStatus status = GpuPow2ModStatus.Unavailable;
|
GpuPow2ModStatus status = GpuPow2ModStatus.Unavailable;
|
||||||
|
|
||||||
if (allowGpuBatch && IsGpuPow2Allowed)
|
if (allowGpuBatch && IsGpuPow2Allowed)
|
||||||
|
{
|
||||||
|
if (batchSize <= StackGpuBatchSize)
|
||||||
{
|
{
|
||||||
if (batchSize <= StackGpuBatchSize)
|
Span<ulong> localRemainders = stackGpuRemainders.Slice(0, batchSize);
|
||||||
|
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders, divisorData);
|
||||||
|
if (status == GpuPow2ModStatus.Success)
|
||||||
{
|
{
|
||||||
Span<ulong> localRemainders = stackGpuRemainders.Slice(0, batchSize);
|
gpuSuccess = true;
|
||||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, localRemainders, divisorData);
|
gpuStackRemainders = true;
|
||||||
if (status == GpuPow2ModStatus.Success)
|
}
|
||||||
{
|
}
|
||||||
gpuSuccess = true;
|
else
|
||||||
gpuStackRemainders = true;
|
{
|
||||||
}
|
gpuPool = uLongPool.Rent(batchSize);
|
||||||
|
Span<ulong> pooledRemainders = gpuPool.AsSpan(0, batchSize);
|
||||||
|
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders, divisorData);
|
||||||
|
if (status == GpuPow2ModStatus.Success)
|
||||||
|
{
|
||||||
|
pooledGpuRemainders = pooledRemainders;
|
||||||
|
gpuSuccess = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
gpuPool = ArrayPool<ulong>.Shared.Rent(batchSize);
|
uLongPool.Return(gpuPool, clearArray: false);
|
||||||
Span<ulong> pooledRemainders = gpuPool.AsSpan(0, batchSize);
|
gpuPool = null;
|
||||||
status = PrimeOrderGpuHeuristics.TryPow2ModBatch(batch, prime, pooledRemainders, divisorData);
|
|
||||||
if (status == GpuPow2ModStatus.Success)
|
|
||||||
{
|
|
||||||
pooledGpuRemainders = pooledRemainders;
|
|
||||||
gpuSuccess = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false);
|
|
||||||
gpuPool = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!gpuSuccess && (status == GpuPow2ModStatus.Overflow || status == GpuPow2ModStatus.Unavailable))
|
|
||||||
{
|
|
||||||
allowGpuBatch = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < batchSize && powUsed < powBudget; i++)
|
if (!gpuSuccess && (status == GpuPow2ModStatus.Overflow || status == GpuPow2ModStatus.Unavailable))
|
||||||
{
|
{
|
||||||
ulong candidate = batch[i];
|
allowGpuBatch = false;
|
||||||
powUsed++;
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool equalsOne;
|
for (int i = 0; i < batchSize && powUsed < powBudget; i++)
|
||||||
if (gpuSuccess)
|
{
|
||||||
{
|
ulong candidate = batch[i];
|
||||||
ulong remainderValue = gpuStackRemainders ? stackGpuRemainders[i] : pooledGpuRemainders[i];
|
powUsed++;
|
||||||
equalsOne = remainderValue == 1UL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData);
|
|
||||||
}
|
|
||||||
if (!equalsOne)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryConfirmCandidateCpu(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
bool equalsOne;
|
||||||
{
|
if (gpuSuccess)
|
||||||
continue;
|
{
|
||||||
}
|
ulong remainderValue = gpuStackRemainders ? stackGpuRemainders[i] : pooledGpuRemainders[i];
|
||||||
|
equalsOne = remainderValue == 1UL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
equalsOne = Pow2EqualsOneCpu(candidate, prime, divisorData);
|
||||||
|
}
|
||||||
|
if (!equalsOne)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (gpuPool is not null)
|
if (!TryConfirmCandidateCpu(prime, candidate, divisorData, config, ref powUsed, powBudget))
|
||||||
{
|
{
|
||||||
ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false);
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
result = candidate;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gpuPool is not null)
|
if (gpuPool is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<ulong>.Shared.Return(gpuPool, clearArray: false);
|
uLongPool.Return(gpuPool, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
index += batchSize;
|
result = candidate;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// DebugLog("No candidate confirmed");
|
if (gpuPool is not null)
|
||||||
return false;
|
{
|
||||||
}
|
uLongPool.Return(gpuPool, clearArray: false);
|
||||||
finally
|
}
|
||||||
{
|
|
||||||
orderFactors.Dispose();
|
index += batchSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DebugLog("No candidate confirmed");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates)
|
private static void SortCandidates(ulong prime, ulong? previousOrder, List<ulong> candidates)
|
||||||
@@ -765,6 +857,36 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryGetDivisorCycle(ulong prime, in MontgomeryDivisorData divisorData, out ulong cycleLength)
|
||||||
|
{
|
||||||
|
if (prime <= 1UL || IsDivisorCycleSuppressed)
|
||||||
|
{
|
||||||
|
cycleLength = 0UL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s_hasCachedDivisorCycle && s_cachedDivisorCycleModulus == prime)
|
||||||
|
{
|
||||||
|
cycleLength = s_cachedDivisorCycleLength;
|
||||||
|
return cycleLength != 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
var suppression = SuppressDivisorCycleUsage(true);
|
||||||
|
suppression.Enter();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
cycleLength = MersenneDivisorCycles.CalculateCycleLength(prime, divisorData);
|
||||||
|
s_cachedDivisorCycleModulus = prime;
|
||||||
|
s_cachedDivisorCycleLength = cycleLength;
|
||||||
|
s_hasCachedDivisorCycle = true;
|
||||||
|
return cycleLength != 0UL;
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
suppression.Exit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static bool Pow2EqualsOneCpu(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData)
|
private static bool Pow2EqualsOneCpu(ulong exponent, ulong prime, in MontgomeryDivisorData divisorData)
|
||||||
{
|
{
|
||||||
if (IsGpuPow2Allowed)
|
if (IsGpuPow2Allowed)
|
||||||
@@ -776,6 +898,11 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TryGetDivisorCycle(prime, divisorData, out ulong cycleLength) && cycleLength != 0UL)
|
||||||
|
{
|
||||||
|
return exponent.Pow2MontgomeryModWithCycleCpu(cycleLength, divisorData) == 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
return exponent.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -945,7 +1072,10 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
|
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry[] factors = ArrayPool<FactorEntry>.Shared.Rent(counts.Count);
|
int dictionaryCount = counts.Count;
|
||||||
|
FactorEntry[] factors = dictionaryCount >= PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(dictionaryCount)
|
||||||
|
: new FactorEntry[dictionaryCount];
|
||||||
int index = 0;
|
int index = 0;
|
||||||
foreach (KeyValuePair<ulong, int> entry in counts)
|
foreach (KeyValuePair<ulong, int> entry in counts)
|
||||||
{
|
{
|
||||||
@@ -979,7 +1109,9 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
|
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorEntry[] array = ArrayPool<FactorEntry>.Shared.Rent(actualCount);
|
FactorEntry[] array = actualCount >= PerfectNumberConstants.PooledArrayThreshold
|
||||||
|
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(actualCount)
|
||||||
|
: new FactorEntry[actualCount];
|
||||||
int arrayIndex = 0;
|
int arrayIndex = 0;
|
||||||
for (int i = 0; i < factorCount; i++)
|
for (int i = 0; i < factorCount; i++)
|
||||||
{
|
{
|
||||||
@@ -1193,8 +1325,11 @@ internal static partial class PrimeOrderCalculator
|
|||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong AdvancePolynomial(ulong x, ulong c, ulong modulus)
|
private static ulong AdvancePolynomial(ulong x, ulong c, ulong modulus)
|
||||||
{
|
{
|
||||||
UInt128 value = (UInt128)x * x + c;
|
ref MutableUInt128 polynomial = ref s_pollardRhoPolynomialBuffer;
|
||||||
return (ulong)(value % modulus);
|
polynomial.Set(x);
|
||||||
|
polynomial.Mul(x);
|
||||||
|
polynomial.Add(c);
|
||||||
|
return polynomial.Mod(modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong BinaryGcd(ulong a, ulong b)
|
private static ulong BinaryGcd(ulong a, ulong b)
|
||||||
@@ -1318,8 +1453,19 @@ internal static partial class PrimeOrderCalculator
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData)
|
private static ulong CalculateByFactorizationCpu(ulong prime, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
|
if (!IsGpuHeuristicDevice && IsGpuCycleAllowed)
|
||||||
|
{
|
||||||
|
PrimeOrderSearchConfig strictConfig = config.Mode == PrimeOrderMode.Strict
|
||||||
|
? config
|
||||||
|
: new PrimeOrderSearchConfig(config.SmallFactorLimit, config.PollardRhoMilliseconds, config.MaxPowChecks, PrimeOrderMode.Strict);
|
||||||
|
if (PrimeOrderGpuHeuristics.TryCalculateOrder(prime, previousOrder: null, strictConfig, divisorData, out ulong gpuOrder))
|
||||||
|
{
|
||||||
|
return gpuOrder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ulong phi = prime - 1UL;
|
ulong phi = prime - 1UL;
|
||||||
Dictionary<ulong, int> counts = new(capacity: 8);
|
Dictionary<ulong, int> counts = new(capacity: 8);
|
||||||
FactorCompletely(phi, counts);
|
FactorCompletely(phi, counts);
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
internal readonly struct PrimeTestTimeLimit
|
||||||
|
{
|
||||||
|
private readonly long _deadlineTimestamp;
|
||||||
|
|
||||||
|
private PrimeTestTimeLimit(bool isActive, long deadlineTimestamp)
|
||||||
|
{
|
||||||
|
IsActive = isActive;
|
||||||
|
_deadlineTimestamp = deadlineTimestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool IsActive { get; }
|
||||||
|
|
||||||
|
internal static bool TryCreate(TimeSpan? timeLimit, out PrimeTestTimeLimit limit)
|
||||||
|
{
|
||||||
|
if (!timeLimit.HasValue)
|
||||||
|
{
|
||||||
|
limit = new PrimeTestTimeLimit(isActive: false, deadlineTimestamp: long.MaxValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TimeSpan limitValue = timeLimit.GetValueOrDefault();
|
||||||
|
if (limitValue <= TimeSpan.Zero)
|
||||||
|
{
|
||||||
|
limit = new PrimeTestTimeLimit(isActive: true, deadlineTimestamp: long.MinValue);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
double seconds = limitValue.TotalSeconds;
|
||||||
|
if (double.IsInfinity(seconds))
|
||||||
|
{
|
||||||
|
limit = new PrimeTestTimeLimit(isActive: true, deadlineTimestamp: long.MaxValue);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double scaled = seconds * Stopwatch.Frequency;
|
||||||
|
long limitTicks = scaled >= long.MaxValue ? long.MaxValue : (long)scaled;
|
||||||
|
long startTimestamp = Stopwatch.GetTimestamp();
|
||||||
|
long deadline = startTimestamp <= long.MaxValue - limitTicks ? startTimestamp + limitTicks : long.MaxValue;
|
||||||
|
if (deadline < startTimestamp)
|
||||||
|
{
|
||||||
|
deadline = long.MaxValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
limit = new PrimeTestTimeLimit(isActive: true, deadlineTimestamp: deadline);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal bool HasExpired()
|
||||||
|
{
|
||||||
|
if (!IsActive)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_deadlineTimestamp == long.MaxValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_deadlineTimestamp == long.MinValue)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Stopwatch.GetTimestamp() >= _deadlineTimestamp;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
|
internal static class ThreadLocalArrayPool<T>
|
||||||
|
{
|
||||||
|
[ThreadStatic]
|
||||||
|
private static ArrayPool<T>? s_threadPool;
|
||||||
|
|
||||||
|
public static ArrayPool<T> Shared
|
||||||
|
{
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
get
|
||||||
|
{
|
||||||
|
ArrayPool<T>? pool = s_threadPool;
|
||||||
|
if (pool is null)
|
||||||
|
{
|
||||||
|
pool = ArrayPool<T>.Create();
|
||||||
|
s_threadPool = pool;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -310,6 +310,37 @@ public static class ULongExtensions
|
|||||||
return state.MulModWithNativeModulo(b, modulus);
|
return state.MulModWithNativeModulo(b, modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ulong Pow2ModBinaryGpu(this ulong exponent, ulong modulus)
|
||||||
|
{
|
||||||
|
if (modulus <= 1UL)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ulong remainingExponent = exponent;
|
||||||
|
ulong result = 1UL % modulus;
|
||||||
|
ulong baseValue = 2UL % modulus;
|
||||||
|
|
||||||
|
while (remainingExponent != 0UL)
|
||||||
|
{
|
||||||
|
if ((remainingExponent & 1UL) != 0UL)
|
||||||
|
{
|
||||||
|
result = result.MulMod64GpuCompatible(baseValue, modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
remainingExponent >>= 1;
|
||||||
|
if (remainingExponent == 0UL)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
baseValue = baseValue.MulMod64GpuCompatible(baseValue, modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong MontgomeryMultiply(this ulong a, ulong b, ulong modulus, ulong nPrime)
|
public static ulong MontgomeryMultiply(this ulong a, ulong b, ulong modulus, ulong nPrime)
|
||||||
{
|
{
|
||||||
@@ -346,48 +377,63 @@ public static class ULongExtensions
|
|||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
ulong[] oddPowers = InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowerCount);
|
|
||||||
|
Span<ulong> oddPowerStorage = oddPowerCount <= PerfectNumberConstants.MaxOddPowersCount ? stackalloc ulong[PerfectNumberConstants.MaxOddPowersCount] : default;
|
||||||
|
ulong[]? pooledOddPowers = null;
|
||||||
|
|
||||||
|
if (oddPowerCount < PerfectNumberConstants.PooledArrayThreshold)
|
||||||
|
{
|
||||||
|
oddPowerStorage = new ulong[oddPowerCount];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pooledOddPowers = ArrayPool<ulong>.Shared.Rent(oddPowerCount);
|
||||||
|
oddPowerStorage = pooledOddPowers;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<ulong> oddPowers = oddPowerStorage[..oddPowerCount];
|
||||||
|
InitializeMontgomeryOddPowersCpu(divisor, modulus, nPrime, oddPowers);
|
||||||
|
|
||||||
int index = bitLength - 1;
|
int index = bitLength - 1;
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
{
|
{
|
||||||
if (((exponent >> index) & 1UL) == 0UL)
|
if (((exponent >> index) & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
index--;
|
index--;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
int windowStart = index - windowSize + 1;
|
int windowStart = index - windowSize + 1;
|
||||||
if (windowStart < 0)
|
if (windowStart < 0)
|
||||||
{
|
{
|
||||||
windowStart = 0;
|
windowStart = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
while (((exponent >> windowStart) & 1UL) == 0UL)
|
||||||
{
|
{
|
||||||
windowStart++;
|
windowStart++;
|
||||||
}
|
}
|
||||||
|
|
||||||
int windowLength = index - windowStart + 1;
|
int windowLength = index - windowStart + 1;
|
||||||
for (int square = 0; square < windowLength; square++)
|
for (int square = 0; square < windowLength; square++)
|
||||||
{
|
{
|
||||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong mask = (1UL << windowLength) - 1UL;
|
ulong mask = (1UL << windowLength) - 1UL;
|
||||||
ulong windowValue = (exponent >> windowStart) & mask;
|
ulong windowValue = (exponent >> windowStart) & mask;
|
||||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
||||||
ulong multiplier = oddPowers[tableIndex];
|
ulong multiplier = oddPowers[tableIndex];
|
||||||
result = result.MontgomeryMultiply(multiplier, modulus, nPrime);
|
result = result.MontgomeryMultiply(multiplier, modulus, nPrime);
|
||||||
|
|
||||||
index = windowStart - 1;
|
index = windowStart - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oddPowerCount >= PerfectNumberConstants.PooledArrayThreshold)
|
if (pooledOddPowers is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<ulong>.Shared.Return(oddPowers);
|
ArrayPool<ulong>.Shared.Return(pooledOddPowers);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keepMontgomery)
|
if (keepMontgomery)
|
||||||
{
|
{
|
||||||
@@ -533,26 +579,34 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong[] InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
private static void InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, Span<ulong> destination)
|
||||||
{
|
{
|
||||||
ulong[] oddPowers = oddPowerCount < PerfectNumberConstants.PooledArrayThreshold ? new ulong[oddPowerCount] : ArrayPool<ulong>.Shared.Rent(oddPowerCount);
|
int oddPowerCount = destination.Length;
|
||||||
|
if (oddPowerCount == 0)
|
||||||
oddPowers[0] = divisor.MontgomeryTwo;
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool computedOnGpu = oddPowerCount <= PerfectNumberConstants.MaxOddPowersCount
|
||||||
|
&& MontgomeryOddPowerGpu.TryCompute(divisor, oddPowerCount, destination);
|
||||||
|
if (computedOnGpu)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
destination[0] = divisor.MontgomeryTwo;
|
||||||
if (oddPowerCount == 1)
|
if (oddPowerCount == 1)
|
||||||
{
|
{
|
||||||
return oddPowers;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong previous,
|
ulong square = divisor.MontgomeryTwoSquared;
|
||||||
square = divisor.MontgomeryTwoSquared;
|
ulong current = destination[0];
|
||||||
|
|
||||||
for (int i = 1; i < oddPowerCount; i++)
|
for (int i = 1; i < oddPowerCount; i++)
|
||||||
{
|
{
|
||||||
previous = oddPowers[i - 1];
|
current = current.MontgomeryMultiply(square, modulus, nPrime);
|
||||||
oddPowers[i] = previous.MontgomeryMultiply(square, modulus, nPrime);
|
destination[i] = current;
|
||||||
}
|
}
|
||||||
|
|
||||||
return oddPowers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# GPU Modular Arithmetic in the `--mersenne=bydivisor` Path
|
||||||
|
|
||||||
|
The GPU implementation now routes single-use exponent checks through standard modular arithmetic while keeping Montgomery reduction only where repeated multiplications reuse the same modulus.
|
||||||
|
|
||||||
|
## Kernels using standard modular arithmetic
|
||||||
|
|
||||||
|
- `CheckKernel` in `MersenneNumberDivisorByDivisorGpuTester` evaluates each divisor candidate with `Pow2ModBinaryGpu` and compares the result directly against 1. 【F:PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs†L561-L645】【F:PerfectNumbers.Core/ULongExtensions.cs†L314-L339】
|
||||||
|
- `ComputeExponentKernel` in the same tester precomputes batched residues with the plain helper so CPU lookups receive standard-domain results. 【F:PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs†L780-L791】【F:PerfectNumbers.Core/ULongExtensions.cs†L314-L339】
|
||||||
|
- `ComputeResiduesKernel` inside `MersenneNumberDivisorResidueGpuEvaluator` now mirrors that logic when building residue tables for mixed CPU/GPU scans. 【F:PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs†L102-L114】【F:PerfectNumbers.Core/ULongExtensions.cs†L314-L339】
|
||||||
|
- The order-search helpers in `PrimeOrderGpuHeuristics` (`TrySpecialMaxKernel`, `ExponentLoweringKernel`, and `Pow2EqualsOneKernel`) also rely on `Pow2ModBinaryGpu` for their equality checks. 【F:PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs†L798-L866】【F:PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs†L1596-L1598】
|
||||||
|
|
||||||
|
## Kernels still using Montgomery arithmetic
|
||||||
|
|
||||||
|
- `MontgomeryOddPowerKernelScan` in `GpuKernelPool` continues to build windowed odd-power tables with `MontgomeryMultiply`, which amortizes the Montgomery setup over many multiplications per modulus. 【F:PerfectNumbers.Core/Gpu/GpuKernelPool.cs†L200-L237】【F:PerfectNumbers.Core/ULongExtensions.cs†L532-L559】
|
||||||
|
|
||||||
|
## Shared helper
|
||||||
|
|
||||||
|
- `Pow2ModBinaryGpu` in `ULongExtensions` implements the GPU-friendly binary exponentiation loop that powers the standard modular path across these kernels. 【F:PerfectNumbers.Core/ULongExtensions.cs†L314-L339】
|
||||||
@@ -8,4 +8,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../PerfectNumbers.Core/PerfectNumbers.Core.csproj" />
|
<ProjectReference Include="../../PerfectNumbers.Core/PerfectNumbers.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
<ItemGroup>
|
||||||
|
<Compile Remove=".temp.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
Reference in new issue
Block a user