Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eae153e4a4 | ||
|
|
574e4b8007 |
No files matched your search
@@ -15,7 +15,4 @@
|
|||||||
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
<PackageReference Include="BenchmarkDotNet" Version="0.13.12" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove=".temp.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -50,7 +50,7 @@ public class GpuPow2ModBenchmarks
|
|||||||
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
return Pow2ModSingleBit(Input.Exponent, Input.Modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, GpuUInt128 modulus)
|
private static GpuUInt128 Pow2ModSingleBit(ulong exponent, in GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
if (modulus.IsZero || modulus == GpuUInt128.One)
|
if (modulus.IsZero || modulus == GpuUInt128.One)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -158,16 +158,36 @@ public class MulMod64Benchmarks
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Uses the production <see cref="ULongExtensions.MulMod64(ulong, ulong, ulong)"/> helper shared by CPU and GPU callers.
|
/// Uses the GPU-friendly <see cref="ULongExtensions.MulMod64GpuCompatible(ulong, ulong, ulong)"/> helper; dense inputs now
|
||||||
|
/// cost 24.4–61.9 ns, while NearFullRange and SparseOperands finish near 7.27 ns and ZeroOperands stay baseline-fast at
|
||||||
|
/// 3.59 ns.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Observed means prior to the helper consolidation: CrossWordBlend 24.422 ns, MixedBitPattern 61.850 ns, NearFullRange 7.267 ns,
|
/// Observed means: CrossWordBlend 24.422 ns (6.86×), MixedBitPattern 61.850 ns, NearFullRange 7.267 ns,
|
||||||
/// PrimeSizedModulus 55.114 ns, SparseOperands 7.261 ns, ZeroOperands 3.587 ns.
|
/// PrimeSizedModulus 55.114 ns, SparseOperands 7.261 ns, ZeroOperands 3.587 ns.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong UnifiedMulMod()
|
public ulong GpuCompatibleBaseline()
|
||||||
{
|
{
|
||||||
return Input.Left.MulMod64(Input.Right, Input.Modulus);
|
// TODO: Callers in production should migrate to ULongExtensions.MulMod64 where GPU parity
|
||||||
|
// is not required; that baseline stayed 6–17× faster for dense 64-bit operands.
|
||||||
|
return Input.Left.MulMod64GpuCompatible(Input.Right, Input.Modulus);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Uses the deferred native-modulo helper <see cref="ULongExtensions.MulMod64GpuCompatibleDeferred(ulong, ulong, ulong)"/>;
|
||||||
|
/// excels when operands are tiny (2.01 ns on ZeroOperands) yet remains the slowest choice on dense data at 146–295 ns.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// Observed means: CrossWordBlend 146.559 ns (40.63×), MixedBitPattern 255.555 ns, NearFullRange 21.390 ns,
|
||||||
|
/// PrimeSizedModulus 294.486 ns, SparseOperands 18.182 ns, ZeroOperands 2.009 ns.
|
||||||
|
/// </remarks>
|
||||||
|
[Benchmark]
|
||||||
|
public ulong GpuCompatibleDeferred()
|
||||||
|
{
|
||||||
|
// TODO: Retire the deferred GPU shim from runtime paths and keep it only for benchmarks;
|
||||||
|
// ULongExtensions.MulMod64 avoids the 6×–82× slowdown on real-world operand mixes.
|
||||||
|
return Input.Left.MulMod64GpuCompatibleDeferred(Input.Right, Input.Modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -1,75 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,100 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,75 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,31 +52,14 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
/// Baseline right-to-left Montgomery ladder; measured 36.36 μs on the small sample set and 117.32 μs on the large one.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark(Baseline = true)]
|
[Benchmark(Baseline = true)]
|
||||||
public ulong BaselineCpu()
|
public ulong Baseline()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out _);
|
GetData(out var exponents, out var divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Baseline right-to-left Montgomery ladder; measured ??.?? μs on the small sample set and ??.?? μs on the large one.
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
|
||||||
public ulong BaselineGpu()
|
|
||||||
{
|
|
||||||
GetData(out var exponents, out var divisors, out _);
|
|
||||||
ulong checksum = 0UL;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -86,31 +69,14 @@ public class Pow2MontgomeryModBenchmarks
|
|||||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong BaselineWithKnownCycleCpu()
|
public ulong BaselineWithKnownCycle()
|
||||||
{
|
{
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
GetData(out var exponents, out var divisors, out var cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Uses a known divisor cycle to fold the exponent first, dropping runtimes to 26.04 μs on small inputs (0.72× baseline) and 9.53 μs on large inputs (0.08× baseline).
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
|
||||||
public ulong BaselineWithKnownCycleGpu()
|
|
||||||
{
|
|
||||||
GetData(out var exponents, out var divisors, out var cycles);
|
|
||||||
ulong checksum = 0UL;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
|
|||||||
@@ -89,32 +89,14 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// large set.
|
/// large set.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark(Baseline = true)]
|
// [Benchmark(Baseline = true)]
|
||||||
public ulong MontgomeryWithoutCycleCpu()
|
public ulong MontgomeryWithoutCycle()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedCpu(divisors[i], keepMontgomery: false);
|
checksum ^= exponents[i].Pow2MontgomeryModWindowed(divisors[i], keepMontgomery: false);
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Baseline Montgomery reduction without precomputed cycle data; measured 35.41 μs on the small sample and 117.33 μs on the
|
|
||||||
/// large set.
|
|
||||||
/// </summary>
|
|
||||||
// [Benchmark(Baseline = true)]
|
|
||||||
public ulong MontgomeryWithoutCycleGpu()
|
|
||||||
{
|
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
|
||||||
ulong checksum = 0UL;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -125,32 +107,14 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// on the large one (0.08×).
|
/// on the large one (0.08×).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithPrecomputedCycleCpu()
|
public ulong MontgomeryWithPrecomputedCycle()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycles[i], divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycles[i], divisors[i]);
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Montgomery reduction with a known cycle length (cycle lookup only); 25.81 μs on the small set (0.73× baseline) and 9.12 μs
|
|
||||||
/// on the large one (0.08×).
|
|
||||||
/// </summary>
|
|
||||||
[Benchmark]
|
|
||||||
public ulong MontgomeryWithPrecomputedCycleGpu()
|
|
||||||
{
|
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out ulong[] cycles);
|
|
||||||
ulong checksum = 0UL;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycles[i], divisors[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -161,7 +125,7 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
// [Benchmark]
|
// [Benchmark]
|
||||||
public ulong MontgomeryWithGpuCycleComputationCpu()
|
public ulong MontgomeryWithGpuCycleComputation()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -169,33 +133,15 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Montgomery reduction with the cycle computed on the fly using the GPU helper; costs 54.09 ms on the small benchmark due to
|
|
||||||
/// cycle discovery, but drops to 12.91 μs on the large set (0.11× baseline).
|
|
||||||
/// </summary>
|
|
||||||
// [Benchmark]
|
|
||||||
public ulong MontgomeryWithGpuCycleComputationGpu()
|
|
||||||
{
|
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
|
||||||
ulong checksum = 0UL;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLengthGpu(divisors[i].Modulus);
|
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
[Benchmark]
|
||||||
public ulong MontgomeryWithHeuristicCycleComputationCpu()
|
public ulong MontgomeryWithHeuristicCycleComputation()
|
||||||
{
|
{
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
||||||
ulong checksum = 0UL;
|
ulong checksum = 0UL;
|
||||||
@@ -203,22 +149,7 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
for (int i = 0; i < SampleCount; i++)
|
for (int i = 0; i < SampleCount; i++)
|
||||||
{
|
{
|
||||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleCpu(cycle, divisors[i]);
|
checksum ^= exponents[i].Pow2MontgomeryModWithCycle(cycle, divisors[i]);
|
||||||
}
|
|
||||||
|
|
||||||
return checksum;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Benchmark]
|
|
||||||
public ulong MontgomeryWithHeuristicCycleComputationGpu()
|
|
||||||
{
|
|
||||||
GetData(out ulong[] exponents, out MontgomeryDivisorData[] divisors, out _);
|
|
||||||
ulong checksum = 0UL;
|
|
||||||
|
|
||||||
for (int i = 0; i < SampleCount; i++)
|
|
||||||
{
|
|
||||||
ulong cycle = CalculateCycleLengthWithHeuristics(divisors[i].Modulus);
|
|
||||||
checksum ^= exponents[i].Pow2MontgomeryModWithCycleGpu(cycle, divisors[i]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return checksum;
|
return checksum;
|
||||||
@@ -348,17 +279,16 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine("Trying heuristic. Prime order calculation");
|
Console.WriteLine("Trying heuristic. Prime order calculation");
|
||||||
#endif
|
#endif
|
||||||
ulong order = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
||||||
modulus,
|
modulus,
|
||||||
_previousPrimeOrder,
|
_previousPrimeOrder,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
if (order != 0UL)
|
if (orderResult.Order != 0UL)
|
||||||
{
|
{
|
||||||
_previousPrimeOrder = order;
|
_previousPrimeOrder = orderResult.Order;
|
||||||
return order;
|
return orderResult.Order;
|
||||||
}
|
}
|
||||||
|
|
||||||
_previousPrimeOrder = null;
|
_previousPrimeOrder = null;
|
||||||
@@ -377,16 +307,15 @@ public class Pow2MontgomeryModCycleComputationBenchmarks
|
|||||||
#if DEBUG
|
#if DEBUG
|
||||||
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
Console.WriteLine("Trying heuristic. Wide prime order calculation");
|
||||||
#endif
|
#endif
|
||||||
UInt128 order = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResultWide orderResult = PrimeOrderCalculator.Calculate(
|
||||||
modulus,
|
modulus,
|
||||||
_previousWidePrimeOrder,
|
_previousWidePrimeOrder,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
if (order != UInt128.Zero)
|
if (orderResult.Order != UInt128.Zero)
|
||||||
{
|
{
|
||||||
_previousWidePrimeOrder = order;
|
_previousWidePrimeOrder = orderResult.Order;
|
||||||
return order;
|
return orderResult.Order;
|
||||||
}
|
}
|
||||||
|
|
||||||
_previousWidePrimeOrder = null;
|
_previousWidePrimeOrder = null;
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return (UInt128)result;
|
return (UInt128)result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldUseSingleBit(GpuUInt128 exponent)
|
private static bool ShouldUseSingleBit(in GpuUInt128 exponent)
|
||||||
{
|
{
|
||||||
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
if (exponent.High == 0UL && exponent.Low <= Pow2WindowFallbackThreshold)
|
||||||
{
|
{
|
||||||
@@ -257,7 +257,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -267,7 +267,7 @@ public class Pow2MontgomeryModUInt128Benchmarks
|
|||||||
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
return ((value.Low >> bitIndex) & 1UL) != 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||||
{
|
{
|
||||||
if (windowStart != 0)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,22 +8,16 @@ public static class Program
|
|||||||
{
|
{
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
if (args.Length > 0)
|
// var config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
||||||
|
// BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
||||||
|
|
||||||
|
Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
||||||
{
|
{
|
||||||
IConfig config = DefaultConfig.Instance.WithOptions(ConfigOptions.JoinSummary | ConfigOptions.DisableOptimizationsValidator);
|
Scale = Pow2MontgomeryModCycleComputationBenchmarks.InputScale.Large
|
||||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args, config);
|
};
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run();
|
GpuPrimeWorkLimiter.SetLimit(1);
|
||||||
|
benchmarks.Setup();
|
||||||
// Pow2MontgomeryModCycleComputationBenchmarks benchmarks = new()
|
|
||||||
// {
|
|
||||||
// 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,7 +11,4 @@
|
|||||||
<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,7 +22,4 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../EvenPerfectBitScanner/EvenPerfectBitScanner.csproj" />
|
<ProjectReference Include="../EvenPerfectBitScanner/EvenPerfectBitScanner.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove=".temp.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -540,7 +540,7 @@ public class ProgramTests
|
|||||||
public void Main_displays_help_when_requested()
|
public void Main_displays_help_when_requested()
|
||||||
{
|
{
|
||||||
var main = typeof(Program).GetMethod("Main", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var main = typeof(Program).GetMethod("Main", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
var writer = new StringWriter();
|
using var writer = new StringWriter();
|
||||||
TextWriter original = Console.Out;
|
TextWriter original = Console.Out;
|
||||||
Console.SetOut(writer);
|
Console.SetOut(writer);
|
||||||
|
|
||||||
@@ -554,7 +554,6 @@ public class ProgramTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
string output = writer.ToString();
|
string output = writer.ToString();
|
||||||
writer.Dispose();
|
|
||||||
output.Should().Contain("Usage:");
|
output.Should().Contain("Usage:");
|
||||||
output.Should().Contain("--mersenne-device=cpu|gpu");
|
output.Should().Contain("--mersenne-device=cpu|gpu");
|
||||||
output.Should().Contain("--primes-device=cpu|gpu");
|
output.Should().Contain("--primes-device=cpu|gpu");
|
||||||
|
|||||||
@@ -16,7 +16,4 @@
|
|||||||
<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,7 +44,6 @@ 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;
|
||||||
@@ -75,8 +74,6 @@ 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
|
||||||
@@ -184,51 +181,6 @@ 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;
|
||||||
@@ -425,8 +377,6 @@ internal static class Program
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GpuKernelPool.CpuCycleUsesGpu = orderOnGpu;
|
|
||||||
|
|
||||||
if (useByDivisor)
|
if (useByDivisor)
|
||||||
{
|
{
|
||||||
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
|
_byDivisorStartPrime = startPrimeProvided ? currentP : 0UL;
|
||||||
@@ -567,11 +517,6 @@ 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)
|
||||||
@@ -720,21 +665,7 @@ internal static class Program
|
|||||||
|
|
||||||
if (_useByDivisorMode)
|
if (_useByDivisorMode)
|
||||||
{
|
{
|
||||||
if (_byDivisorTester is null)
|
RunByDivisorMode(byDivisorCandidates, threadCount);
|
||||||
{
|
|
||||||
throw new InvalidOperationException("By-divisor tester has not been initialized.");
|
|
||||||
}
|
|
||||||
|
|
||||||
MersenneNumberDivisorByDivisorTester.Run(
|
|
||||||
byDivisorCandidates,
|
|
||||||
_byDivisorTester,
|
|
||||||
_byDivisorPreviousResults,
|
|
||||||
_byDivisorStartPrime,
|
|
||||||
static () => _lastCompositeP = true,
|
|
||||||
static () => _lastCompositeP = false,
|
|
||||||
PrintResult,
|
|
||||||
threadCount,
|
|
||||||
_primeTestLimit);
|
|
||||||
FlushBuffer();
|
FlushBuffer();
|
||||||
StringBuilderPool.Return(_outputBuilder!);
|
StringBuilderPool.Return(_outputBuilder!);
|
||||||
return;
|
return;
|
||||||
@@ -875,6 +806,26 @@ internal static class Program
|
|||||||
return candidates;
|
return candidates;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RunByDivisorMode(List<ulong> candidates, int threadCount)
|
||||||
|
{
|
||||||
|
if (_byDivisorTester is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("By-divisor tester has not been initialized.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Inline this helper by invoking MersenneNumberDivisorByDivisorTester.Run directly at call sites so
|
||||||
|
// the CLI avoids this extra frame during the hot composite-reporting path.
|
||||||
|
MersenneNumberDivisorByDivisorTester.Run(
|
||||||
|
candidates,
|
||||||
|
_byDivisorTester,
|
||||||
|
_byDivisorPreviousResults,
|
||||||
|
_byDivisorStartPrime,
|
||||||
|
static () => _lastCompositeP = true,
|
||||||
|
static () => _lastCompositeP = false,
|
||||||
|
PrintResult,
|
||||||
|
threadCount);
|
||||||
|
}
|
||||||
|
|
||||||
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction)
|
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction)
|
||||||
{
|
{
|
||||||
using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
|
||||||
@@ -939,51 +890,6 @@ 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]");
|
||||||
@@ -1017,13 +923,10 @@ 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
|
||||||
Console.WriteLine(" --filter-p=<path> process only p from previous run results (required for --mersenne=bydivisor; primes must be strictly increasing)");
|
Console.WriteLine(" --filter-p=<path> process only p from previous run results (required for --mersenne=bydivisor)");
|
||||||
Console.WriteLine(" --write-batch-size=<value> overwrite frequency of disk writes (default 100 lines)");
|
Console.WriteLine(" --write-batch-size=<value> overwrite frequency of disk writes (default 100 lines)");
|
||||||
Console.WriteLine(" --gcd-filter enable early sieve based on GCD");
|
Console.WriteLine(" --gcd-filter enable early sieve based on GCD");
|
||||||
Console.WriteLine(" --help, -help, --?, -?, /? show this help message");
|
Console.WriteLine(" --help, -help, --?, -?, /? show this help message");
|
||||||
|
|||||||
@@ -15,15 +15,9 @@ public class GpuContextPoolTests
|
|||||||
{
|
{
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
try
|
|
||||||
{
|
|
||||||
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
lease.Accelerator.AcceleratorType.Should().Be(AcceleratorType.CPU);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
lease.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
@@ -36,27 +30,17 @@ public class GpuContextPoolTests
|
|||||||
|
|
||||||
Context firstContext;
|
Context firstContext;
|
||||||
Accelerator firstAccelerator;
|
Accelerator firstAccelerator;
|
||||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
firstContext = lease.Context;
|
firstContext = lease.Context;
|
||||||
firstAccelerator = lease.Accelerator;
|
firstAccelerator = lease.Accelerator;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
lease.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
lease.Context.Should().BeSameAs(firstContext);
|
lease.Context.Should().BeSameAs(firstContext);
|
||||||
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
lease.Accelerator.Should().BeSameAs(firstAccelerator);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
lease.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
@@ -68,27 +52,17 @@ public class GpuContextPoolTests
|
|||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
Context firstContext;
|
Context firstContext;
|
||||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
firstContext = lease.Context;
|
firstContext = lease.Context;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
lease.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
|
|
||||||
lease = GpuContextPool.RentPreferred(preferCpu: true);
|
using (var lease = GpuContextPool.RentPreferred(preferCpu: true))
|
||||||
try
|
|
||||||
{
|
{
|
||||||
lease.Context.Should().NotBeSameAs(firstContext);
|
lease.Context.Should().NotBeSameAs(firstContext);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
lease.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
GpuContextPool.DisposeAll();
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,18 +199,11 @@ public class GpuKernelCompilationTests
|
|||||||
[MemberData(nameof(KernelLoaders))]
|
[MemberData(nameof(KernelLoaders))]
|
||||||
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
public void Kernel_compiles_without_internal_compiler_errors(string name, Action<Accelerator> compile)
|
||||||
{
|
{
|
||||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
try
|
|
||||||
{
|
|
||||||
var accelerator = lease.Accelerator;
|
var accelerator = lease.Accelerator;
|
||||||
Action action = () => compile(accelerator);
|
Action action = () => compile(accelerator);
|
||||||
action.Should().NotThrow($"kernel {name} should compile");
|
action.Should().NotThrow($"kernel {name} should compile");
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
lease.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
private static void CompileDivisorCycleCacheKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,14 +18,13 @@ public class GpuPrimeLimiterTests
|
|||||||
var startedSecond = new TaskCompletionSource<bool>();
|
var startedSecond = new TaskCompletionSource<bool>();
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
|
|
||||||
var first = GpuPrimeWorkLimiter.Acquire();
|
using var first = GpuPrimeWorkLimiter.Acquire();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
|
||||||
var secondTask = Task.Run(() =>
|
var secondTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
var second = GpuPrimeWorkLimiter.Acquire();
|
using var second = GpuPrimeWorkLimiter.Acquire();
|
||||||
startedSecond.TrySetResult(true);
|
startedSecond.TrySetResult(true);
|
||||||
second.Dispose();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Give secondTask a moment to attempt acquire (it should block).
|
// Give secondTask a moment to attempt acquire (it should block).
|
||||||
|
|||||||
@@ -150,9 +150,7 @@ public class NttGpuMathTests
|
|||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
|
|
||||||
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
using var gpu = GpuContextPool.Rent();
|
||||||
try
|
|
||||||
{
|
|
||||||
var accelerator = gpu.Accelerator;
|
var accelerator = gpu.Accelerator;
|
||||||
|
|
||||||
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
|
||||||
@@ -177,11 +175,6 @@ public class NttGpuMathTests
|
|||||||
prod.Should().Be(r2);
|
prod.Should().Be(r2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
gpu.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
@@ -345,24 +338,13 @@ public class NttGpuMathTests
|
|||||||
values[1] = new GpuUInt128(0UL, 2UL);
|
values[1] = new GpuUInt128(0UL, 2UL);
|
||||||
var modulus = new GpuUInt128(0UL, 17UL);
|
var modulus = new GpuUInt128(0UL, 17UL);
|
||||||
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
var primitiveRoot = new GpuUInt128(0UL, 3UL);
|
||||||
GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
|
using (var gpu = GpuContextPool.Rent())
|
||||||
try
|
|
||||||
{
|
{
|
||||||
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
|
||||||
try
|
|
||||||
{
|
|
||||||
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
buffer.View.CopyFromCPU(ref values[0], values.Length);
|
||||||
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
buffer.Dispose();
|
buffer.Dispose();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
gpu.Dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify that the square cache now holds entries for the accelerator.
|
// Verify that the square cache now holds entries for the accelerator.
|
||||||
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
var field = typeof(NttGpuMath).GetField("SquareCache", BindingFlags.NonPublic | BindingFlags.Static)!;
|
||||||
|
|||||||
@@ -33,9 +33,9 @@ public class NttMontgomeryTests
|
|||||||
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
NttGpuMath.Square(cpu, modulus, primitiveRoot);
|
||||||
|
|
||||||
// Device execution
|
// Device execution
|
||||||
GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
|
using var context = GpuContextPool.Rent();
|
||||||
var accelerator = context.Accelerator;
|
var accelerator = context.Accelerator;
|
||||||
var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
|
||||||
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
|
||||||
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
|
NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
|
||||||
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
|
buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
|
||||||
@@ -44,9 +44,6 @@ public class NttMontgomeryTests
|
|||||||
{
|
{
|
||||||
gpu[i].Should().Be(cpu[i]);
|
gpu[i].Should().Be(cpu[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.Dispose();
|
|
||||||
context.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|||||||
@@ -359,9 +359,10 @@ public class PrimeOrderGpuHeuristicsTests
|
|||||||
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
PrimeOrderGpuHeuristics.OverflowRegistry[prime] = 0;
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(prime);
|
||||||
ulong result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault, PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(prime, null, divisorData, PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
|
|
||||||
result.Should().Be(3UL);
|
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||||
|
result.Order.Should().Be(3UL);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,9 +11,7 @@ public class GpuKernelPoolTests
|
|||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
public void EnsureSmallCyclesOnDevice_returns_same_view_on_subsequent_calls()
|
||||||
{
|
{
|
||||||
GpuContextPool.GpuContextLease lease = GpuContextPool.RentPreferred(preferCpu: true);
|
using var lease = GpuContextPool.RentPreferred(preferCpu: true);
|
||||||
try
|
|
||||||
{
|
|
||||||
var accelerator = lease.Accelerator;
|
var accelerator = lease.Accelerator;
|
||||||
|
|
||||||
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
var first = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
|
||||||
@@ -22,11 +20,6 @@ public class GpuKernelPoolTests
|
|||||||
second.Equals(first).Should().BeTrue();
|
second.Equals(first).Should().BeTrue();
|
||||||
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
second.Length.Should().Be(MersenneDivisorCycles.SmallDivisorsMax + 1);
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
lease.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
|
|||||||
@@ -17,14 +17,13 @@ public class GpuWorkLimiterTests
|
|||||||
var startedSecond = new TaskCompletionSource<bool>();
|
var startedSecond = new TaskCompletionSource<bool>();
|
||||||
var sw = new Stopwatch();
|
var sw = new Stopwatch();
|
||||||
|
|
||||||
var first = GpuWorkLimiter.Acquire();
|
using var first = GpuWorkLimiter.Acquire();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
|
||||||
var secondTask = Task.Run(() =>
|
var secondTask = Task.Run(() =>
|
||||||
{
|
{
|
||||||
var second = GpuWorkLimiter.Acquire();
|
using var second = GpuWorkLimiter.Acquire();
|
||||||
startedSecond.TrySetResult(true);
|
startedSecond.TrySetResult(true);
|
||||||
second.Dispose();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
await Task.Delay(50);
|
await Task.Delay(50);
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
|||||||
{
|
{
|
||||||
MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0);
|
||||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||||
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||||
var reader = new BinaryReader(stream);
|
using var reader = new BinaryReader(stream);
|
||||||
while (stream.Position < stream.Length)
|
while (stream.Position < stream.Length)
|
||||||
{
|
{
|
||||||
ulong d = reader.ReadUInt64();
|
ulong d = reader.ReadUInt64();
|
||||||
@@ -41,9 +41,6 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
|||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor);
|
||||||
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData));
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.Dispose();
|
|
||||||
stream.Dispose();
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@@ -64,8 +61,8 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
|||||||
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
|
MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4);
|
||||||
|
|
||||||
var pairs = new List<(ulong divisor, ulong cycle)>();
|
var pairs = new List<(ulong divisor, ulong cycle)>();
|
||||||
var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
using var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
|
||||||
var reader = new BinaryReader(stream);
|
using var reader = new BinaryReader(stream);
|
||||||
while (stream.Position < stream.Length)
|
while (stream.Position < stream.Length)
|
||||||
{
|
{
|
||||||
ulong divisor = reader.ReadUInt64();
|
ulong divisor = reader.ReadUInt64();
|
||||||
@@ -98,9 +95,6 @@ public class MersenneDivisorCyclesGenerateGpuTests
|
|||||||
|
|
||||||
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
|
var (_, completeCount) = MersenneDivisorCycles.FindLast(path);
|
||||||
completeCount.Should().Be(expected.Count);
|
completeCount.Should().Be(expected.Count);
|
||||||
|
|
||||||
reader.Dispose();
|
|
||||||
stream.Dispose();
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,8 +26,7 @@ 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);
|
||||||
@@ -60,7 +59,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
|
|||||||
return DivisorLimit;
|
return DivisorLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||||
{
|
{
|
||||||
divisorsExhausted = true;
|
divisorsExhausted = true;
|
||||||
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
return prime % 7UL != 0UL && prime % 11UL != 0UL;
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
@@ -8,125 +7,78 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
|
|
||||||
public class MersenneNumberDivisorCpuTesterTests
|
public class MersenneNumberDivisorCpuTesterTests
|
||||||
{
|
{
|
||||||
private const ulong LargeDivisor1 = 4_000_169UL;
|
|
||||||
private const ulong LargeDivisor2 = 4_000_561UL;
|
|
||||||
|
|
||||||
[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(83UL);
|
tester.ConfigureFromMaxPrime(11UL);
|
||||||
|
|
||||||
tester.IsPrime(59UL, out bool divisorsExhausted).Should().BeTrue();
|
tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
|
||||||
divisorsExhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
|
|
||||||
tester.IsPrime(61UL, out divisorsExhausted).Should().BeTrue();
|
tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
|
||||||
divisorsExhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
|
|
||||||
tester.IsPrime(67UL, out divisorsExhausted).Should().BeTrue();
|
tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
|
||||||
divisorsExhausted.Should().BeTrue();
|
divisorsExhausted.Should().BeTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_prime_test_limit_disables_divisor_exhaustion()
|
public void ByDivisor_session_checks_divisors_across_primes()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
tester.ConfigureFromMaxPrime(83UL);
|
tester.ConfigureFromMaxPrime(13UL);
|
||||||
|
|
||||||
tester.IsPrime(59UL, out bool divisorsExhausted, TimeSpan.Zero).Should().BeTrue();
|
using var session = tester.CreateDivisorSession();
|
||||||
divisorsExhausted.Should().BeFalse();
|
ulong[] primes = { 5UL, 7UL, 11UL, 13UL };
|
||||||
}
|
byte[] hits = new byte[primes.Length];
|
||||||
|
|
||||||
[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_checks_divisors_across_exponents()
|
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||||
{
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
|
||||||
ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL, 333_380UL };
|
|
||||||
byte[] hits = new byte[exponents.Length];
|
|
||||||
|
|
||||||
MontgomeryDivisorData divisor1 = MontgomeryDivisorData.FromModulus(LargeDivisor1);
|
|
||||||
ulong cycle1 = MersenneDivisorCycles.CalculateCycleLength(LargeDivisor1, divisor1);
|
|
||||||
session.CheckDivisor(LargeDivisor1, divisor1, cycle1, exponents, hits);
|
|
||||||
|
|
||||||
byte[] expected1 = new byte[exponents.Length];
|
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
expected1[i] = exponents[i].Pow2ModWindowedCpu(divisor1.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected1);
|
|
||||||
|
|
||||||
Array.Fill(hits, (byte)0);
|
Array.Fill(hits, (byte)0);
|
||||||
MontgomeryDivisorData divisor2 = MontgomeryDivisorData.FromModulus(LargeDivisor2);
|
ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL));
|
||||||
ulong cycle2 = MersenneDivisorCycles.CalculateCycleLength(LargeDivisor2, divisor2);
|
session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits);
|
||||||
session.CheckDivisor(LargeDivisor2, divisor2, cycle2, exponents, hits);
|
hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 });
|
||||||
|
|
||||||
byte[] expected2 = new byte[exponents.Length];
|
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
expected2[i] = exponents[i].Pow2ModWindowedCpu(divisor2.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected2);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_large_divisor1_as_composite()
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
using var session = tester.CreateDivisorSession();
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(LargeDivisor1);
|
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(LargeDivisor1, divisorData);
|
|
||||||
|
|
||||||
ulong[] exponents = { 59UL, 67UL, cycle, cycle * 2UL, cycle + 1UL };
|
|
||||||
byte[] hits = new byte[exponents.Length];
|
byte[] hits = new byte[exponents.Length];
|
||||||
session.CheckDivisor(LargeDivisor1, divisorData, cycle, exponents, hits);
|
|
||||||
|
|
||||||
byte[] expected = new byte[exponents.Length];
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||||
{
|
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||||
expected[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected);
|
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_large_divisor2_as_composite()
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
var tester = new MersenneNumberDivisorByDivisorCpuTester();
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(61UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
using var session = tester.CreateDivisorSession();
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(LargeDivisor2);
|
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(LargeDivisor2, divisorData);
|
|
||||||
|
|
||||||
ulong[] exponents = { 61UL, 73UL, cycle, cycle * 2UL, cycle - 1UL };
|
|
||||||
byte[] hits = new byte[exponents.Length];
|
byte[] hits = new byte[exponents.Length];
|
||||||
session.CheckDivisor(LargeDivisor2, divisorData, cycle, exponents, hits);
|
|
||||||
|
|
||||||
byte[] expected = new byte[exponents.Length];
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||||
{
|
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||||
expected[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected);
|
hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12,12 +12,12 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
public class MersenneNumberDivisorGpuTesterTests
|
public class MersenneNumberDivisorGpuTesterTests
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData(117_652UL, 4_000_169UL, true)]
|
[InlineData(33UL, 7UL, true)]
|
||||||
[InlineData(333_380UL, 4_000_561UL, true)]
|
[InlineData(35UL, 31UL, true)]
|
||||||
[InlineData(59UL, 4_000_169UL, false)]
|
[InlineData(37UL, 223UL, true)]
|
||||||
[InlineData(61UL, 4_000_561UL, false)]
|
[InlineData(33UL, 13UL, false)]
|
||||||
[InlineData(333_379UL, 4_000_561UL, false)]
|
[InlineData(35UL, 73UL, false)]
|
||||||
[InlineData(117_653UL, 4_000_169UL, false)]
|
[InlineData(37UL, 227UL, false)]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
public void IsDivisible_returns_expected(ulong exponent, ulong divisor, bool expected)
|
||||||
{
|
{
|
||||||
@@ -86,17 +86,6 @@ 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()
|
||||||
@@ -152,41 +141,23 @@ public class MersenneNumberDivisorGpuTesterTests
|
|||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_session_checks_divisors_across_exponents()
|
public void ByDivisor_session_checks_divisors_across_primes()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
using var session = tester.CreateDivisorSession();
|
||||||
ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL, 333_380UL };
|
ulong[] primes = { 31UL, 37UL, 41UL, 43UL };
|
||||||
byte[] hits = new byte[exponents.Length];
|
byte[] hits = new byte[primes.Length];
|
||||||
|
|
||||||
MontgomeryDivisorData divisor1 = MontgomeryDivisorData.FromModulus(4_000_169UL);
|
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||||
ulong cycle1 = MersenneDivisorCycles.CalculateCycleLength(4_000_169UL, divisor1);
|
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||||
session.CheckDivisor(4_000_169UL, divisor1, cycle1, exponents, hits);
|
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 });
|
||||||
|
|
||||||
byte[] expected1 = new byte[exponents.Length];
|
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
expected1[i] = exponents[i].Pow2ModWindowedCpu(divisor1.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected1);
|
|
||||||
|
|
||||||
Array.Fill(hits, (byte)0);
|
Array.Fill(hits, (byte)0);
|
||||||
MontgomeryDivisorData divisor2 = MontgomeryDivisorData.FromModulus(4_000_561UL);
|
ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL));
|
||||||
ulong cycle2 = MersenneDivisorCycles.CalculateCycleLength(4_000_561UL, divisor2);
|
session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits);
|
||||||
session.CheckDivisor(4_000_561UL, divisor2, cycle2, exponents, hits);
|
hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 });
|
||||||
|
|
||||||
byte[] expected2 = new byte[exponents.Length];
|
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
expected2[i] = exponents[i].Pow2ModWindowedCpu(divisor2.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected2);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -198,134 +169,122 @@ public class MersenneNumberDivisorGpuTesterTests
|
|||||||
GpuBatchSize = 2,
|
GpuBatchSize = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(47UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
using var session = tester.CreateDivisorSession();
|
||||||
ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL };
|
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||||
byte[] hits = new byte[exponents.Length];
|
byte[] hits = new byte[primes.Length];
|
||||||
|
|
||||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(4_000_169UL);
|
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(4_000_169UL, divisor);
|
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||||
session.CheckDivisor(4_000_169UL, divisor, cycle, exponents, hits);
|
|
||||||
|
|
||||||
byte[] expected = new byte[exponents.Length];
|
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
expected[i] = exponents[i].Pow2ModWindowedCpu(divisor.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_large_divisor1_as_composite()
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_seven_as_composite()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||||
{
|
{
|
||||||
GpuBatchSize = 5,
|
GpuBatchSize = 5,
|
||||||
};
|
};
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(43UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
using var session = tester.CreateDivisorSession();
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(4_000_169UL);
|
ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL };
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(4_000_169UL, divisorData);
|
|
||||||
|
|
||||||
ulong[] exponents = { 59UL, 67UL, cycle, cycle * 2UL, cycle + 1UL };
|
|
||||||
byte[] hits = new byte[exponents.Length];
|
byte[] hits = new byte[exponents.Length];
|
||||||
|
|
||||||
session.CheckDivisor(4_000_169UL, divisorData, cycle, exponents, hits);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL);
|
||||||
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData);
|
||||||
|
|
||||||
|
session.CheckDivisor(7UL, divisorData, cycle, exponents, hits);
|
||||||
|
|
||||||
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
||||||
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
||||||
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
||||||
byte[] expected = new byte[exponents.Length];
|
byte[] computedHits = new byte[exponents.Length];
|
||||||
for (int i = 0; i < residues.Length; i++)
|
for (int i = 0; i < residues.Length; i++)
|
||||||
{
|
{
|
||||||
expected[i] = residues[i] == 1UL ? (byte)1 : (byte)0;
|
computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] direct = new byte[exponents.Length];
|
computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
direct[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
expected.Should().Equal(direct);
|
|
||||||
hits.Should().Equal(direct);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_large_divisor2_as_composite()
|
public void ByDivisor_session_marks_mersenne_numbers_divisible_by_eleven_as_composite()
|
||||||
{
|
{
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
var tester = new MersenneNumberDivisorByDivisorGpuTester
|
||||||
{
|
{
|
||||||
GpuBatchSize = 5,
|
GpuBatchSize = 5,
|
||||||
};
|
};
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(61UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
using var session = tester.CreateDivisorSession();
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(4_000_561UL);
|
ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL };
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(4_000_561UL, divisorData);
|
|
||||||
|
|
||||||
ulong[] exponents = { 61UL, 73UL, cycle, cycle * 2UL, cycle - 1UL };
|
|
||||||
byte[] hits = new byte[exponents.Length];
|
byte[] hits = new byte[exponents.Length];
|
||||||
|
|
||||||
session.CheckDivisor(4_000_561UL, divisorData, cycle, exponents, hits);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL);
|
||||||
|
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData);
|
||||||
|
|
||||||
|
session.CheckDivisor(11UL, divisorData, cycle, exponents, hits);
|
||||||
|
|
||||||
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
var hostBufferField = typeof(MersenneNumberDivisorByDivisorGpuTester.DivisorScanSession)
|
||||||
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
.GetField("_hostBuffer", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
ulong[] hostBuffer = (ulong[])hostBufferField.GetValue(session)!;
|
||||||
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
Span<ulong> residues = hostBuffer.AsSpan(0, exponents.Length);
|
||||||
byte[] expected = new byte[exponents.Length];
|
byte[] computedHits = new byte[exponents.Length];
|
||||||
for (int i = 0; i < residues.Length; i++)
|
for (int i = 0; i < residues.Length; i++)
|
||||||
{
|
{
|
||||||
expected[i] = residues[i] == 1UL ? (byte)1 : (byte)0;
|
computedHits[i] = residues[i] == divisorData.MontgomeryOne ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] direct = new byte[exponents.Length];
|
computedHits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 });
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
direct[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
expected.Should().Equal(direct);
|
|
||||||
hits.Should().Equal(direct);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void ByDivisor_gpu_tester_generates_cycles_for_large_divisors()
|
public void ByDivisor_gpu_tester_skips_divisors_excluded_by_small_cycle_generation()
|
||||||
{
|
{
|
||||||
|
var cycles = MersenneDivisorCycles.Shared;
|
||||||
|
var tableField = typeof(MersenneDivisorCycles).GetField("_table", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
|
var smallCyclesField = typeof(MersenneDivisorCycles).GetField("_smallCycles", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||||
|
|
||||||
|
var originalTable = (List<(ulong Divisor, ulong Cycle)>)tableField.GetValue(cycles)!;
|
||||||
|
var originalSmall = (ulong[]?)smallCyclesField.GetValue(cycles);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var patchedTable = new List<(ulong Divisor, ulong Cycle)>();
|
||||||
|
ulong[] patchedSmall = new ulong[PerfectNumberConstants.MaxQForDivisorCycles + 1];
|
||||||
|
|
||||||
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(191UL);
|
||||||
|
patchedSmall[191] = MersenneDivisorCycles.CalculateCycleLength(191UL, divisorData);
|
||||||
|
|
||||||
|
tableField.SetValue(cycles, patchedTable);
|
||||||
|
smallCyclesField.SetValue(cycles, patchedSmall);
|
||||||
|
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||||
|
|
||||||
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
var tester = new MersenneNumberDivisorByDivisorGpuTester();
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(19UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
typeof(MersenneNumberDivisorByDivisorGpuTester)
|
||||||
ulong[] exponents = { 59UL, 67UL, 117_652UL };
|
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
|
||||||
byte[] hits = new byte[exponents.Length];
|
.SetValue(tester, 200UL);
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(4_000_169UL);
|
tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue();
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(4_000_169UL, divisorData);
|
divisorsExhausted.Should().BeTrue();
|
||||||
|
}
|
||||||
session.CheckDivisor(4_000_169UL, divisorData, cycle, exponents, hits);
|
finally
|
||||||
|
|
||||||
byte[] expected = new byte[exponents.Length];
|
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
{
|
||||||
expected[i] = exponents[i].Pow2ModWindowedCpu(divisorData.Modulus) == 1UL ? (byte)1 : (byte)0;
|
tableField.SetValue(cycles, originalTable);
|
||||||
|
smallCyclesField.SetValue(cycles, originalSmall);
|
||||||
|
DivisorCycleCache.Shared.RefreshSnapshot();
|
||||||
|
GpuContextPool.DisposeAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
hits.Should().Equal(expected);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -337,25 +296,16 @@ public class MersenneNumberDivisorGpuTesterTests
|
|||||||
BatchSize = 2,
|
BatchSize = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
tester.ConfigureFromMaxPrime(197UL);
|
tester.ConfigureFromMaxPrime(47UL);
|
||||||
|
|
||||||
var session = tester.CreateDivisorSession();
|
using var session = tester.CreateDivisorSession();
|
||||||
ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL };
|
ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL };
|
||||||
byte[] hits = new byte[exponents.Length];
|
byte[] hits = new byte[primes.Length];
|
||||||
|
|
||||||
MontgomeryDivisorData divisor = MontgomeryDivisorData.FromModulus(4_000_169UL);
|
ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL));
|
||||||
ulong cycle = MersenneDivisorCycles.CalculateCycleLength(4_000_169UL, divisor);
|
session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits);
|
||||||
session.CheckDivisor(4_000_169UL, divisor, cycle, exponents, hits);
|
|
||||||
|
|
||||||
byte[] expected = new byte[exponents.Length];
|
hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 });
|
||||||
for (int i = 0; i < exponents.Length; i++)
|
|
||||||
{
|
|
||||||
expected[i] = exponents[i].Pow2ModWindowedCpu(divisor.Modulus) == 1UL ? (byte)1 : (byte)0;
|
|
||||||
}
|
|
||||||
|
|
||||||
hits.Should().Equal(expected);
|
|
||||||
|
|
||||||
session.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,7 +22,4 @@
|
|||||||
<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,9 +7,6 @@ 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 };
|
||||||
@@ -48,14 +45,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
ulong expected = ComputeOrderByDoubling(prime);
|
ulong expected = ComputeOrderByDoubling(prime);
|
||||||
|
|
||||||
ulong result = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
result.Should().Be(expected);
|
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||||
|
result.Order.Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -65,21 +62,21 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
ulong previousOrder = ComputeOrderByDoubling(previousPrime);
|
||||||
|
|
||||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder,
|
previousOrder,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
ulong strict = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.StrictDefault);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
heuristic.Should().Be(strict);
|
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||||
|
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||||
|
heuristic.Order.Should().Be(strict.Order);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -88,14 +85,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
[InlineData(3UL, 2UL)]
|
[InlineData(3UL, 2UL)]
|
||||||
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
public void Calculate_handles_trivial_primes(ulong prime, ulong expectedOrder)
|
||||||
{
|
{
|
||||||
ulong result = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(prime),
|
MontgomeryDivisorData.FromModulus(prime),
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
result.Should().Be(expectedOrder);
|
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||||
|
result.Order.Should().Be(expectedOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -108,14 +105,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
maxPowChecks: 8,
|
maxPowChecks: 8,
|
||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
|
|
||||||
ulong result = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult result = PrimeOrderCalculator.Calculate(
|
||||||
prime: 13UL,
|
prime: 13UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
MontgomeryDivisorData.FromModulus(13UL),
|
MontgomeryDivisorData.FromModulus(13UL),
|
||||||
config,
|
config);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
result.Should().Be(ComputeOrderByDoubling(13UL));
|
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
||||||
|
result.Order.Should().Be(ComputeOrderByDoubling(13UL));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -129,14 +126,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Heuristic);
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(239UL);
|
||||||
ulong heuristic = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult heuristic = PrimeOrderCalculator.Calculate(
|
||||||
prime: 239UL,
|
prime: 239UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
heuristicConfig,
|
heuristicConfig);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
heuristic.Should().Be(ComputeOrderByDoubling(239UL));
|
heuristic.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved);
|
||||||
|
heuristic.Order.Should().Be(ComputeOrderByDoubling(239UL));
|
||||||
|
|
||||||
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
var strictConfig = new PrimeOrderCalculator.PrimeOrderSearchConfig(
|
||||||
smallFactorLimit: 2,
|
smallFactorLimit: 2,
|
||||||
@@ -144,14 +141,14 @@ public class PrimeOrderCalculatorTests
|
|||||||
maxPowChecks: 1,
|
maxPowChecks: 1,
|
||||||
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
mode: PrimeOrderCalculator.PrimeOrderMode.Strict);
|
||||||
|
|
||||||
ulong strict = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult strict = PrimeOrderCalculator.Calculate(
|
||||||
prime: 239UL,
|
prime: 239UL,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
strictConfig,
|
strictConfig);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
strict.Should().Be(heuristic);
|
strict.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||||
|
strict.Order.Should().Be(heuristic.Order);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -160,13 +157,13 @@ public class PrimeOrderCalculatorTests
|
|||||||
{
|
{
|
||||||
UInt128 prime = UInt128.Parse("18446744073709641691");
|
UInt128 prime = UInt128.Parse("18446744073709641691");
|
||||||
|
|
||||||
UInt128 result = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResultWide result = PrimeOrderCalculator.Calculate(
|
||||||
prime,
|
prime,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault);
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Gpu);
|
|
||||||
|
|
||||||
result.Should().Be((UInt128)1229782938247309446UL);
|
result.Status.Should().Be(PrimeOrderCalculator.PrimeOrderStatus.Found);
|
||||||
|
result.Order.Should().Be((UInt128)1229782938247309446UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@@ -200,5 +197,4 @@ public class PrimeOrderCalculatorTests
|
|||||||
|
|
||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
public enum ByDivisorDeltasDevice
|
|
||||||
{
|
|
||||||
Cpu,
|
|
||||||
Gpu
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
public enum ByDivisorMontgomeryDevice
|
|
||||||
{
|
|
||||||
Cpu = 0,
|
|
||||||
Gpu = 1,
|
|
||||||
}
|
|
||||||
@@ -1,91 +1,78 @@
|
|||||||
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 System.Threading;
|
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Cpu;
|
namespace PerfectNumbers.Core.Cpu;
|
||||||
|
|
||||||
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
|
||||||
{
|
{
|
||||||
|
private readonly object _sync = new();
|
||||||
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new();
|
||||||
private ulong _divisorLimit;
|
private ulong _divisorLimit;
|
||||||
private int _lastStatusDivisor;
|
private ulong _lastStatusDivisor;
|
||||||
private bool _isConfigured;
|
private bool _isConfigured;
|
||||||
private int _batchSize = 1_024;
|
private int _batchSize = 1_024;
|
||||||
private ByDivisorDeltasDevice _deltasDevice = ByDivisorDeltasDevice.Cpu;
|
|
||||||
private ByDivisorMontgomeryDevice _montgomeryDevice = ByDivisorMontgomeryDevice.Cpu;
|
|
||||||
|
|
||||||
public int BatchSize
|
public int BatchSize
|
||||||
{
|
{
|
||||||
get => _batchSize;
|
get => _batchSize;
|
||||||
set
|
set => _batchSize = Math.Max(1, value);
|
||||||
{
|
|
||||||
int sanitized = Math.Max(1, value);
|
|
||||||
_batchSize = sanitized;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public ByDivisorDeltasDevice DeltasDevice
|
|
||||||
{
|
|
||||||
get => _deltasDevice;
|
|
||||||
set => _deltasDevice = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ByDivisorMontgomeryDevice MontgomeryDevice
|
|
||||||
{
|
|
||||||
get => _montgomeryDevice;
|
|
||||||
set => _montgomeryDevice = value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
{
|
{
|
||||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||||
_lastStatusDivisor = 0;
|
_lastStatusDivisor = 0UL;
|
||||||
_isConfigured = true;
|
_isConfigured = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// The by-divisor driver configures the tester during startup, so exposing the divisor limit never
|
lock (_sync)
|
||||||
// re-enters this method before initialization completes.
|
{
|
||||||
// if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
// {
|
{
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
// }
|
}
|
||||||
|
|
||||||
return _divisorLimit;
|
return _divisorLimit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||||
{
|
{
|
||||||
// The CLI wires the tester through ConfigureFromMaxPrime before invoking query helpers, so the guard
|
lock (_sync)
|
||||||
// would never trigger in production runs.
|
{
|
||||||
// if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
// {
|
{
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
// }
|
}
|
||||||
|
|
||||||
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||||
{
|
{
|
||||||
// The tester is configured once during startup, so runtime IsPrime calls always observe a ready
|
ulong allowedMax;
|
||||||
// configuration and the legacy guard can remain commented out.
|
lock (_sync)
|
||||||
// if (!_isConfigured)
|
{
|
||||||
// {
|
if (!_isConfigured)
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
{
|
||||||
// }
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
}
|
||||||
|
|
||||||
ulong divisorLimit = _divisorLimit;
|
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
ulong allowedMax = ComputeAllowedMaxDivisor(prime, divisorLimit);
|
}
|
||||||
|
|
||||||
if (allowedMax < 3UL)
|
if (allowedMax < 3UL)
|
||||||
{
|
{
|
||||||
@@ -93,22 +80,24 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong processedCount;
|
ulong processedCount = 0UL;
|
||||||
ulong lastProcessed;
|
ulong lastProcessed = 0UL;
|
||||||
bool processedAll;
|
bool processedAll = false;
|
||||||
|
|
||||||
bool composite = CheckDivisors(
|
bool composite = CheckDivisors(
|
||||||
prime,
|
prime,
|
||||||
allowedMax,
|
allowedMax,
|
||||||
out lastProcessed,
|
out lastProcessed,
|
||||||
out processedAll,
|
out processedAll,
|
||||||
out processedCount,
|
out processedCount);
|
||||||
timeLimit);
|
|
||||||
|
|
||||||
if (processedCount > 0UL)
|
if (processedCount > 0UL)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
{
|
{
|
||||||
UpdateStatusUnsafe(processedCount);
|
UpdateStatusUnsafe(processedCount);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (composite)
|
if (composite)
|
||||||
{
|
{
|
||||||
@@ -122,21 +111,22 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
|
||||||
{
|
{
|
||||||
// EvenPerfectBitScanner always provides equally sized spans here, so the historical guard never triggers.
|
if (allowedMaxValues.Length < primes.Length)
|
||||||
// if (allowedMaxValues.Length < primes.Length)
|
{
|
||||||
// {
|
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
||||||
// throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
// PrepareCandidates is scheduled only after ConfigureFromMaxPrime succeeds, so the tester state is
|
ulong divisorLimit;
|
||||||
// stable here and the defensive guard is no longer needed.
|
|
||||||
// if (!_isConfigured)
|
|
||||||
// {
|
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Configuration is immutable after startup, so reading the cached limit does not require locking.
|
lock (_sync)
|
||||||
ulong divisorLimit = _divisorLimit;
|
{
|
||||||
|
if (!_isConfigured)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
}
|
||||||
|
|
||||||
|
divisorLimit = _divisorLimit;
|
||||||
|
}
|
||||||
|
|
||||||
for (int index = 0; index < primes.Length; index++)
|
for (int index = 0; index < primes.Length; index++)
|
||||||
{
|
{
|
||||||
@@ -146,12 +136,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
||||||
{
|
{
|
||||||
// The session pool is populated only after configuration, so consumers never request a session before
|
lock (_sync)
|
||||||
// ConfigureFromMaxPrime completes.
|
{
|
||||||
// if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
// {
|
{
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
// }
|
}
|
||||||
|
|
||||||
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
||||||
{
|
{
|
||||||
@@ -161,6 +151,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
return new DivisorScanSession(this);
|
return new DivisorScanSession(this);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ReturnSession(DivisorScanSession session)
|
private void ReturnSession(DivisorScanSession session)
|
||||||
{
|
{
|
||||||
@@ -172,50 +163,23 @@ 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 step = (UInt128)prime << 1;
|
UInt128 step = (UInt128)prime << 1;
|
||||||
// EvenPerfectBitScanner only supplies primes greater than one here, so the shifted step never becomes zero.
|
if (step == UInt128.Zero)
|
||||||
// if (step == UInt128.Zero)
|
{
|
||||||
// {
|
processedAll = true;
|
||||||
// processedAll = true;
|
return false;
|
||||||
// return false;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
UInt128 limit = allowedMax;
|
UInt128 limit = allowedMax;
|
||||||
UInt128 divisor = step + UInt128.One;
|
UInt128 divisor = step + UInt128.One;
|
||||||
@@ -248,13 +212,6 @@ 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;
|
||||||
@@ -265,10 +222,7 @@ 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)
|
||||||
{
|
{
|
||||||
// Each divisor appears only once in this monotonically increasing sequence, so
|
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
|
||||||
// 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)
|
||||||
@@ -278,9 +232,6 @@ 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);
|
||||||
@@ -298,7 +249,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
if (divisorCycle == 0UL)
|
if (divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
// Keep this guard active to surface mismatches between generated candidates and the snapshot cache.
|
|
||||||
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
Console.WriteLine($"Divisor cycle was not calculated for {prime}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -316,252 +266,9 @@ 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);
|
|
||||||
// Primes above one yield a non-zero 2p value even after widening to GpuUInt128, so the defensive
|
|
||||||
// zero guard remains commented out to document the invariant without branching.
|
|
||||||
// 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;
|
|
||||||
// batchCapacity and remainingCandidates are strictly positive in this path, so the zero-count
|
|
||||||
// shortcut never executes on production scans.
|
|
||||||
// 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.Pow2MontgomeryModWithCycle(divisorCycle, divisorData);
|
||||||
return residue == 1UL ? (byte)1 : (byte)0;
|
return residue == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -583,34 +290,24 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int interval = PerfectNumberConstants.ConsoleInterval;
|
ulong interval = PerfectNumberConstants.ConsoleInterval;
|
||||||
// PerfectNumberConstants.ConsoleInterval is a positive constant, so this guard never triggers on EvenPerfectBitScanner.
|
if (interval == 0UL)
|
||||||
// if (interval <= 0)
|
|
||||||
// {
|
|
||||||
// Volatile.Write(ref _lastStatusDivisor, 0);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int processedDelta = (int)(processedCount % (uint)interval);
|
|
||||||
if (processedDelta == 0)
|
|
||||||
{
|
{
|
||||||
|
_lastStatusDivisor = 0UL;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true)
|
ulong total = _lastStatusDivisor + processedCount;
|
||||||
{
|
|
||||||
int previous = Volatile.Read(ref _lastStatusDivisor);
|
|
||||||
int total = previous + processedDelta;
|
|
||||||
if (total >= interval)
|
if (total >= interval)
|
||||||
|
{
|
||||||
|
do
|
||||||
{
|
{
|
||||||
total -= interval;
|
total -= interval;
|
||||||
}
|
}
|
||||||
|
while (total >= interval);
|
||||||
|
}
|
||||||
|
|
||||||
if (previous == Interlocked.CompareExchange(ref _lastStatusDivisor, total, previous))
|
_lastStatusDivisor = total;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||||
@@ -630,34 +327,23 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
||||||
{
|
{
|
||||||
// The by-divisor scanner never emits exponents below the first odd prime, so the defensive guard stays commented out to
|
if (prime <= 1UL)
|
||||||
// avoid branching on production workloads.
|
{
|
||||||
// if (prime <= 1UL)
|
return 0UL;
|
||||||
// {
|
}
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Production exponents always satisfy prime - 1 >= 64, so the legacy shift-based clamp would overflow on the hot path.
|
|
||||||
// Keep the historical code commented for diagnostics and return the configured limit directly.
|
|
||||||
// if (prime - 1UL >= 64UL)
|
|
||||||
// {
|
|
||||||
// return divisorLimit;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
|
||||||
|
|
||||||
|
if (prime - 1UL >= 64UL)
|
||||||
|
{
|
||||||
return divisorLimit;
|
return divisorLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||||
|
}
|
||||||
|
|
||||||
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
private sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
{
|
{
|
||||||
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)
|
||||||
{
|
{
|
||||||
@@ -677,30 +363,16 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
int length = primes.Length;
|
int length = primes.Length;
|
||||||
// The production scheduler batches at least one exponent per divisor, so the legacy empty-span shortcut remains
|
if (length == 0)
|
||||||
// commented to avoid branching on the hot path.
|
|
||||||
// if (length == 0)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
MontgomeryDivisorData effectiveDivisorData = divisorData;
|
|
||||||
if (effectiveDivisorData.Modulus != divisor)
|
|
||||||
{
|
{
|
||||||
// Each divisor surfaces only once across the monotonic scan, so rebuild the Montgomery data
|
return;
|
||||||
// instead of retaining a cache entry that would never be reused within future checks.
|
|
||||||
effectiveDivisorData = MontgomeryDivisorData.FromModulus(divisor);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong modulus = effectiveDivisorData.Modulus;
|
MontgomeryDivisorData cachedData = divisorData;
|
||||||
// EvenPerfectBitScanner only schedules odd divisors greater than one, and the fallback
|
if (cachedData.Modulus != divisor)
|
||||||
// above rebuilds Montgomery data when the modulus mismatches, so this guard never
|
{
|
||||||
// triggers for production scans.
|
cachedData = MontgomeryDivisorDataCache.Get(divisor);
|
||||||
// if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
}
|
||||||
// {
|
|
||||||
// hits.Clear();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (divisorCycle == 0UL)
|
if (divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
@@ -712,24 +384,9 @@ 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(divisorData);
|
var exponentStepper = new ExponentRemainderStepper(cachedData);
|
||||||
if (!exponentStepper.IsValidModulus)
|
if (!exponentStepper.IsValidModulus)
|
||||||
{
|
{
|
||||||
hits.Clear();
|
hits.Clear();
|
||||||
@@ -743,7 +400,6 @@ 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]);
|
||||||
@@ -757,169 +413,6 @@ 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)
|
|
||||||
{
|
|
||||||
// Prime batches are never empty on the by-divisor scanner path, so the zero-length guard stays commented
|
|
||||||
// out to remove the redundant branch.
|
|
||||||
// if (requiredLength <= 0)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Sessions release pooled buffers between scans; a single null check covers every array because buffers are rented and returned together.
|
|
||||||
if (requiredLength <= _bufferCapacity && _primeDeltas is not null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int newCapacity = _bufferCapacity;
|
|
||||||
if (newCapacity == 0)
|
|
||||||
{
|
|
||||||
// EvenPerfectBitScanner hits this branch on the first batch of each session, so keep the seed allocation.
|
|
||||||
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()
|
|
||||||
{
|
|
||||||
// Buffers are lazily allocated per session; the _primeDeltas guard applies to every pooled array because sessions rent and release them together.
|
|
||||||
if (_primeDeltas is null)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ArrayPool<ulong>.Shared.Return(_primeDeltas, clearArray: false);
|
|
||||||
ArrayPool<ulong>.Shared.Return(_cycleRemainders!, clearArray: false);
|
|
||||||
ArrayPool<ulong>.Shared.Return(_residues!, clearArray: false);
|
|
||||||
_primeDeltas = null;
|
|
||||||
_cycleRemainders = null;
|
|
||||||
_residues = null;
|
|
||||||
_bufferCapacity = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ComputePrimeDeltas(ReadOnlySpan<ulong> primes, Span<ulong> deltas)
|
|
||||||
{
|
|
||||||
// Prime batches on the scanning path are never empty, so the legacy guard stays commented to remove the extra branch.
|
|
||||||
// if (primes.Length == 0)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong previous = primes[0];
|
|
||||||
deltas[0] = previous;
|
|
||||||
|
|
||||||
// Production batches feed strictly increasing primes; callers outside the scanning path must uphold the contract.
|
|
||||||
for (int i = 1; i < primes.Length; i++)
|
|
||||||
{
|
|
||||||
ulong current = primes[i];
|
|
||||||
deltas[i] = current - previous;
|
|
||||||
previous = current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void ComputeCycleRemainders(ulong cycleLength, ReadOnlySpan<ulong> primes, ReadOnlySpan<ulong> deltas, Span<ulong> remainders)
|
|
||||||
{
|
|
||||||
// Divisor cycles are validated before reaching the GPU helper, and prime batches are non-empty, so
|
|
||||||
// the defensive zero checks remain commented out here.
|
|
||||||
// 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)
|
|
||||||
{
|
|
||||||
// A zero modulus indicates a missing cycle entry; preserve the fallback so snapshot regressions remain visible.
|
|
||||||
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)
|
||||||
@@ -928,7 +421,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
_disposed = true;
|
_disposed = true;
|
||||||
ReleaseGpuEvaluator();
|
|
||||||
_owner.ReturnSession(this);
|
_owner.ReturnSession(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Threading;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
|
||||||
|
|
||||||
internal static class DeterministicRandom
|
|
||||||
{
|
|
||||||
[ThreadStatic]
|
|
||||||
private static ulong s_threadState;
|
|
||||||
|
|
||||||
private static long s_seedCounter;
|
|
||||||
|
|
||||||
public static ulong NextUInt64()
|
|
||||||
{
|
|
||||||
ulong state = s_threadState;
|
|
||||||
if (state == 0UL)
|
|
||||||
{
|
|
||||||
state = InitializeThreadState();
|
|
||||||
}
|
|
||||||
|
|
||||||
state ^= state >> 12;
|
|
||||||
state ^= state << 25;
|
|
||||||
state ^= state >> 27;
|
|
||||||
s_threadState = state;
|
|
||||||
return state * 2685821657736338717UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UInt128 NextUInt128()
|
|
||||||
{
|
|
||||||
UInt128 high = (UInt128)NextUInt64();
|
|
||||||
UInt128 low = (UInt128)NextUInt64();
|
|
||||||
return (high << 64) | low;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ulong InitializeThreadState()
|
|
||||||
{
|
|
||||||
ulong seed = (ulong)Interlocked.Increment(ref s_seedCounter);
|
|
||||||
seed += 0x9E3779B97F4A7C15UL;
|
|
||||||
seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL;
|
|
||||||
seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL;
|
|
||||||
seed ^= seed >> 31;
|
|
||||||
if (seed == 0UL)
|
|
||||||
{
|
|
||||||
seed = 0x9E3779B97F4A7C15UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
return seed;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -63,9 +63,7 @@ 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]
|
||||||
: length < PerfectNumberConstants.PooledArrayThreshold
|
: new Span<int>(rentedMissing = ArrayPool<int>.Shared.Rent(length), 0, length);
|
||||||
? new Span<int>(new int[length])
|
|
||||||
: new Span<int>(rentedMissing = ThreadLocalArrayPool<int>.Shared.Rent(length), 0, length);
|
|
||||||
|
|
||||||
int missingCount = 0;
|
int missingCount = 0;
|
||||||
|
|
||||||
@@ -86,11 +84,12 @@ public sealed class DivisorCycleCache
|
|||||||
}
|
}
|
||||||
|
|
||||||
cycles[i] = cached;
|
cycles[i] = cached;
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
missingBuffer[missingCount++] = i;
|
missingBuffer[missingCount++] = i;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (missingCount != 0)
|
if (missingCount != 0)
|
||||||
{
|
{
|
||||||
@@ -103,12 +102,11 @@ public sealed class DivisorCycleCache
|
|||||||
{
|
{
|
||||||
ComputeCyclesCpu(divisors, cycles, missingIndices);
|
ComputeCyclesCpu(divisors, cycles, missingIndices);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rentedMissing is not null)
|
if (rentedMissing is not null)
|
||||||
{
|
{
|
||||||
ThreadLocalArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
|
ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -130,16 +128,12 @@ 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]
|
||||||
: count < PerfectNumberConstants.PooledArrayThreshold
|
: new Span<ulong>(rentedDivisors = ArrayPool<ulong>.Shared.Rent(count), 0, count);
|
||||||
? 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]
|
||||||
: count < PerfectNumberConstants.PooledArrayThreshold
|
: new Span<ulong>(rentedResults = ArrayPool<ulong>.Shared.Rent(count), 0, count);
|
||||||
? 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++)
|
||||||
{
|
{
|
||||||
@@ -155,12 +149,8 @@ public sealed class DivisorCycleCache
|
|||||||
|
|
||||||
if (rentedDivisors is not null)
|
if (rentedDivisors is not null)
|
||||||
{
|
{
|
||||||
ThreadLocalArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
|
ArrayPool<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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,30 +172,22 @@ 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]
|
||||||
: length < PerfectNumberConstants.PooledArrayThreshold
|
: new Span<ulong>(rentedPow = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||||
? 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]
|
||||||
: length < PerfectNumberConstants.PooledArrayThreshold
|
: new Span<ulong>(rentedOrder = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||||
? 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]
|
||||||
: length < PerfectNumberConstants.PooledArrayThreshold
|
: new Span<ulong>(rentedResult = ArrayPool<ulong>.Shared.Rent(length), 0, length);
|
||||||
? 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]
|
||||||
: length < PerfectNumberConstants.PooledArrayThreshold
|
: new Span<byte>(rentedStatus = ArrayPool<byte>.Shared.Rent(length), 0, length);
|
||||||
? 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);
|
||||||
@@ -252,22 +234,10 @@ public sealed class DivisorCycleCache
|
|||||||
resultSpan.CopyTo(destination);
|
resultSpan.CopyTo(destination);
|
||||||
if (rentedPow is not null)
|
if (rentedPow is not null)
|
||||||
{
|
{
|
||||||
ThreadLocalArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
|
ArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
|
||||||
}
|
ArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false);
|
||||||
|
ArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false);
|
||||||
if (rentedOrder is not null)
|
ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false);
|
||||||
{
|
|
||||||
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();
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
// TODO: Route these state resets through the ProcessEightBitWindows helper once the scalar
|
||||||
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
// Pow2MontgomeryMod implementation adopts it so delta stepping inherits the benchmarked
|
||||||
// 2× gains for large exponents.
|
// 2× gains for large exponents.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -59,7 +59,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
// cached Montgomery residue ladder highlighted in MersenneDivisorCycleLengthGpuBenchmarks.
|
||||||
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
// TODO: Replace this per-delta powmod with the upcoming windowed ladder so incremental
|
||||||
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
|
// updates stop paying the single-bit cost highlighted in GpuPow2ModBenchmarks.
|
||||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return ReduceCurrent();
|
return ReduceCurrent();
|
||||||
@@ -77,7 +77,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
// TODO: Switch this reload to the shared windowed pow2 helper once available so CPU
|
||||||
// residue checks align with the optimized ProcessEightBitWindows timings.
|
// residue checks align with the optimized ProcessEightBitWindows timings.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -88,7 +88,7 @@ internal struct ExponentRemainderStepper
|
|||||||
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
// lookups so this branch also avoids recomputing powmods when the snapshot lacks the divisor.
|
||||||
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
// TODO: Use the windowed delta pow2 helper here as well to avoid the single-bit ladder that
|
||||||
// currently lags behind the benchmarked implementation for huge divisor cycles.
|
// currently lags behind the benchmarked implementation for huge divisor cycles.
|
||||||
ulong multiplier = delta.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
ulong multiplier = delta.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||||
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
_currentMontgomery = _currentMontgomery.MontgomeryMultiply(multiplier, _modulus, _nPrime);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
return _currentMontgomery == _montgomeryOne;
|
return _currentMontgomery == _montgomeryOne;
|
||||||
@@ -123,7 +123,7 @@ internal struct ExponentRemainderStepper
|
|||||||
{
|
{
|
||||||
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
// TODO: Replace this fallback path with the upcoming ProcessEightBitWindows helper so
|
||||||
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
|
// fresh Montgomery states also benefit from the faster pow2 ladder measured on CPUs.
|
||||||
_currentMontgomery = exponent.Pow2MontgomeryModWindowedCpu(_divisor, keepMontgomery: true);
|
_currentMontgomery = exponent.Pow2MontgomeryModWindowed(_divisor, keepMontgomery: true);
|
||||||
_previousExponent = exponent;
|
_previousExponent = exponent;
|
||||||
_hasState = true;
|
_hasState = true;
|
||||||
isUnity = _currentMontgomery == _montgomeryOne;
|
isUnity = _currentMontgomery == _montgomeryOne;
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
namespace PerfectNumbers.Core
|
|
||||||
{
|
|
||||||
public readonly struct FactorEntry
|
|
||||||
{
|
|
||||||
public readonly ulong Value;
|
|
||||||
public readonly int Exponent = 1;
|
|
||||||
|
|
||||||
public FactorEntry(ulong value, int exponent)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
Exponent = exponent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FactorEntry(ulong value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -126,7 +126,7 @@ public static class GpuContextPool
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct GpuContextLease
|
public struct GpuContextLease : IDisposable
|
||||||
{
|
{
|
||||||
private readonly PooledContext _ctx;
|
private readonly PooledContext _ctx;
|
||||||
private bool disposed;
|
private bool disposed;
|
||||||
@@ -145,7 +145,8 @@ public static class GpuContextPool
|
|||||||
|
|
||||||
public object ExecutionLock => _ctx.ExecutionLock;
|
public object ExecutionLock => _ctx.ExecutionLock;
|
||||||
|
|
||||||
internal void ReturnToPool()
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (disposed)
|
if (disposed)
|
||||||
{
|
{
|
||||||
@@ -155,10 +156,6 @@ public static class GpuContextPool
|
|||||||
Return(_ctx);
|
Return(_ctx);
|
||||||
disposed = true;
|
disposed = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
ReturnToPool();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -37,7 +37,6 @@ 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;
|
||||||
@@ -76,10 +75,10 @@ public readonly struct ResiduePrimeViews(
|
|||||||
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
|
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels)
|
public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels)
|
||||||
{
|
{
|
||||||
private bool disposedValue;
|
private bool disposedValue;
|
||||||
private GpuPrimeWorkLimiter.Lease? _limiter = limiter;
|
private IDisposable? _limiter = limiter;
|
||||||
private GpuContextLease? _gpu = gpu;
|
private GpuContextLease? _gpu = gpu;
|
||||||
private KernelContainer _kernels = kernels;
|
private KernelContainer _kernels = kernels;
|
||||||
private AcceleratorStream? _stream;
|
private AcceleratorStream? _stream;
|
||||||
@@ -110,15 +109,15 @@ public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLe
|
|||||||
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>>>();
|
||||||
});
|
});
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,15 +126,15 @@ public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLe
|
|||||||
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>>>();
|
||||||
});
|
});
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,15 +143,15 @@ public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLe
|
|||||||
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>>>();
|
||||||
});
|
});
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -161,15 +160,15 @@ public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLe
|
|||||||
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>>>();
|
||||||
});
|
});
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,67 +177,16 @@ public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLe
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
// TODO: Plumb the small-cycles device buffer into all kernels that can benefit
|
||||||
@@ -396,10 +344,7 @@ public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLe
|
|||||||
_stream?.Dispose();
|
_stream?.Dispose();
|
||||||
_stream = null;
|
_stream = null;
|
||||||
|
|
||||||
if (_gpu.HasValue)
|
_gpu?.Dispose();
|
||||||
{
|
|
||||||
_gpu.Value.ReturnToPool();
|
|
||||||
}
|
|
||||||
_gpu = null;
|
_gpu = null;
|
||||||
|
|
||||||
_executionLock = null;
|
_executionLock = null;
|
||||||
@@ -732,25 +677,25 @@ public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLe
|
|||||||
// GC.SuppressFinalize(this);
|
// GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct ExecutionScope
|
public readonly struct ExecutionScope : IDisposable
|
||||||
{
|
{
|
||||||
private readonly object? _lock;
|
private readonly object? _lock;
|
||||||
|
|
||||||
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);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -759,8 +704,6 @@ 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());
|
||||||
@@ -777,8 +720,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;
|
||||||
@@ -789,7 +732,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)
|
||||||
@@ -803,8 +746,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 &&
|
||||||
@@ -833,7 +776,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)
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ public static class GpuPrimeWorkLimiter
|
|||||||
private static readonly object _lock = new();
|
private static readonly object _lock = new();
|
||||||
private static int _currentLimit = 1;
|
private static int _currentLimit = 1;
|
||||||
|
|
||||||
public static Lease Acquire()
|
public static IDisposable Acquire()
|
||||||
{
|
{
|
||||||
// TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
|
// TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
|
||||||
// GPU jobs stop allocating Releaser instances on every acquisition.
|
// GPU jobs stop allocating Releaser instances on every acquisition.
|
||||||
var sem = _semaphore;
|
var sem = _semaphore;
|
||||||
sem.Wait();
|
sem.Wait();
|
||||||
return new Lease(sem);
|
return new Releaser(sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetLimit(int value)
|
public static void SetLimit(int value)
|
||||||
@@ -36,12 +36,12 @@ public static class GpuPrimeWorkLimiter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Lease
|
private sealed class Releaser : IDisposable
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _sem;
|
private readonly SemaphoreSlim _sem;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public Lease(SemaphoreSlim sem)
|
public Releaser(SemaphoreSlim sem)
|
||||||
{
|
{
|
||||||
_sem = sem;
|
_sem = sem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -267,7 +267,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(in ReadOnlyGpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
|
public void Add(in GpuUInt128 other) => AddInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void AddInternal(ulong otherHigh, ulong otherLow)
|
private void AddInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -294,17 +294,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
public void AddMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => AddModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
public void AddMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(GpuUInt128 value, GpuUInt128 modulus)
|
public void AddMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
AddModInternal(value.High, value.Low, in readOnlyMod);
|
AddModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -390,7 +390,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
public void AddMod(in GpuUInt128 value, ulong modulus) => AddMod(value.Low, modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AddMod(ulong value, ulong modulus)
|
public void AddMod(ulong value, ulong modulus)
|
||||||
@@ -414,7 +414,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(in ReadOnlyGpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Sub(GpuUInt128 other) => SubInternal(other.High, other.Low);
|
public void Sub(in GpuUInt128 other) => SubInternal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void SubInternal(ulong otherHigh, ulong otherLow)
|
private void SubInternal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -461,7 +461,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(in ReadOnlyGpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul64(GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
public void Mul64(in GpuUInt128 other) => Mul64Internal(other.High, other.Low);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
private void Mul64Internal(ulong otherHigh, ulong otherLow)
|
||||||
@@ -588,7 +588,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static int TrailingZeroCount(GpuUInt128 value)
|
public static int TrailingZeroCount(in GpuUInt128 value)
|
||||||
{
|
{
|
||||||
if (value.Low != 0UL)
|
if (value.Low != 0UL)
|
||||||
{
|
{
|
||||||
@@ -646,17 +646,17 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(in ReadOnlyGpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
public void SubMod(in GpuUInt128 value, in ReadOnlyGpuUInt128 modulus) => SubModInternal(value.High, value.Low, in modulus);
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(in ReadOnlyGpuUInt128 value, GpuUInt128 modulus)
|
public void SubMod(in ReadOnlyGpuUInt128 value, in GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void SubMod(GpuUInt128 value, GpuUInt128 modulus)
|
public void SubMod(in GpuUInt128 value, in GpuUInt128 modulus)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyMod = modulus.AsReadOnly();
|
||||||
SubModInternal(value.High, value.Low, in readOnlyMod);
|
SubModInternal(value.High, value.Low, in readOnlyMod);
|
||||||
@@ -727,7 +727,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Mul(GpuUInt128 other)
|
public void Mul(in GpuUInt128 other)
|
||||||
{
|
{
|
||||||
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyThis = AsReadOnly();
|
||||||
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
ReadOnlyGpuUInt128 readOnlyOther = other.AsReadOnly();
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ public static class GpuWorkLimiter
|
|||||||
private static readonly object _lock = new();
|
private static readonly object _lock = new();
|
||||||
private static int _currentLimit = 1;
|
private static int _currentLimit = 1;
|
||||||
|
|
||||||
public static Lease Acquire()
|
public static IDisposable Acquire()
|
||||||
{
|
{
|
||||||
// TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the
|
// TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the
|
||||||
// GpuLimiterThroughputBenchmarks so limiter acquisition does not allocate when we enter the
|
// GpuLimiterThroughputBenchmarks so limiter acquisition does not allocate when we enter the
|
||||||
// GPU scanning hot path.
|
// GPU scanning hot path.
|
||||||
var sem = _semaphore;
|
var sem = _semaphore;
|
||||||
sem.Wait();
|
sem.Wait();
|
||||||
return new Lease(sem);
|
return new Releaser(sem);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void SetLimit(int value)
|
public static void SetLimit(int value)
|
||||||
@@ -40,12 +40,12 @@ public static class GpuWorkLimiter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class Lease
|
private sealed class Releaser : IDisposable
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _sem;
|
private readonly SemaphoreSlim _sem;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public Lease(SemaphoreSlim sem)
|
public Releaser(SemaphoreSlim sem)
|
||||||
{
|
{
|
||||||
_sem = sem;
|
_sem = sem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Numerics;
|
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;
|
||||||
@@ -20,6 +20,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
private const int RemainderSlotCount = 4;
|
private const int RemainderSlotCount = 4;
|
||||||
|
|
||||||
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
private int _gpuBatchSize = GpuConstants.ScanBatchSize;
|
||||||
|
private readonly object _sync = new();
|
||||||
private ulong _divisorLimit;
|
private ulong _divisorLimit;
|
||||||
private bool _isConfigured;
|
private bool _isConfigured;
|
||||||
|
|
||||||
@@ -35,7 +36,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>>(ComputeExponentKernel));
|
_kernelExponentCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>>(ComputeMontgomeryExponentKernel));
|
||||||
|
|
||||||
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));
|
||||||
@@ -64,30 +65,34 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public void ConfigureFromMaxPrime(ulong maxPrime)
|
public void ConfigureFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
// Configuration happens once during startup, so no synchronization is required to cache the divisor limit.
|
lock (_sync)
|
||||||
|
{
|
||||||
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
|
||||||
_isConfigured = true;
|
_isConfigured = true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null)
|
public bool IsPrime(ulong prime, out bool divisorsExhausted)
|
||||||
{
|
{
|
||||||
// The CLI always configures the tester during startup, so the historical guard is commented out to avoid branching.
|
ulong allowedMax;
|
||||||
// if (!_isConfigured)
|
int batchCapacity;
|
||||||
// {
|
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Configuration is immutable, so the cached values can be read without locking.
|
lock (_sync)
|
||||||
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
{
|
||||||
int batchCapacity = _gpuBatchSize;
|
if (!_isConfigured)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
}
|
||||||
|
|
||||||
// The GPU path always receives an allowed maximum well above the minimal divisor, so the legacy
|
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
// guard stays commented out to document the invariant without branching.
|
batchCapacity = _gpuBatchSize;
|
||||||
// if (allowedMax < 3UL)
|
}
|
||||||
// {
|
|
||||||
// divisorsExhausted = true;
|
if (allowedMax < 3UL)
|
||||||
// return true;
|
{
|
||||||
// }
|
divisorsExhausted = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool composite;
|
bool composite;
|
||||||
bool coveredRange;
|
bool coveredRange;
|
||||||
@@ -104,7 +109,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
composite = CheckDivisors(
|
composite = CheckDivisors(
|
||||||
prime,
|
prime,
|
||||||
allowedMax,
|
allowedMax,
|
||||||
timeLimit,
|
|
||||||
accelerator,
|
accelerator,
|
||||||
kernel,
|
kernel,
|
||||||
resources.DivisorsBuffer,
|
resources.DivisorsBuffer,
|
||||||
@@ -123,7 +127,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
resources.CycleCandidates,
|
resources.CycleCandidates,
|
||||||
resources.CycleLengths,
|
resources.CycleLengths,
|
||||||
resources.CycleCapacity,
|
resources.CycleCapacity,
|
||||||
resources,
|
|
||||||
out lastProcessed,
|
out lastProcessed,
|
||||||
out coveredRange,
|
out coveredRange,
|
||||||
out processedCount
|
out processedCount
|
||||||
@@ -152,14 +155,18 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues));
|
||||||
}
|
}
|
||||||
|
|
||||||
// The GPU pipeline calls ConfigureFromMaxPrime before preparing candidates, so the legacy guard
|
ulong divisorLimit;
|
||||||
// remains commented out to document the invariant without branching.
|
|
||||||
// if (!_isConfigured)
|
lock (_sync)
|
||||||
// {
|
{
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
if (!_isConfigured)
|
||||||
// }
|
{
|
||||||
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
|
}
|
||||||
|
|
||||||
|
divisorLimit = _divisorLimit;
|
||||||
|
}
|
||||||
|
|
||||||
ulong divisorLimit = _divisorLimit;
|
|
||||||
for (int index = 0; index < primes.Length; index++)
|
for (int index = 0; index < primes.Length; index++)
|
||||||
{
|
{
|
||||||
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
|
||||||
@@ -169,7 +176,6 @@ 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,
|
||||||
@@ -188,7 +194,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ulong[] cycleCandidates,
|
ulong[] cycleCandidates,
|
||||||
ulong[] cycleLengths,
|
ulong[] cycleLengths,
|
||||||
int cycleCapacity,
|
int cycleCapacity,
|
||||||
BatchResources resources,
|
|
||||||
out ulong lastProcessed,
|
out ulong lastProcessed,
|
||||||
out bool coveredRange,
|
out bool coveredRange,
|
||||||
out ulong processedCount)
|
out ulong processedCount)
|
||||||
@@ -198,15 +203,6 @@ 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;
|
||||||
@@ -216,8 +212,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
||||||
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||||
|
|
||||||
// Batch resources expose a positive cycle capacity, so skip the redundant clamp here.
|
cycleCapacity = Math.Max(1, cycleCapacity);
|
||||||
// cycleCapacity = Math.Max(1, cycleCapacity);
|
|
||||||
|
|
||||||
int chunkCapacity = Math.Min(batchCapacity, cycleCapacity);
|
int chunkCapacity = Math.Min(batchCapacity, cycleCapacity);
|
||||||
int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup;
|
int maxThreadsPerGroup = (int)accelerator.MaxNumThreadsPerGroup;
|
||||||
@@ -226,22 +221,20 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup);
|
chunkCapacity = Math.Min(chunkCapacity, maxThreadsPerGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
// cycleCapacity already bounds chunkCapacity above zero, so the fallback would never execute.
|
if (chunkCapacity <= 0)
|
||||||
// if (chunkCapacity <= 0)
|
{
|
||||||
// {
|
chunkCapacity = cycleCapacity;
|
||||||
// chunkCapacity = cycleCapacity;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
ulong[] filteredDivisors = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||||
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
ulong[] divisorGaps = ArrayPool<ulong>.Shared.Rent(chunkCapacity);
|
||||||
|
|
||||||
UInt128 twoP128 = (UInt128)prime << 1;
|
UInt128 twoP128 = (UInt128)prime << 1;
|
||||||
// Primes above one yield a non-zero 2p value, so the legacy zero guard remains commented out.
|
if (twoP128 == UInt128.Zero)
|
||||||
// if (twoP128 == UInt128.Zero)
|
{
|
||||||
// {
|
coveredRange = true;
|
||||||
// coveredRange = true;
|
return false;
|
||||||
// return false;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
UInt128 allowedMax128 = allowedMax;
|
UInt128 allowedMax128 = allowedMax;
|
||||||
UInt128 firstDivisor128 = twoP128 + UInt128.One;
|
UInt128 firstDivisor128 = twoP128 + UInt128.One;
|
||||||
@@ -252,13 +245,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
UInt128 maxK128 = (allowedMax128 - UInt128.One) / twoP128;
|
UInt128 maxK128 = (allowedMax128 - UInt128.One) / twoP128;
|
||||||
// Once the first divisor falls within the allowed range, at least one multiplier k is admissible,
|
if (maxK128 == UInt128.Zero)
|
||||||
// so keep the defensive zero check commented out.
|
{
|
||||||
// if (maxK128 == UInt128.Zero)
|
coveredRange = true;
|
||||||
// {
|
return false;
|
||||||
// coveredRange = true;
|
}
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
|
ulong maxK = maxK128 > ulong.MaxValue ? ulong.MaxValue : (ulong)maxK128;
|
||||||
ulong currentK = 1UL;
|
ulong currentK = 1UL;
|
||||||
@@ -287,12 +278,6 @@ 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)
|
||||||
@@ -300,12 +285,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
chunkCount = (int)remainingK;
|
chunkCount = (int)remainingK;
|
||||||
}
|
}
|
||||||
|
|
||||||
// chunkCapacity and the remaining multiplier range are strictly positive here, so the zero
|
if (chunkCount <= 0)
|
||||||
// chunk fallback never triggers on production scans.
|
{
|
||||||
// if (chunkCount <= 0)
|
break;
|
||||||
// {
|
}
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount);
|
Span<ulong> candidateSpan = cycleCandidates.AsSpan(0, chunkCount);
|
||||||
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
|
Span<ulong> gapSpan = divisorGaps.AsSpan(0, chunkCount);
|
||||||
@@ -343,10 +326,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
gapView.CopyFromCPU(ref MemoryMarshal.GetReference(gapSpan), chunkCount);
|
gapView.CopyFromCPU(ref MemoryMarshal.GetReference(gapSpan), chunkCount);
|
||||||
|
|
||||||
ComputeRemaindersOnGpu(chunkCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig);
|
ComputeRemaindersOnGpu(chunkCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel);
|
||||||
ComputeRemaindersOnGpu(chunkCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig);
|
ComputeRemaindersOnGpu(chunkCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel);
|
||||||
ComputeRemaindersOnGpu(chunkCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig);
|
ComputeRemaindersOnGpu(chunkCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel);
|
||||||
ComputeRemaindersOnGpu(chunkCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig);
|
ComputeRemaindersOnGpu(chunkCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel);
|
||||||
|
|
||||||
BuildCandidateMaskOnGpu(chunkCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, maskSpan, candidateMaskKernel);
|
BuildCandidateMaskOnGpu(chunkCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, maskSpan, candidateMaskKernel);
|
||||||
|
|
||||||
@@ -413,8 +396,6 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,18 +445,18 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ArrayView1D<byte, Stride1D.Dense> deltaView,
|
ArrayView1D<byte, Stride1D.Dense> deltaView,
|
||||||
ArrayView1D<byte, Stride1D.Dense> remainderView,
|
ArrayView1D<byte, Stride1D.Dense> remainderView,
|
||||||
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
|
Action<Index1D, ArrayView<ulong>, ArrayView<byte>, byte> deltaKernel,
|
||||||
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel,
|
Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> scanKernel)
|
||||||
Func<int, KernelConfig> configProvider)
|
|
||||||
{
|
{
|
||||||
// GPU batches always contain at least one divisor gap, so the empty-count guard remains commented
|
if (count <= 0)
|
||||||
// to eliminate redundant branching.
|
{
|
||||||
// if (count <= 0)
|
return;
|
||||||
// {
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
deltaKernel(count, gapView, deltaView, modulus);
|
deltaKernel(count, gapView, deltaView, modulus);
|
||||||
KernelConfig kernelConfig = configProvider(count);
|
SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic<int>(count);
|
||||||
|
Index1D gridDim = new Index1D(1);
|
||||||
|
Index1D groupDim = new Index1D(count);
|
||||||
|
KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig);
|
||||||
|
|
||||||
scanKernel(kernelConfig, new Index1D(count), deltaView, remainderView, baseRemainder, modulus);
|
scanKernel(kernelConfig, new Index1D(count), deltaView, remainderView, baseRemainder, modulus);
|
||||||
}
|
}
|
||||||
@@ -492,13 +473,11 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
Span<byte> destination,
|
Span<byte> destination,
|
||||||
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel)
|
Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> maskKernel)
|
||||||
{
|
{
|
||||||
// Candidate batches always provide at least one entry, so the zero-count guard stays commented out
|
if (count <= 0)
|
||||||
// to avoid clearing spans unnecessarily.
|
{
|
||||||
// if (count <= 0)
|
destination.Clear();
|
||||||
// {
|
return;
|
||||||
// destination.Clear();
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView);
|
maskKernel(count, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView);
|
||||||
maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
maskView.CopyToCPU(ref MemoryMarshal.GetReference(destination), count);
|
||||||
@@ -506,12 +485,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count)
|
private static byte FetchLastRemainder(ArrayView1D<byte, Stride1D.Dense> remainderView, int count)
|
||||||
{
|
{
|
||||||
// Kernels always process at least one remainder in this path, so the empty-span guard remains
|
if (count <= 0)
|
||||||
// commented to document the invariant.
|
{
|
||||||
// if (count <= 0)
|
return 0;
|
||||||
// {
|
}
|
||||||
// return 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
byte value = 0;
|
byte value = 0;
|
||||||
remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1);
|
remainderView.SubView(count - 1, 1).CopyToCPU(ref value, 1);
|
||||||
@@ -524,11 +501,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
if (result >= modulus)
|
if (result >= modulus)
|
||||||
{
|
{
|
||||||
result -= modulus;
|
result -= modulus;
|
||||||
// The step deltas remain below the modulus, so a single subtraction suffices on this path.
|
if (result >= modulus)
|
||||||
// if (result >= modulus)
|
{
|
||||||
// {
|
result %= modulus;
|
||||||
// result %= modulus;
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (byte)result;
|
return (byte)result;
|
||||||
@@ -536,60 +512,46 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
|
||||||
{
|
{
|
||||||
// The production configuration seeds the scan with exponents above the first odd prime, so the defensive guard remains
|
if (maxPrime <= 1UL)
|
||||||
// commented to document the invariant without branching.
|
{
|
||||||
// if (maxPrime <= 1UL)
|
return 0UL;
|
||||||
// {
|
}
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Real workloads push maxPrime well past the 64-bit shift boundary, so keep the historical implementation commented out
|
|
||||||
// and return ulong.MaxValue directly.
|
|
||||||
// if (maxPrime - 1UL >= 64UL)
|
|
||||||
// {
|
|
||||||
// return ulong.MaxValue;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return (1UL << (int)(maxPrime - 1UL)) - 1UL;
|
|
||||||
|
|
||||||
|
if (maxPrime - 1UL >= 64UL)
|
||||||
|
{
|
||||||
return ulong.MaxValue;
|
return ulong.MaxValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return (1UL << (int)(maxPrime - 1UL)) - 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
|
||||||
{
|
{
|
||||||
// The by-divisor search emits candidate exponents starting at the first odd prime, so the defensive guard remains
|
if (prime <= 1UL)
|
||||||
// commented to document that invariant.
|
{
|
||||||
// if (prime <= 1UL)
|
return 0UL;
|
||||||
// {
|
}
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Production exponents are always large enough that the shift-based clamp would overflow, so skip the legacy branch and
|
|
||||||
// return the configured divisor limit directly.
|
|
||||||
// if (prime - 1UL >= 64UL)
|
|
||||||
// {
|
|
||||||
// return divisorLimit;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
|
||||||
|
|
||||||
|
if (prime - 1UL >= 64UL)
|
||||||
|
{
|
||||||
return divisorLimit;
|
return divisorLimit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
|
||||||
|
}
|
||||||
|
|
||||||
private static void CheckKernel(Index1D index, ArrayView<MontgomeryDivisorData> divisors, ArrayView<ulong> exponents, ArrayView<byte> hits)
|
private static void CheckKernel(Index1D index, ArrayView<MontgomeryDivisorData> divisors, ArrayView<ulong> exponents, ArrayView<byte> hits)
|
||||||
{
|
{
|
||||||
MontgomeryDivisorData divisor = divisors[index];
|
MontgomeryDivisorData divisor = divisors[index];
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
// The divisor staging path filters out non-prime and even moduli before they reach the device, so this guard
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
// would never fire for EvenPerfectBitScanner workloads.
|
{
|
||||||
// if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
hits[index] = 0;
|
||||||
// {
|
return;
|
||||||
// hits[index] = 0;
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
hits[index] = exponent.Pow2ModWindowedGpu(modulus) == 1UL ? (byte)1 : (byte)0;
|
hits[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL ? (byte)1 : (byte)0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
public sealed class DivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
|
||||||
@@ -629,20 +591,16 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
int length = primes.Length;
|
int length = primes.Length;
|
||||||
// Production batches always contain at least one exponent, so the empty-span guard remains commented out to remove
|
if (length == 0)
|
||||||
// the redundant branch.
|
{
|
||||||
// if (length == 0)
|
return;
|
||||||
// {
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// The divisor staging path filters non-prime and even moduli before kernels launch, so keep the defensive
|
if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL)
|
||||||
// modulus guard commented out to avoid branching in the hot loop.
|
{
|
||||||
// if (divisorData.Modulus <= 1UL || (divisorData.Modulus & 1UL) == 0UL)
|
hits.Clear();
|
||||||
// {
|
return;
|
||||||
// hits.Clear();
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ArrayView1D<ulong, Stride1D.Dense> exponentsView = _exponentsBuffer.View;
|
ArrayView1D<ulong, Stride1D.Dense> exponentsView = _exponentsBuffer.View;
|
||||||
ArrayView1D<ulong, Stride1D.Dense> resultsView = _resultsBuffer.View;
|
ArrayView1D<ulong, Stride1D.Dense> resultsView = _resultsBuffer.View;
|
||||||
@@ -801,36 +759,35 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
mask[globalIndex] = 1;
|
mask[globalIndex] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void ComputeExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
private static void ComputeMontgomeryExponentKernel(Index1D index, MontgomeryDivisorData divisor, ArrayView<ulong> exponents, ArrayView<ulong> results)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
// Kernel inputs are constrained to odd prime moduli, so this defensive branch would never execute on the configured
|
if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
||||||
// scanning path.
|
{
|
||||||
// if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
results[index] = 0UL;
|
||||||
// {
|
return;
|
||||||
// results[index] = 0UL;
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
|
|
||||||
results[index] = exponent.Pow2ModWindowedGpu(modulus);
|
results[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ulong DivisorLimit
|
public ulong DivisorLimit
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
// The by-divisor scanner always configures the tester before exposing the limit, so the guard remains
|
lock (_sync)
|
||||||
// commented out to document the invariant without forcing a runtime branch.
|
{
|
||||||
// if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
// {
|
{
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
// }
|
}
|
||||||
|
|
||||||
return _divisorLimit;
|
return _divisorLimit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private BatchResources RentBatchResources(Accelerator accelerator, int capacity)
|
private BatchResources RentBatchResources(Accelerator accelerator, int capacity)
|
||||||
{
|
{
|
||||||
@@ -845,17 +802,15 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
private void ReturnBatchResources(Accelerator accelerator, BatchResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => []).Add(resources);
|
private void ReturnBatchResources(Accelerator accelerator, BatchResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => []).Add(resources);
|
||||||
|
|
||||||
private sealed class BatchResources
|
private sealed class BatchResources : IDisposable
|
||||||
{
|
{
|
||||||
internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity)
|
internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity)
|
||||||
{
|
{
|
||||||
int cycleCapacity = DivisorCycleCache.Shared.PreferredBatchSize;
|
int cycleCapacity = DivisorCycleCache.Shared.PreferredBatchSize;
|
||||||
// The shared cache always reports a positive preferred size on production workloads, so the fallback remains
|
if (cycleCapacity <= 0)
|
||||||
// commented out to avoid branching during resource initialization.
|
{
|
||||||
// if (cycleCapacity <= 0)
|
cycleCapacity = 1;
|
||||||
// {
|
}
|
||||||
// cycleCapacity = 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int actualCapacity = Math.Max(capacity, cycleCapacity);
|
int actualCapacity = Math.Max(capacity, cycleCapacity);
|
||||||
|
|
||||||
@@ -907,24 +862,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
internal readonly Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> RemainderScanKernel;
|
internal readonly Action<KernelConfig, Index1D, ArrayView<byte>, ArrayView<byte>, byte, byte> RemainderScanKernel;
|
||||||
internal readonly Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> CandidateMaskKernel;
|
internal readonly Action<Index1D, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, ArrayView<byte>, byte, ArrayView<byte>> CandidateMaskKernel;
|
||||||
|
|
||||||
private readonly Dictionary<int, KernelConfig> _remainderScanConfigs = new();
|
|
||||||
|
|
||||||
internal readonly int Capacity;
|
internal readonly int Capacity;
|
||||||
|
|
||||||
internal readonly int CycleCapacity;
|
internal readonly int CycleCapacity;
|
||||||
|
|
||||||
internal KernelConfig GetRemainderScanConfig(int count)
|
|
||||||
{
|
|
||||||
if (!_remainderScanConfigs.TryGetValue(count, out KernelConfig config))
|
|
||||||
{
|
|
||||||
SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic<int>(count);
|
|
||||||
config = new KernelConfig(new Index1D(1), new Index1D(count), sharedMemoryConfig);
|
|
||||||
_remainderScanConfigs[count] = config;
|
|
||||||
}
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
DivisorsBuffer.Dispose();
|
DivisorsBuffer.Dispose();
|
||||||
@@ -953,24 +894,25 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
public ulong GetAllowedMaxDivisor(ulong prime)
|
public ulong GetAllowedMaxDivisor(ulong prime)
|
||||||
{
|
{
|
||||||
// The CLI configures the GPU tester once at startup, so callers never request divisor limits before
|
lock (_sync)
|
||||||
// initialization completes.
|
{
|
||||||
// if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
// {
|
{
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
// }
|
}
|
||||||
|
|
||||||
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
|
public DivisorScanSession CreateDivisorSession()
|
||||||
{
|
{
|
||||||
// Divisor sessions are handed out only after configuration, so this guard would never trigger on the
|
lock (_sync)
|
||||||
// production path.
|
{
|
||||||
// if (!_isConfigured)
|
if (!_isConfigured)
|
||||||
// {
|
{
|
||||||
// throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
|
||||||
// }
|
}
|
||||||
|
|
||||||
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
if (_sessionPool.TryTake(out DivisorScanSession? session))
|
||||||
{
|
{
|
||||||
@@ -982,6 +924,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,273 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using ILGPU;
|
|
||||||
using ILGPU.Runtime;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Gpu;
|
|
||||||
|
|
||||||
internal sealed class MersenneNumberDivisorCandidateGpuEvaluator
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
// Candidate batches always carry at least one divisor on the by-divisor path, so the empty-count
|
|
||||||
// guard remains commented out to eliminate the branch.
|
|
||||||
// if (count == 0)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (count > _capacity)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
// The evaluator always starts with a positive capacity, so the zero-capacity fallback remains commented.
|
|
||||||
// if (newCapacity == 0)
|
|
||||||
// {
|
|
||||||
// newCapacity = 1;
|
|
||||||
// }
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
// Modulus lookups only cover {10, 8, 5, 3, 7, 11}, so the zero-modulus guard stays commented out.
|
|
||||||
// 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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,190 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
// Montgomery batches always include at least one modulus on the by-divisor path, so the empty-length
|
|
||||||
// guard stays commented out to avoid branching.
|
|
||||||
// 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;
|
|
||||||
// The builder always starts with a positive capacity, so the zero-capacity fallback remains commented.
|
|
||||||
// if (newCapacity == 0)
|
|
||||||
// {
|
|
||||||
// newCapacity = 1;
|
|
||||||
// }
|
|
||||||
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;
|
|
||||||
// The builder launches kernels with an extent equal to the modulus count, so the bounds guard remains commented
|
|
||||||
// out to avoid redundant branching on every thread.
|
|
||||||
// if (idx >= moduli.Length)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong modulus = moduli[idx];
|
|
||||||
// All staged divisors are odd primes on the scanner path, so keep the defensive modulus guard commented out to
|
|
||||||
// remove the branch from the kernel.
|
|
||||||
// if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
|
||||||
// {
|
|
||||||
// destination[idx] = default;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong nPrime = ComputeMontgomeryNPrime(modulus);
|
|
||||||
ulong montgomeryOne = ComputeMontgomeryResidue64(modulus);
|
|
||||||
ulong montgomeryTwo = montgomeryOne.MulMod64(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,146 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using ILGPU;
|
|
||||||
using ILGPU.Runtime;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Gpu;
|
|
||||||
|
|
||||||
internal sealed class MersenneNumberDivisorRemainderGpuStepper
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("MersenneNumberDivisorRemainderGpuStepper was disposed more than once.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_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));
|
|
||||||
}
|
|
||||||
|
|
||||||
// GPU remainder batches always advance by a positive chunk count and operate on fixed-size tables, so
|
|
||||||
// the defensive early-return remains commented out.
|
|
||||||
// if (advanceCount <= 0 || remainders.Length == 0)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Production code always calls the stepper with a fixed-length table, so the historical length guard stays commented.
|
|
||||||
// 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;
|
|
||||||
// Kernel launches match the remainder table length, so the bounds guard stays commented to avoid redundant branching.
|
|
||||||
// if (idx >= remainders.Length)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int modulus = GetModulus(idx);
|
|
||||||
// The modulus selector only emits positive bases (10, 8, 5, 3, 7, 11) for the configured indices, so the zero-modulus
|
|
||||||
// fallback remains commented out.
|
|
||||||
// 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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
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
|
|
||||||
{
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
throw new InvalidOperationException("MersenneNumberDivisorResidueGpuEvaluator was disposed more than once.");
|
|
||||||
}
|
|
||||||
|
|
||||||
_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;
|
|
||||||
// Residue batches always include at least one exponent when scheduled from the by-divisor scanner, so
|
|
||||||
// the empty-length guard remains commented out to avoid branching.
|
|
||||||
// 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;
|
|
||||||
// The evaluator always starts with a positive capacity, so the zero-capacity fallback stays commented.
|
|
||||||
// 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;
|
|
||||||
// Batched launches match the buffer length on production workloads, so the bounds guard stays commented out to remove
|
|
||||||
// the redundant comparison.
|
|
||||||
// if (idx >= length)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong exponent = exponents[idx];
|
|
||||||
ulong modulus = divisorData.Modulus;
|
|
||||||
// Residue batches only include valid odd moduli, so the defensive check stays commented to avoid re-validating
|
|
||||||
// the invariant on every iteration.
|
|
||||||
// if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
|
||||||
// {
|
|
||||||
// results[idx] = 0UL;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
results[idx] = exponent.Pow2ModWindowedGpu(modulus);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -327,9 +327,8 @@ public class MersenneNumberLucasLehmerGpuTester
|
|||||||
// TODO: Replace StreamWriter with the pooled TextFileWriter pipeline so persisting NTT
|
// TODO: Replace StreamWriter with the pooled TextFileWriter pipeline so persisting NTT
|
||||||
// parameters reuses the zero-allocation buffered writes highlighted in the scanner I/O
|
// parameters reuses the zero-allocation buffered writes highlighted in the scanner I/O
|
||||||
// benchmarks instead of allocating a new encoder per append.
|
// benchmarks instead of allocating a new encoder per append.
|
||||||
var writer = new StreamWriter(ParameterFilePath, append: true);
|
using var writer = new StreamWriter(ParameterFilePath, append: true);
|
||||||
writer.WriteLine($"{length} {modulus.High} {modulus.Low} {primitiveRoot.High} {primitiveRoot.Low}");
|
writer.WriteLine($"{length} {modulus.High} {modulus.Low} {primitiveRoot.High} {primitiveRoot.Low}");
|
||||||
writer.Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +338,7 @@ public class MersenneNumberLucasLehmerGpuTester
|
|||||||
ulong foundRoot = 0UL;
|
ulong foundRoot = 0UL;
|
||||||
bool success = false;
|
bool success = false;
|
||||||
var processorCount = Environment.ProcessorCount;
|
var processorCount = Environment.ProcessorCount;
|
||||||
var cts = new System.Threading.CancellationTokenSource();
|
using var cts = new System.Threading.CancellationTokenSource();
|
||||||
object sync = new();
|
object sync = new();
|
||||||
|
|
||||||
// TODO: Move this Parallel.For to the shared low-overhead work scheduler once the NTT parameter
|
// TODO: Move this Parallel.For to the shared low-overhead work scheduler once the NTT parameter
|
||||||
@@ -382,13 +381,11 @@ public class MersenneNumberLucasLehmerGpuTester
|
|||||||
{
|
{
|
||||||
modulus = new GpuUInt128(0UL, foundCandidate);
|
modulus = new GpuUInt128(0UL, foundCandidate);
|
||||||
primitiveRoot = new GpuUInt128(0UL, foundRoot);
|
primitiveRoot = new GpuUInt128(0UL, foundRoot);
|
||||||
cts.Dispose();
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
modulus = new GpuUInt128(0UL, 18446744069414584321UL);
|
modulus = new GpuUInt128(0UL, 18446744069414584321UL);
|
||||||
primitiveRoot = new GpuUInt128(0UL, 7UL);
|
primitiveRoot = new GpuUInt128(0UL, 7UL);
|
||||||
cts.Dispose();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
var divisorBuffer = accelerator.Allocate1D<MontgomeryDivisorData>(divisorCount);
|
|
||||||
var countBuffer = accelerator.Allocate1D<int>(divisorCount);
|
|
||||||
var resultBuffer = accelerator.Allocate1D<ulong>(divisorCount * stride);
|
|
||||||
|
|
||||||
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);
|
|
||||||
|
|
||||||
resultBuffer.Dispose();
|
|
||||||
countBuffer.Dispose();
|
|
||||||
divisorBuffer.Dispose();
|
|
||||||
execution.Dispose();
|
|
||||||
lease.Dispose();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -47,7 +47,7 @@ public static class NttGpuMath
|
|||||||
|
|
||||||
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks.
|
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks.
|
||||||
|
|
||||||
private sealed class SquareCacheEntry
|
private sealed class SquareCacheEntry : IDisposable
|
||||||
{
|
{
|
||||||
public MemoryBuffer1D<GpuUInt128, Stride1D.Dense> Buffer { get; }
|
public MemoryBuffer1D<GpuUInt128, Stride1D.Dense> Buffer { get; }
|
||||||
public GpuUInt128 Root { get; }
|
public GpuUInt128 Root { get; }
|
||||||
|
|||||||
@@ -415,9 +415,9 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
ulong? previousOrder,
|
ulong? previousOrder,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig config,
|
PrimeOrderCalculator.PrimeOrderSearchConfig config,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
out ulong order)
|
out PrimeOrderCalculator.PrimeOrderResult result)
|
||||||
{
|
{
|
||||||
order = 0UL;
|
result = default;
|
||||||
|
|
||||||
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
var lease = GpuKernelPool.GetKernel(useGpuOrder: true);
|
||||||
var execution = lease.EnterExecutionScope();
|
var execution = lease.EnterExecutionScope();
|
||||||
@@ -497,17 +497,18 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
throw new InvalidOperationException("GPU Pollard Rho stack overflow; increase HeuristicStackCapacity.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ulong order = 0UL;
|
||||||
resultBuffer.View.CopyToCPU(ref order, 1);
|
resultBuffer.View.CopyToCPU(ref order, 1);
|
||||||
|
|
||||||
if (kernelStatus == PrimeOrderKernelStatus.FactoringFailure)
|
PrimeOrderCalculator.PrimeOrderStatus finalStatus = kernelStatus == PrimeOrderKernelStatus.Found
|
||||||
{
|
? PrimeOrderCalculator.PrimeOrderStatus.Found
|
||||||
order = 0UL;
|
: PrimeOrderCalculator.PrimeOrderStatus.HeuristicUnresolved;
|
||||||
}
|
|
||||||
|
|
||||||
|
result = new PrimeOrderCalculator.PrimeOrderResult(finalStatus, order);
|
||||||
DisposeResources();
|
DisposeResources();
|
||||||
lease.Dispose();
|
lease.Dispose();
|
||||||
|
|
||||||
return order != 0UL;
|
return true;
|
||||||
|
|
||||||
void DisposeResources()
|
void DisposeResources()
|
||||||
{
|
{
|
||||||
@@ -805,15 +806,13 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
for (int i = 0; i < factorCount; i++)
|
for (int i = 0; i < factorCount; i++)
|
||||||
{
|
{
|
||||||
ulong factor = factors[i];
|
ulong factor = factors[i];
|
||||||
// The factorization routine emits strictly greater-than-one factors for phi on this path, so the
|
if (factor <= 1UL)
|
||||||
// defensive continue would never execute.
|
{
|
||||||
// if (factor <= 1UL)
|
continue;
|
||||||
// {
|
}
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong reduced = phi / factor;
|
ulong reduced = phi / factor;
|
||||||
if (reduced.Pow2ModWindowedGpu(divisor.Modulus) == 1UL)
|
if (reduced.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -829,7 +828,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
if (residue == 1UL || residue == 7UL)
|
if (residue == 1UL || residue == 7UL)
|
||||||
{
|
{
|
||||||
ulong half = phi >> 1;
|
ulong half = phi >> 1;
|
||||||
if ((half).Pow2ModWindowedGpu(divisor.Modulus) == 1UL)
|
if (Pow2EqualsOneKernel(half, divisor))
|
||||||
{
|
{
|
||||||
order = half;
|
order = half;
|
||||||
}
|
}
|
||||||
@@ -850,11 +849,10 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
{
|
{
|
||||||
ulong primeFactor = factors[i];
|
ulong primeFactor = factors[i];
|
||||||
int exponent = exponents[i];
|
int exponent = exponents[i];
|
||||||
// Factorization never yields unity on the scanning path, so skip the redundant guard.
|
if (primeFactor <= 1UL)
|
||||||
// if (primeFactor <= 1UL)
|
{
|
||||||
// {
|
continue;
|
||||||
// continue;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
for (int iteration = 0; iteration < exponent; iteration++)
|
for (int iteration = 0; iteration < exponent; iteration++)
|
||||||
{
|
{
|
||||||
@@ -864,7 +862,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong reduced = order / primeFactor;
|
ulong reduced = order / primeFactor;
|
||||||
if ((reduced).Pow2ModWindowedGpu(divisor.Modulus) == 1UL)
|
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||||
{
|
{
|
||||||
order = reduced;
|
order = reduced;
|
||||||
continue;
|
continue;
|
||||||
@@ -890,14 +888,12 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
ArrayView1D<ulong, Stride1D.Dense> compositeStack,
|
ArrayView1D<ulong, Stride1D.Dense> compositeStack,
|
||||||
ArrayView1D<byte, Stride1D.Dense> statusOut)
|
ArrayView1D<byte, Stride1D.Dense> statusOut)
|
||||||
{
|
{
|
||||||
// Order values handed to the kernel are strictly positive on the production path, so the zero guard stays commented
|
if (order == 0UL)
|
||||||
// out to remove the redundant branch.
|
{
|
||||||
// if (order == 0UL)
|
return false;
|
||||||
// {
|
}
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if ((order).Pow2ModWindowedGpu(divisor.Modulus) != 1UL)
|
if (!Pow2EqualsOneKernel(order, divisor))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -927,11 +923,10 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
{
|
{
|
||||||
ulong primeFactor = factors[i];
|
ulong primeFactor = factors[i];
|
||||||
int exponent = exponents[i];
|
int exponent = exponents[i];
|
||||||
// Factorization never yields one on the by-divisor path, so the defensive continue stays commented out.
|
if (primeFactor <= 1UL)
|
||||||
// if (primeFactor <= 1UL)
|
{
|
||||||
// {
|
continue;
|
||||||
// continue;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
ulong reduced = order;
|
ulong reduced = order;
|
||||||
for (int iteration = 0; iteration < exponent; iteration++)
|
for (int iteration = 0; iteration < exponent; iteration++)
|
||||||
@@ -942,7 +937,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
reduced /= primeFactor;
|
reduced /= primeFactor;
|
||||||
if ((reduced).Pow2ModWindowedGpu(divisor.Modulus) == 1UL)
|
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1004,32 +999,28 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
long candidateCapacity = candidates.Length;
|
long candidateCapacity = candidates.Length;
|
||||||
int candidateLimit = candidateCapacity < HeuristicCandidateLimit ? (int)candidateCapacity : HeuristicCandidateLimit;
|
int candidateLimit = candidateCapacity < HeuristicCandidateLimit ? (int)candidateCapacity : HeuristicCandidateLimit;
|
||||||
int candidateCount = BuildCandidatesKernel(order, workFactors, workExponents, factorCount, candidates, stackIndex, stackExponent, stackProduct, candidateLimit);
|
int candidateCount = BuildCandidatesKernel(order, workFactors, workExponents, factorCount, candidates, stackIndex, stackExponent, stackProduct, candidateLimit);
|
||||||
// Candidate enumeration always yields at least one order on the configured workloads, so the zero-count guard remains
|
if (candidateCount == 0)
|
||||||
// commented out to eliminate the redundant branch.
|
{
|
||||||
// if (candidateCount == 0)
|
return false;
|
||||||
// {
|
}
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
SortCandidatesKernel(prime, previousOrder, hasPreviousOrder != 0, candidates, candidateCount);
|
SortCandidatesKernel(prime, previousOrder, hasPreviousOrder != 0, candidates, candidateCount);
|
||||||
|
|
||||||
int powBudget = maxPowChecks <= 0 ? candidateCount : maxPowChecks;
|
int powBudget = maxPowChecks <= 0 ? candidateCount : maxPowChecks;
|
||||||
// The heuristics always reserve at least one check, so the zero-budget fallback remains commented out.
|
if (powBudget <= 0)
|
||||||
// if (powBudget <= 0)
|
{
|
||||||
// {
|
powBudget = candidateCount;
|
||||||
// powBudget = candidateCount;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
int powUsed = 0;
|
int powUsed = 0;
|
||||||
|
|
||||||
for (int i = 0; i < candidateCount && powUsed < powBudget; i++)
|
for (int i = 0; i < candidateCount && powUsed < powBudget; i++)
|
||||||
{
|
{
|
||||||
ulong candidate = candidates[i];
|
ulong candidate = candidates[i];
|
||||||
// Candidate generation skips 1 by construction, so the defensive continue stays commented out.
|
if (candidate <= 1UL)
|
||||||
// if (candidate <= 1UL)
|
{
|
||||||
// {
|
continue;
|
||||||
// continue;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if (powUsed >= powBudget)
|
if (powUsed >= powBudget)
|
||||||
{
|
{
|
||||||
@@ -1037,7 +1028,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
powUsed++;
|
powUsed++;
|
||||||
if ((candidate).Pow2ModWindowedGpu(divisor.Modulus) != 1UL)
|
if (!Pow2EqualsOneKernel(candidate, divisor))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -1317,11 +1308,10 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
{
|
{
|
||||||
ulong primeFactor = factors[i];
|
ulong primeFactor = factors[i];
|
||||||
int exponent = exponents[i];
|
int exponent = exponents[i];
|
||||||
// Factorization never yields one for candidate orders, so keep the guard commented out to avoid re-checking inputs.
|
if (primeFactor <= 1UL)
|
||||||
// if (primeFactor <= 1UL)
|
{
|
||||||
// {
|
continue;
|
||||||
// continue;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
ulong reduced = candidate;
|
ulong reduced = candidate;
|
||||||
for (int iteration = 0; iteration < exponent; iteration++)
|
for (int iteration = 0; iteration < exponent; iteration++)
|
||||||
@@ -1338,7 +1328,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
|
|
||||||
powUsed++;
|
powUsed++;
|
||||||
if ((reduced).Pow2ModWindowedGpu(divisor.Modulus) == 1UL)
|
if (Pow2EqualsOneKernel(reduced, divisor))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -1360,21 +1350,17 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
ArrayView1D<ulong, Stride1D.Dense> compositeStack,
|
ArrayView1D<ulong, Stride1D.Dense> compositeStack,
|
||||||
ArrayView1D<byte, Stride1D.Dense> statusOut)
|
ArrayView1D<byte, Stride1D.Dense> statusOut)
|
||||||
{
|
{
|
||||||
// Composite candidates sent here are always greater than one during production scans, so the trivial-return branch stays
|
if (initial <= 1UL)
|
||||||
// commented out.
|
{
|
||||||
// if (initial <= 1UL)
|
return true;
|
||||||
// {
|
}
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int stackCapacity = (int)compositeStack.Length;
|
int stackCapacity = (int)compositeStack.Length;
|
||||||
// Pollard-Rho always reserves a positive stack on production workloads, so the overflow guard remains commented out
|
if (stackCapacity <= 0)
|
||||||
// to avoid branching here.
|
{
|
||||||
// if (stackCapacity <= 0)
|
statusOut[0] = (byte)PrimeOrderKernelStatus.PollardOverflow;
|
||||||
// {
|
return false;
|
||||||
// statusOut[0] = (byte)PrimeOrderKernelStatus.PollardOverflow;
|
}
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int stackTop = 0;
|
int stackTop = 0;
|
||||||
compositeStack[stackTop] = initial;
|
compositeStack[stackTop] = initial;
|
||||||
@@ -1384,12 +1370,10 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
{
|
{
|
||||||
stackTop--;
|
stackTop--;
|
||||||
ulong composite = compositeStack[stackTop];
|
ulong composite = compositeStack[stackTop];
|
||||||
// Composite values enqueued for factoring are always greater than one on production workloads, so the guard stays
|
if (composite <= 1UL)
|
||||||
// commented out to avoid extra branching.
|
{
|
||||||
// if (composite <= 1UL)
|
continue;
|
||||||
// {
|
}
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!PeelSmallPrimesKernel(
|
if (!PeelSmallPrimesKernel(
|
||||||
composite,
|
composite,
|
||||||
@@ -1597,7 +1581,9 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
bLocal >>= BitOperations.TrailingZeroCount(bLocal);
|
bLocal >>= BitOperations.TrailingZeroCount(bLocal);
|
||||||
if (aLocal > bLocal)
|
if (aLocal > bLocal)
|
||||||
{
|
{
|
||||||
(aLocal, bLocal) = (bLocal, aLocal);
|
ulong temp = aLocal;
|
||||||
|
aLocal = bLocal;
|
||||||
|
bLocal = temp;
|
||||||
}
|
}
|
||||||
|
|
||||||
bLocal -= aLocal;
|
bLocal -= aLocal;
|
||||||
@@ -1608,10 +1594,15 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool Pow2EqualsOneKernel(ulong exponent, in MontgomeryDivisorData divisor)
|
||||||
|
{
|
||||||
|
return exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false) == 1UL;
|
||||||
|
}
|
||||||
|
|
||||||
private static ulong CalculateByDoublingKernel(ulong prime)
|
private static ulong CalculateByDoublingKernel(ulong prime)
|
||||||
{
|
{
|
||||||
ulong order = 1UL;
|
ulong order = 1UL;
|
||||||
ulong value = 2UL; // Candidate primes exceed two, so the modulo would always return two.
|
ulong value = 2UL % prime;
|
||||||
|
|
||||||
while (value != 1UL)
|
while (value != 1UL)
|
||||||
{
|
{
|
||||||
@@ -1629,11 +1620,10 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
|
|
||||||
private static GpuPow2ModStatus TryPow2ModBatchInternal(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
|
private static GpuPow2ModStatus TryPow2ModBatchInternal(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> remainders)
|
||||||
{
|
{
|
||||||
// Residue batches are never empty on production workloads, so skip the zero-length success shortcut.
|
if (exponents.Length == 0)
|
||||||
// if (exponents.Length == 0)
|
{
|
||||||
// {
|
return GpuPow2ModStatus.Success;
|
||||||
// return GpuPow2ModStatus.Success;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
if (remainders.Length < exponents.Length)
|
if (remainders.Length < exponents.Length)
|
||||||
{
|
{
|
||||||
@@ -1709,30 +1699,27 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
private static void ComputePow2ModCpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
|
private static void ComputePow2ModCpu(ReadOnlySpan<ulong> exponents, ulong prime, in MontgomeryDivisorData divisorData, Span<ulong> results)
|
||||||
{
|
{
|
||||||
int length = exponents.Length;
|
int length = exponents.Length;
|
||||||
ulong modulus = divisorData.Modulus;
|
|
||||||
_ = prime; // modulus and divisorData both describe the same q; retain the parameter to preserve the public signature.
|
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
results[i] = exponents[i].Pow2ModWindowedCpu(modulus);
|
results[i] = Pow2ModCpu(exponents[i], prime, divisorData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryComputeOnGpuWide(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> results)
|
private static bool TryComputeOnGpuWide(ReadOnlySpan<UInt128> exponents, UInt128 prime, Span<UInt128> results)
|
||||||
{
|
{
|
||||||
int length = exponents.Length;
|
int length = exponents.Length;
|
||||||
// Production launches always carry work, so keep the empty-batch shortcut commented out.
|
if (length == 0)
|
||||||
// if (length == 0)
|
{
|
||||||
// {
|
return true;
|
||||||
// return true;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
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);
|
||||||
@@ -1769,6 +1756,7 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
|
|
||||||
exponentBuffer.Dispose();
|
exponentBuffer.Dispose();
|
||||||
remainderBuffer.Dispose();
|
remainderBuffer.Dispose();
|
||||||
|
execution.Dispose();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -1778,8 +1766,6 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
// Small batches use the stack-allocated fast path, leaving the rented arrays null. Retain the guards so pooled
|
|
||||||
// buffers are only returned when they were actually leased.
|
|
||||||
if (rentedExponents is not null)
|
if (rentedExponents is not null)
|
||||||
{
|
{
|
||||||
ArrayPool<GpuUInt128>.Shared.Return(rentedExponents, clearArray: false);
|
ArrayPool<GpuUInt128>.Shared.Return(rentedExponents, clearArray: false);
|
||||||
@@ -1790,7 +1776,6 @@ 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1804,6 +1789,16 @@ internal static partial class PrimeOrderGpuHeuristics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ulong Pow2ModCpu(ulong exponent, ulong modulus, in MontgomeryDivisorData divisorData)
|
||||||
|
{
|
||||||
|
if (modulus <= 1UL)
|
||||||
|
{
|
||||||
|
return 0UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false);
|
||||||
|
}
|
||||||
|
|
||||||
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
private static Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> GetPow2ModKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
return Pow2ModKernelCache.GetOrAdd(accelerator, static accel =>
|
return Pow2ModKernelCache.GetOrAdd(accelerator, static accel =>
|
||||||
@@ -1816,17 +1811,8 @@ 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 modulus = divisor.Modulus;
|
|
||||||
// The by-divisor heuristics call this kernel only with odd prime moduli, so the defensive branch stays commented to
|
|
||||||
// document the invariant without spending a branch in the kernel.
|
|
||||||
// if (modulus <= 1UL || (modulus & 1UL) == 0UL)
|
|
||||||
// {
|
|
||||||
// remainders[index] = 0UL;
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong exponent = exponents[index];
|
ulong exponent = exponents[index];
|
||||||
remainders[index] = exponent.Pow2ModWindowedGpu(modulus);
|
remainders[index] = exponent.Pow2MontgomeryModWindowed(divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
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,16 +12,14 @@ public interface IMersenneNumberDivisorByDivisorTester
|
|||||||
|
|
||||||
ulong GetAllowedMaxDivisor(ulong prime);
|
ulong GetAllowedMaxDivisor(ulong prime);
|
||||||
|
|
||||||
bool IsPrime(ulong prime, out bool divisorsExhausted, TimeSpan? timeLimit = null);
|
bool IsPrime(ulong prime, out bool divisorsExhausted);
|
||||||
|
|
||||||
IDivisorScanSession CreateDivisorSession();
|
IDivisorScanSession CreateDivisorSession();
|
||||||
|
|
||||||
void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
|
void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
|
||||||
|
|
||||||
public interface IDivisorScanSession
|
public interface IDivisorScanSession : IDisposable
|
||||||
{
|
{
|
||||||
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
|
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits);
|
||||||
|
|
||||||
void Dispose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Buffers.Binary;
|
using System.Buffers.Binary;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -19,12 +18,6 @@ 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()
|
||||||
@@ -74,27 +67,23 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exponent and divisor validation happens during scheduling, so the defensive guard remains commented out to keep the
|
if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
||||||
// hot path branch-free.
|
{
|
||||||
// if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
return false;
|
||||||
// {
|
}
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Production scans never revisit divisors in the small-cycle snapshot, so skip the legacy lookup here. Factoring and
|
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||||
// diagnostics can query TryMatchSmallCycleForFactoring when they need those cached values.
|
{
|
||||||
// if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
var small = Shared._smallCycles;
|
||||||
// {
|
if (small is not null)
|
||||||
// var small = Shared._smallCycles;
|
{
|
||||||
// if (small is not null)
|
ulong cached = small[(int)divisor];
|
||||||
// {
|
if (cached != 0UL)
|
||||||
// ulong cached = small[(int)divisor];
|
{
|
||||||
// if (cached != 0UL)
|
return cached == exponent;
|
||||||
// {
|
}
|
||||||
// return cached == exponent;
|
}
|
||||||
// }
|
}
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (TryCalculateCycleLengthHeuristic(divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
if (TryCalculateCycleLengthHeuristic(divisor, divisorData, out ulong heuristicCycle) && heuristicCycle != 0UL)
|
||||||
{
|
{
|
||||||
@@ -102,7 +91,6 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<ulong, FactorCacheEntry>? cache = factorCache;
|
Dictionary<ulong, FactorCacheEntry>? cache = factorCache;
|
||||||
// Each by-divisor scan session starts with a null cache and fills it lazily, so keep the allocation guard in place.
|
|
||||||
if (cache is null)
|
if (cache is null)
|
||||||
{
|
{
|
||||||
cache = new Dictionary<ulong, FactorCacheEntry>(8);
|
cache = new Dictionary<ulong, FactorCacheEntry>(8);
|
||||||
@@ -114,7 +102,7 @@ public class MersenneDivisorCycles
|
|||||||
return cycleLength == exponent;
|
return cycleLength == exponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Pow2ModWindowedCpu(divisorData.Modulus) == 1UL;
|
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
public static bool CycleEqualsExponentForMersenneCandidate(ulong divisor, in MontgomeryDivisorData divisorData, ulong exponent)
|
||||||
@@ -124,70 +112,35 @@ public class MersenneDivisorCycles
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Candidate divisors on the production path sit well above the precomputed snapshot, so skip the small-cycle lookup
|
if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
||||||
// here and leave factoring scenarios to call TryMatchSmallCycleForFactoring when necessary.
|
|
||||||
// if (divisor <= PerfectNumberConstants.MaxQForDivisorCycles)
|
|
||||||
// {
|
|
||||||
// MersenneDivisorCycles shared = Shared;
|
|
||||||
// ulong[]? small = shared._smallCycles;
|
|
||||||
// if (small is not null)
|
|
||||||
// {
|
|
||||||
// ulong cached = small[(int)divisor];
|
|
||||||
// if (cached != 0UL)
|
|
||||||
// {
|
|
||||||
// return cached == exponent;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
return exponent.Pow2ModWindowedCpu(divisorData.Modulus) == 1UL;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static bool TryMatchSmallCycleForFactoring(ulong divisor, ulong exponent, out ulong cycleLength)
|
|
||||||
{
|
{
|
||||||
if (divisor > PerfectNumberConstants.MaxQForDivisorCycles)
|
MersenneDivisorCycles shared = Shared;
|
||||||
|
ulong[]? small = shared._smallCycles;
|
||||||
|
if (small is not null)
|
||||||
{
|
{
|
||||||
// Factoring helpers probe arbitrary divisors, so anything above the snapshot limit legitimately misses here.
|
|
||||||
cycleLength = 0UL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong[]? small = Shared._smallCycles;
|
|
||||||
if (small is null)
|
|
||||||
{
|
|
||||||
// Snapshot recycling during diagnostics can temporarily clear the table; surface the miss so callers recompute.
|
|
||||||
cycleLength = 0UL;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong cached = small[(int)divisor];
|
ulong cached = small[(int)divisor];
|
||||||
if (cached == 0UL)
|
if (cached != 0UL)
|
||||||
{
|
{
|
||||||
// Only a subset of small divisors carry cached cycles; signal the miss so factoring falls back to the GPU helper.
|
return cached == exponent;
|
||||||
cycleLength = 0UL;
|
}
|
||||||
return false;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleLength = cached;
|
return exponent.Pow2MontgomeryModWindowed(divisorData, keepMontgomery: false) == 1UL;
|
||||||
return cached == exponent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
private static bool IsValidMersenneDivisorCandidate(ulong divisor, ulong exponent)
|
||||||
{
|
{
|
||||||
// Production workloads pre-filter invalid inputs, so skip the defensive guards here. Factoring and diagnostics should
|
if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
||||||
// validate inputs before calling into the candidate path.
|
{
|
||||||
// if (exponent <= 1UL || divisor <= 1UL || (divisor & 1UL) == 0UL)
|
return false;
|
||||||
// {
|
}
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
UInt128 step = (UInt128)exponent << 1;
|
UInt128 step = (UInt128)exponent << 1;
|
||||||
// The overflow bail-out is handled by factoring-specific call sites; keep the historical branch commented to document the
|
if (step == UInt128.Zero)
|
||||||
// legacy behavior.
|
{
|
||||||
// if (step == UInt128.Zero)
|
return false;
|
||||||
// {
|
}
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
UInt128 adjusted = (UInt128)divisor - UInt128.One;
|
UInt128 adjusted = (UInt128)divisor - UInt128.One;
|
||||||
return adjusted % step == UInt128.Zero;
|
return adjusted % step == UInt128.Zero;
|
||||||
@@ -196,7 +149,7 @@ public class MersenneDivisorCycles
|
|||||||
public static IEnumerable<(ulong divisor, ulong cycleLength)> EnumerateStream(Stream compressor)
|
public static IEnumerable<(ulong divisor, ulong cycleLength)> EnumerateStream(Stream compressor)
|
||||||
{
|
{
|
||||||
// Binary pairs: (ulong divisor, ulong cycle)
|
// Binary pairs: (ulong divisor, ulong cycle)
|
||||||
var reader = new BinaryReader(compressor, Encoding.UTF8, leaveOpen: true);
|
using var reader = new BinaryReader(compressor, Encoding.UTF8, leaveOpen: true);
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
ulong d, c;
|
ulong d, c;
|
||||||
@@ -207,7 +160,6 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
catch (EndOfStreamException)
|
catch (EndOfStreamException)
|
||||||
{
|
{
|
||||||
reader.Dispose();
|
|
||||||
yield break;
|
yield break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,14 +234,14 @@ public class MersenneDivisorCycles
|
|||||||
return (UInt128)cycle;
|
return (UInt128)cycle;
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 wideOrder = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResultWide wideResult = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (wideOrder != UInt128.Zero)
|
if (wideResult.Order != UInt128.Zero)
|
||||||
{
|
{
|
||||||
return wideOrder;
|
return wideResult.Order;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Otherwise, find order of 2 mod divisor
|
// Otherwise, find order of 2 mod divisor
|
||||||
@@ -321,86 +273,63 @@ public class MersenneDivisorCycles
|
|||||||
{
|
{
|
||||||
cycleLength = 0UL;
|
cycleLength = 0UL;
|
||||||
|
|
||||||
// The by-divisor scanner only supplies odd candidates of the form 2*k*exponent + 1,
|
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||||
// so this power-of-two fast-path never triggers on that call path.
|
{
|
||||||
// The historical fast-path is left here for reference but commented out because it is unreachable
|
cycleLength = 1UL;
|
||||||
// on the EvenPerfectBitScanner pipeline.
|
return true;
|
||||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
}
|
||||||
// {
|
|
||||||
// cycleLength = 1UL;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// EvenPerfectBitScanner filters exponents <= 3 before invoking this helper and each
|
if (divisor <= 3UL || exponent <= 1UL)
|
||||||
// divisor candidate is at least 2 * exponent + 1, so this guard never matches.
|
{
|
||||||
// The fallback calculation is therefore unreachable on the active call path.
|
cycleLength = CalculateCycleLength(divisor, divisorData);
|
||||||
// if (divisor <= 3UL || exponent <= 1UL)
|
return true;
|
||||||
// {
|
}
|
||||||
// cycleLength = CalculateCycleLength(divisor, divisorData);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (computedOrder != 0UL)
|
if (orderResult.Order != 0UL)
|
||||||
{
|
{
|
||||||
cycleLength = computedOrder;
|
cycleLength = orderResult.Order;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong phi = divisor - 1UL;
|
ulong phi = divisor - 1UL;
|
||||||
// Divisors reach this helper only when they exceed the cached table, which means
|
if (phi == 0UL)
|
||||||
// divisor >= 2 * exponent + 1 and phi cannot underflow to zero here.
|
{
|
||||||
// The defensive underflow guard is kept for posterity but disabled for the EvenPerfectBitScanner path.
|
return false;
|
||||||
// if (phi == 0UL)
|
}
|
||||||
// {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
int twoCount = BitOperations.TrailingZeroCount(phi);
|
int twoCount = BitOperations.TrailingZeroCount(phi);
|
||||||
ulong reducedPhi = phi >> twoCount;
|
ulong reducedPhi = phi >> twoCount;
|
||||||
// Every divisor candidate generated by the by-divisor scan satisfies q ≡ 1 (mod 2 * exponent),
|
if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
||||||
// ensuring reducedPhi keeps a factor of exponent and remains non-zero in this context.
|
{
|
||||||
// The divisibility check is therefore unreachable for the current caller and is commented out.
|
return false;
|
||||||
// if (reducedPhi == 0UL || (reducedPhi % exponent) != 0UL)
|
}
|
||||||
// {
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong k = reducedPhi / exponent;
|
ulong k = reducedPhi / exponent;
|
||||||
// reducedPhi always contains at least one factor of exponent, so dividing by exponent leaves k >= 1.
|
if (k == 0UL)
|
||||||
// if (k == 0UL)
|
{
|
||||||
// {
|
return false;
|
||||||
// return false;
|
}
|
||||||
// }
|
|
||||||
|
|
||||||
Dictionary<ulong, int> factorCounts = AcquireFactorCountDictionary(8);
|
Dictionary<ulong, int> factorCounts = new(8);
|
||||||
try
|
if (twoCount > 0)
|
||||||
{
|
{
|
||||||
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleLength = ReduceOrder(divisorData, phi, factorCounts, exponent);
|
cycleLength = ReduceOrder(divisorData, phi, factorCounts);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
ReleaseFactorCountDictionary(factorCounts);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool AccumulateFactors(
|
private static bool AccumulateFactors(
|
||||||
ulong value,
|
ulong value,
|
||||||
@@ -413,22 +342,14 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
FactorCacheEntry cachedFactorization;
|
if (!TryGetFactorization(value, cache, cacheResult, out FactorCacheEntry factorization))
|
||||||
PrimeFactorPartialResult? lease = null;
|
|
||||||
bool fromCache;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!TryGetFactorization(value, cache, cacheResult, out cachedFactorization, out lease, out fromCache))
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromCache)
|
ulong[] primes = factorization.Primes;
|
||||||
{
|
byte[] exponents = factorization.Exponents;
|
||||||
ulong[] primes = cachedFactorization.Primes;
|
int length = factorization.Count;
|
||||||
byte[] exponents = cachedFactorization.Exponents;
|
|
||||||
int length = cachedFactorization.Count;
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
@@ -438,71 +359,27 @@ public class MersenneDivisorCycles
|
|||||||
return true;
|
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
|
|
||||||
{
|
|
||||||
lease?.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
factorization = default;
|
|
||||||
lease = null;
|
|
||||||
fromCache = false;
|
|
||||||
|
|
||||||
if (value <= 1UL)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cache is not null && cache.TryGetValue(value, out factorization))
|
if (cache is not null && cache.TryGetValue(value, out factorization))
|
||||||
{
|
{
|
||||||
fromCache = true;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<ulong, int> scratch = AcquireFactorScratchDictionary(8);
|
Dictionary<ulong, int> scratch = new(8);
|
||||||
try
|
|
||||||
{
|
|
||||||
if (!TryFactorIntoCountsInternal(value, scratch))
|
if (!TryFactorIntoCountsInternal(value, scratch))
|
||||||
{
|
{
|
||||||
factorization = default;
|
factorization = default!;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = scratch.Count;
|
int count = scratch.Count;
|
||||||
if (count == 0)
|
ulong[] primes = new ulong[count];
|
||||||
{
|
byte[] exponents = new byte[count];
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
PrimeFactorPartialResult result = PrimeFactorPartialResult.Rent(count);
|
|
||||||
lease = result;
|
|
||||||
|
|
||||||
Span<ulong> primes = result.PrimeWriteSpan;
|
|
||||||
Span<byte> exponents = result.ExponentWriteSpan;
|
|
||||||
int index = 0;
|
int index = 0;
|
||||||
foreach (KeyValuePair<ulong, int> entry in scratch)
|
foreach (KeyValuePair<ulong, int> entry in scratch)
|
||||||
{
|
{
|
||||||
@@ -511,22 +388,16 @@ public class MersenneDivisorCycles
|
|||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.CommitCount(index);
|
Array.Sort(primes, exponents);
|
||||||
result.Sort();
|
factorization = new FactorCacheEntry(primes, exponents, count);
|
||||||
|
|
||||||
if (cache is not null && cacheResult)
|
if (cache is not null && cacheResult)
|
||||||
{
|
{
|
||||||
FactorCacheEntry cacheEntry = result.ToCacheEntry();
|
cache[value] = factorization;
|
||||||
cache[value] = cacheEntry;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
finally
|
|
||||||
{
|
|
||||||
ReleaseFactorScratchDictionary(scratch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
private static bool TryFactorIntoCountsInternal(ulong value, Dictionary<ulong, int> counts)
|
||||||
{
|
{
|
||||||
@@ -600,22 +471,15 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ulong ReduceOrder(
|
private static ulong ReduceOrder(in MontgomeryDivisorData divisorData, ulong initialOrder, Dictionary<ulong, int> factorCounts)
|
||||||
in MontgomeryDivisorData divisorData,
|
|
||||||
ulong initialOrder,
|
|
||||||
Dictionary<ulong, int> factorCounts,
|
|
||||||
ulong targetOrder)
|
|
||||||
{
|
{
|
||||||
if (factorCounts.Count == 0 || initialOrder == targetOrder)
|
if (factorCounts.Count == 0)
|
||||||
{
|
{
|
||||||
return initialOrder;
|
return initialOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
int count = factorCounts.Count;
|
int count = factorCounts.Count;
|
||||||
bool rentFromPool = count >= PerfectNumberConstants.PooledArrayThreshold;
|
ulong[] primes = ArrayPool<ulong>.Shared.Rent(count);
|
||||||
ulong[] primes = rentFromPool
|
|
||||||
? ThreadLocalArrayPool<ulong>.Shared.Rent(count)
|
|
||||||
: new ulong[count];
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
int index = 0;
|
int index = 0;
|
||||||
@@ -641,16 +505,9 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
|
|
||||||
ulong candidate = order / prime;
|
ulong candidate = order / prime;
|
||||||
if (candidate.Pow2MontgomeryModWindowedCpu(divisorData, keepMontgomery: false) == 1UL)
|
if (candidate.Pow2MontgomeryModWindowed(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -662,10 +519,7 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (rentFromPool)
|
ArrayPool<ulong>.Shared.Return(primes, clearArray: false);
|
||||||
{
|
|
||||||
ThreadLocalArrayPool<ulong>.Shared.Return(primes, clearArray: false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -738,53 +592,6 @@ 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];
|
||||||
@@ -830,7 +637,7 @@ public class MersenneDivisorCycles
|
|||||||
// Start fresh to avoid mixing formats
|
// Start fresh to avoid mixing formats
|
||||||
outputStream.SetLength(0L);
|
outputStream.SetLength(0L);
|
||||||
outputStream.Position = 0L;
|
outputStream.Position = 0L;
|
||||||
var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false);
|
using var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false);
|
||||||
for (var taskIndex = 0; taskIndex < threads; taskIndex++)
|
for (var taskIndex = 0; taskIndex < threads; taskIndex++)
|
||||||
{
|
{
|
||||||
ulong threadStart = start + (ulong)taskIndex * blockSize;
|
ulong threadStart = start + (ulong)taskIndex * blockSize;
|
||||||
@@ -851,11 +658,7 @@ public class MersenneDivisorCycles
|
|||||||
tasks[taskIndex] = Task.Run(() =>
|
tasks[taskIndex] = Task.Run(() =>
|
||||||
{
|
{
|
||||||
ulong rangeLength = localEnd - localStart + 1UL;
|
ulong rangeLength = localEnd - localStart + 1UL;
|
||||||
int localLength = checked((int)rangeLength);
|
(ulong divisor, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent(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
|
||||||
@@ -903,16 +706,12 @@ public class MersenneDivisorCycles
|
|||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
if (rentLocalCycles)
|
ArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
|
||||||
{
|
|
||||||
ThreadLocalArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Task.WaitAll(tasks);
|
Task.WaitAll(tasks);
|
||||||
writer.Dispose();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void GenerateGpu(string path, ulong maxDivisor, int batchSize = 1_000_000, long skipCount = 0L, long nextPosition = 0L)
|
public static void GenerateGpu(string path, ulong maxDivisor, int batchSize = 1_000_000, long skipCount = 0L, long nextPosition = 0L)
|
||||||
@@ -922,7 +721,7 @@ public class MersenneDivisorCycles
|
|||||||
ulong start = 3UL;
|
ulong start = 3UL;
|
||||||
var locker = new object();
|
var locker = new object();
|
||||||
|
|
||||||
var pool = ThreadLocalArrayPool<ulong>.Shared;
|
var pool = ArrayPool<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;
|
||||||
@@ -949,7 +748,7 @@ public class MersenneDivisorCycles
|
|||||||
outputStream.Position = 0L;
|
outputStream.Position = 0L;
|
||||||
}
|
}
|
||||||
|
|
||||||
var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false);
|
using var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
while (skipCount > 0L && start <= maxDivisor)
|
while (skipCount > 0L && start <= maxDivisor)
|
||||||
@@ -1024,7 +823,6 @@ public class MersenneDivisorCycles
|
|||||||
pool.Return(outCycles, clearArray: false);
|
pool.Return(outCycles, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
writer.Dispose();
|
|
||||||
stream.Dispose();
|
stream.Dispose();
|
||||||
lease.Dispose();
|
lease.Dispose();
|
||||||
|
|
||||||
@@ -1128,34 +926,29 @@ 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)
|
||||||
{
|
{
|
||||||
// The by-divisor scanner only supplies odd candidates of the form 2*k*exponent + 1,
|
if ((divisor & (divisor - 1UL)) == 0UL)
|
||||||
// so this power-of-two fast-path never triggers on that call path.
|
{
|
||||||
// The early exit is retained for reference but commented out because it is unreachable on the scanner path.
|
cycleLength = 1UL;
|
||||||
// if ((divisor & (divisor - 1UL)) == 0UL)
|
return true;
|
||||||
// {
|
}
|
||||||
// cycleLength = 1UL;
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// By the divisor-by-divisor pipeline, candidates have the shape 2 * exponent * k + 1 with exponent >= 5,
|
if (divisor <= 3UL)
|
||||||
// so the divisor never drops below 11 on this call path.
|
{
|
||||||
// if (divisor <= 3UL)
|
cycleLength = CalculateCycleLengthFallback(divisor);
|
||||||
// {
|
return true;
|
||||||
// cycleLength = CalculateCycleLengthFallback(divisor);
|
}
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
if (PrimeTester.IsPrimeInternal(divisor, CancellationToken.None))
|
||||||
{
|
{
|
||||||
ulong computedOrder = PrimeOrderCalculator.Calculate(
|
PrimeOrderCalculator.PrimeOrderResult orderResult = PrimeOrderCalculator.Calculate(
|
||||||
divisor,
|
divisor,
|
||||||
previousOrder: null,
|
previousOrder: null,
|
||||||
divisorData,
|
divisorData,
|
||||||
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
PrimeOrderCalculator.PrimeOrderSearchConfig.HeuristicDefault,
|
||||||
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
PrimeOrderCalculator.PrimeOrderHeuristicDevice.Cpu);
|
||||||
if (computedOrder != 0UL)
|
if (orderResult.Order != 0UL)
|
||||||
{
|
{
|
||||||
cycleLength = computedOrder;
|
cycleLength = orderResult.Order;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,7 @@ 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)
|
||||||
{
|
{
|
||||||
@@ -113,12 +112,9 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
ulong maxPrime = 0UL;
|
ulong maxPrime = 0UL;
|
||||||
int primeWriteIndex = 0;
|
int primeWriteIndex = 0;
|
||||||
|
|
||||||
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
|
using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
|
||||||
// We don't need to additionally check it.
|
bool hasPrime = primeEnumerator.MoveNext();
|
||||||
|
ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
|
||||||
// using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
|
|
||||||
// bool hasPrime = primeEnumerator.MoveNext();
|
|
||||||
// ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
|
|
||||||
|
|
||||||
for (int index = startIndex; index < candidateCount; index++)
|
for (int index = startIndex; index < candidateCount; index++)
|
||||||
{
|
{
|
||||||
@@ -131,27 +127,23 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
|
while (hasPrime && currentPrime < candidate)
|
||||||
// We don't need to additionally check it.
|
{
|
||||||
|
hasPrime = primeEnumerator.MoveNext();
|
||||||
|
if (!hasPrime)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// while (hasPrime && currentPrime < candidate)
|
currentPrime = primeEnumerator.Current;
|
||||||
// {
|
}
|
||||||
// hasPrime = primeEnumerator.MoveNext();
|
|
||||||
// if (!hasPrime)
|
|
||||||
// {
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// currentPrime = primeEnumerator.Current;
|
if (!hasPrime || currentPrime != candidate)
|
||||||
// }
|
{
|
||||||
|
markComposite();
|
||||||
// if (!hasPrime || currentPrime != candidate)
|
printResult(candidate, false, false, false);
|
||||||
// if (!Number.IsPrime(candidate))
|
continue;
|
||||||
// {
|
}
|
||||||
// markComposite();
|
|
||||||
// printResult(candidate, false, false, false);
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
|
|
||||||
primesSpan[primeWriteIndex++] = candidate;
|
primesSpan[primeWriteIndex++] = candidate;
|
||||||
|
|
||||||
@@ -180,13 +172,11 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// EvenPerfectBitScanner removes candidates <= 1 before reaching this point,
|
if (maxPrime <= 1UL)
|
||||||
// so this guard never triggers during production runs.
|
{
|
||||||
// if (maxPrime <= 1UL)
|
Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
|
||||||
// {
|
return;
|
||||||
// Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
tester.ConfigureFromMaxPrime(maxPrime);
|
tester.ConfigureFromMaxPrime(maxPrime);
|
||||||
|
|
||||||
@@ -246,7 +236,7 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
|
|
||||||
void ProcessPrime(ulong prime)
|
void ProcessPrime(ulong prime)
|
||||||
{
|
{
|
||||||
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted, primeTestLimit);
|
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
|
||||||
|
|
||||||
if (!isPrime)
|
if (!isPrime)
|
||||||
{
|
{
|
||||||
@@ -259,52 +249,21 @@ public static class MersenneNumberDivisorByDivisorTester
|
|||||||
printResult(prime, true, divisorsExhausted, true);
|
printResult(prime, true, divisorsExhausted, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (workerCount > filteredPrimes.Count)
|
if (workerCount == 1)
|
||||||
{
|
|
||||||
workerCount = filteredPrimes.Count;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (workerCount <= 1)
|
|
||||||
{
|
{
|
||||||
foreach (ulong prime in filteredPrimes)
|
foreach (ulong prime in filteredPrimes)
|
||||||
{
|
{
|
||||||
ProcessPrime(prime);
|
ProcessPrime(prime);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
(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;
|
ParallelOptions options = new()
|
||||||
if (workerIndex < remaining)
|
|
||||||
{
|
|
||||||
groupSize++;
|
|
||||||
}
|
|
||||||
|
|
||||||
startIndex = groupStartIndex;
|
|
||||||
int endIndex = startIndex + groupSize;
|
|
||||||
workerRanges[workerIndex] = (startIndex, endIndex);
|
|
||||||
groupStartIndex = endIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
ParallelOptions parallelOptions = new()
|
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = workerCount
|
MaxDegreeOfParallelism = workerCount
|
||||||
};
|
};
|
||||||
|
|
||||||
Parallel.ForEach(workerRanges, parallelOptions, range =>
|
Parallel.ForEach(filteredPrimes, options, ProcessPrime);
|
||||||
{
|
|
||||||
for (int candidateIndex = range.Start; candidateIndex < range.End; candidateIndex++)
|
|
||||||
{
|
|
||||||
ProcessPrime(filteredPrimes[candidateIndex]);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -371,6 +371,10 @@ public static class MersennePrimeFactorTester
|
|||||||
return 2UL;
|
return 2UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rng = new Random(1234567); // TODO: Replace this RNG with the deterministic span-based
|
||||||
|
// sequence measured faster in PollardRhoBenchmarks so we avoid
|
||||||
|
// per-call Random allocations.
|
||||||
|
|
||||||
ulong c, d, x, y, diff;
|
ulong c, d, x, y, diff;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -379,8 +383,8 @@ public static class MersennePrimeFactorTester
|
|||||||
return 0UL;
|
return 0UL;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (DeterministicRandom.NextUInt64() % (n - 1UL)) + 1UL;
|
c = (ulong)rng.Next(2, int.MaxValue);
|
||||||
x = (DeterministicRandom.NextUInt64() % (n - 2UL)) + 2UL;
|
x = (ulong)rng.Next(2, int.MaxValue);
|
||||||
y = x;
|
y = x;
|
||||||
d = 1UL;
|
d = 1UL;
|
||||||
|
|
||||||
@@ -519,6 +523,9 @@ public static class MersennePrimeFactorTester
|
|||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var rng = new Random(1234567); // TODO: Replace with the deterministic UInt128 sequence from
|
||||||
|
// PollardRho128Benchmarks so we eliminate Random allocations.
|
||||||
|
|
||||||
UInt128 c, d, x, y, diff;
|
UInt128 c, d, x, y, diff;
|
||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
@@ -527,8 +534,8 @@ public static class MersennePrimeFactorTester
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
c = (DeterministicRandom.NextUInt128() % (n - 1)) + 1;
|
c = (UInt128)rng.NextInt64(2, long.MaxValue);
|
||||||
x = (DeterministicRandom.NextUInt128() % (n - 2)) + 2;
|
x = (UInt128)rng.NextInt64(2, long.MaxValue);
|
||||||
y = x;
|
y = x;
|
||||||
d = 1;
|
d = 1;
|
||||||
|
|
||||||
|
|||||||
@@ -45,4 +45,32 @@ 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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,243 +0,0 @@
|
|||||||
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,94 +0,0 @@
|
|||||||
using System;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core
|
|
||||||
{
|
|
||||||
public sealed class PartialFactorResult
|
|
||||||
{
|
|
||||||
[ThreadStatic]
|
|
||||||
private static PartialFactorResult? s_poolHead;
|
|
||||||
|
|
||||||
public static readonly PartialFactorResult Empty = new()
|
|
||||||
{
|
|
||||||
Cofactor = 1UL,
|
|
||||||
FullyFactored = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
public FactorEntry[]? Factors;
|
|
||||||
public ulong Cofactor;
|
|
||||||
public bool FullyFactored;
|
|
||||||
public int Count;
|
|
||||||
|
|
||||||
private PartialFactorResult? _next;
|
|
||||||
private bool _factorsFromPool;
|
|
||||||
|
|
||||||
private PartialFactorResult()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PartialFactorResult Rent(in FactorEntry[]? factors, ulong cofactor, bool fullyFactored, int count)
|
|
||||||
{
|
|
||||||
PartialFactorResult? instance = s_poolHead;
|
|
||||||
if (instance is null)
|
|
||||||
{
|
|
||||||
instance = new PartialFactorResult();
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
s_poolHead = instance._next;
|
|
||||||
}
|
|
||||||
|
|
||||||
FactorEntry[]? factorArray = factors;
|
|
||||||
instance._next = null;
|
|
||||||
instance.Factors = factorArray;
|
|
||||||
instance.Cofactor = cofactor;
|
|
||||||
instance.FullyFactored = fullyFactored;
|
|
||||||
instance.Count = count;
|
|
||||||
instance._factorsFromPool = factorArray is not null && factorArray.Length >= PerfectNumberConstants.PooledArrayThreshold;
|
|
||||||
return instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PartialFactorResult WithAdditionalPrime(ulong prime)
|
|
||||||
{
|
|
||||||
FactorEntry[]? source = Factors;
|
|
||||||
if (source is null || Count == 0)
|
|
||||||
{
|
|
||||||
FactorEntry[] single = new FactorEntry[1];
|
|
||||||
single[0] = new FactorEntry(prime);
|
|
||||||
return Rent(single, 1UL, true, 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);
|
|
||||||
extended[Count] = new FactorEntry(prime);
|
|
||||||
Span<FactorEntry> span = extended.AsSpan(0, newCount);
|
|
||||||
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
|
|
||||||
return Rent(extended, 1UL, true, newCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
if (this == Empty)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,6 @@ public static class PerfectNumberConstants
|
|||||||
public const int MaxQForDivisorCycles = 4_000_000;
|
public const int MaxQForDivisorCycles = 4_000_000;
|
||||||
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
public static readonly uint PrimesLimit = 1_000_000; //(ulong)Array.MaxLength;// 1_000_000;
|
||||||
public const int MaxOddPowersCount = 128;
|
public const int MaxOddPowersCount = 128;
|
||||||
public const int PooledArrayThreshold = 64;
|
|
||||||
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
// TODO: Load these limits from the benchmark-driven configuration so CPU and GPU scans stay aligned with the optimal
|
||||||
// divisor-cycle datasets we generate offline.
|
// divisor-cycle datasets we generate offline.
|
||||||
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
// TODO: Promote these magic numbers into a runtime profile derived from EvenPerfectBitScanner.Benchmarks so we can retune
|
||||||
|
|||||||
@@ -22,7 +22,4 @@
|
|||||||
<InternalsVisibleTo Include="EvenPerfectBitScanner.Benchmarks" />
|
<InternalsVisibleTo Include="EvenPerfectBitScanner.Benchmarks" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Remove=".temp.cs" />
|
|
||||||
</ItemGroup>
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -1,150 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Buffers;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core
|
|
||||||
{
|
|
||||||
internal sealed class PrimeFactorPartialResult
|
|
||||||
{
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -9,15 +9,30 @@ namespace PerfectNumbers.Core;
|
|||||||
|
|
||||||
internal static partial class PrimeOrderCalculator
|
internal static partial class PrimeOrderCalculator
|
||||||
{
|
{
|
||||||
public static UInt128 Calculate(
|
internal readonly struct PrimeOrderResultWide
|
||||||
|
{
|
||||||
|
public PrimeOrderResultWide(PrimeOrderStatus status, UInt128 order)
|
||||||
|
{
|
||||||
|
Status = status;
|
||||||
|
Order = order;
|
||||||
|
}
|
||||||
|
|
||||||
|
public PrimeOrderStatus Status { get; }
|
||||||
|
|
||||||
|
public UInt128 Order { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PrimeOrderResultWide Calculate(UInt128 prime, UInt128? previousOrder, PrimeOrderSearchConfig config)
|
||||||
|
=> Calculate(prime, previousOrder, config, PrimeOrderHeuristicDevice.Gpu);
|
||||||
|
|
||||||
|
public static PrimeOrderResultWide Calculate(
|
||||||
UInt128 prime,
|
UInt128 prime,
|
||||||
UInt128? previousOrder,
|
UInt128? previousOrder,
|
||||||
PrimeOrderSearchConfig config,
|
PrimeOrderSearchConfig config,
|
||||||
PrimeOrderHeuristicDevice device)
|
PrimeOrderHeuristicDevice device)
|
||||||
{
|
{
|
||||||
var scope = UsePow2Mode(device);
|
using var scope = UsePow2Mode(device);
|
||||||
MontgomeryDivisorData divisorData;
|
MontgomeryDivisorData divisorData;
|
||||||
UInt128 result;
|
|
||||||
if (prime <= ulong.MaxValue)
|
if (prime <= ulong.MaxValue)
|
||||||
{
|
{
|
||||||
ulong? previous = null;
|
ulong? previous = null;
|
||||||
@@ -36,49 +51,41 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
ulong prime64 = (ulong)prime;
|
ulong prime64 = (ulong)prime;
|
||||||
divisorData = MontgomeryDivisorData.FromModulus(prime64);
|
divisorData = MontgomeryDivisorData.FromModulus(prime64);
|
||||||
ulong order64 = Calculate(prime64, previous, divisorData, config, device);
|
PrimeOrderResult result = Calculate(prime64, previous, divisorData, config, device);
|
||||||
result = order64 == 0UL ? UInt128.Zero : (UInt128)order64;
|
return new PrimeOrderResultWide(result.Status, result.Order);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
divisorData = default;
|
divisorData = default;
|
||||||
result = CalculateWideInternal(prime, previousOrder, divisorData, config);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
scope.Dispose();
|
return CalculateWideInternal(prime, previousOrder, divisorData, config);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
private static PrimeOrderResultWide CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config)
|
||||||
{
|
{
|
||||||
if (prime <= UInt128.One)
|
if (prime <= UInt128.One)
|
||||||
{
|
{
|
||||||
return UInt128.One;
|
return new PrimeOrderResultWide(PrimeOrderStatus.Found, UInt128.One);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prime == (UInt128)3UL)
|
if (prime == (UInt128)3UL)
|
||||||
{
|
{
|
||||||
return (UInt128)2UL;
|
return new PrimeOrderResultWide(PrimeOrderStatus.Found, (UInt128)2UL);
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 phi = prime - UInt128.One;
|
UInt128 phi = prime - UInt128.One;
|
||||||
var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
using var debugScope = UseDebugLogging(!IsGpuHeuristicDevice);
|
||||||
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
PartialFactorResult128 phiFactors = PartialFactorWide(phi, config);
|
||||||
UInt128 result;
|
|
||||||
if (phiFactors.Factors is null)
|
if (phiFactors.Factors is null)
|
||||||
{
|
{
|
||||||
result = FinishStrictlyWide(prime, divisorData);
|
return FinishStrictlyWide(prime, divisorData, config.Mode);
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
debugScope.Dispose();
|
return RunHeuristicPipelineWide(prime, previousOrder, divisorData, config, phi, phiFactors);
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 RunHeuristicPipelineWide(
|
private static PrimeOrderResultWide RunHeuristicPipelineWide(
|
||||||
UInt128 prime,
|
UInt128 prime,
|
||||||
UInt128? previousOrder,
|
UInt128? previousOrder,
|
||||||
in MontgomeryDivisorData divisorData,
|
in MontgomeryDivisorData divisorData,
|
||||||
@@ -88,7 +95,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
|
||||||
{
|
{
|
||||||
return phi;
|
return new PrimeOrderResultWide(PrimeOrderStatus.Found, phi);
|
||||||
}
|
}
|
||||||
|
|
||||||
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
UInt128 candidateOrder = InitializeStartingOrderWide(prime, phi, divisorData);
|
||||||
@@ -96,25 +103,27 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
if (TryConfirmOrderWide(prime, candidateOrder, divisorData, config))
|
||||||
{
|
{
|
||||||
return candidateOrder;
|
return new PrimeOrderResultWide(PrimeOrderStatus.Found, candidateOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config.Mode == PrimeOrderMode.Strict)
|
if (config.Mode == PrimeOrderMode.Strict)
|
||||||
{
|
{
|
||||||
return FinishStrictlyWide(prime, divisorData);
|
return FinishStrictlyWide(prime, divisorData, PrimeOrderMode.Strict);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
if (TryHeuristicFinishWide(prime, candidateOrder, previousOrder, divisorData, config, out UInt128 order))
|
||||||
{
|
{
|
||||||
return order;
|
return new PrimeOrderResultWide(PrimeOrderStatus.Found, order);
|
||||||
}
|
}
|
||||||
|
|
||||||
return candidateOrder;
|
return FinishStrictlyWide(prime, divisorData, config.Mode);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static UInt128 FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData)
|
private static PrimeOrderResultWide FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData, PrimeOrderMode mode)
|
||||||
{
|
{
|
||||||
return CalculateByFactorizationWide(prime, divisorData);
|
UInt128 strictOrder = CalculateByFactorizationWide(prime, divisorData);
|
||||||
|
PrimeOrderStatus status = mode == PrimeOrderMode.Strict ? PrimeOrderStatus.Found : PrimeOrderStatus.HeuristicUnresolved;
|
||||||
|
return new PrimeOrderResultWide(status, strictOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors)
|
||||||
@@ -162,7 +171,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
return order;
|
return order;
|
||||||
}
|
}
|
||||||
|
|
||||||
tempArray = ThreadLocalArrayPool<FactorEntry128>.Shared.Rent(length + 1);
|
tempArray = ArrayPool<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 +209,7 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (tempArray is not null)
|
if (tempArray is not null)
|
||||||
{
|
{
|
||||||
ThreadLocalArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
|
ArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -664,7 +673,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 ? ThreadLocalArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
|
FactorEntry128[]? rented = counts.Count > 0 ? ArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
|
||||||
int index = 0;
|
int index = 0;
|
||||||
if (rented is not null)
|
if (rented is not null)
|
||||||
{
|
{
|
||||||
@@ -682,7 +691,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);
|
||||||
ThreadLocalArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
|
ArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fullyFactored = cofactor == UInt128.One;
|
bool fullyFactored = cofactor == UInt128.One;
|
||||||
@@ -1062,20 +1071,15 @@ internal static partial class PrimeOrderCalculator
|
|||||||
|
|
||||||
private readonly struct FactorEntry128
|
private readonly struct FactorEntry128
|
||||||
{
|
{
|
||||||
public FactorEntry128(UInt128 value)
|
|
||||||
{
|
|
||||||
Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public FactorEntry128(UInt128 value, int exponent)
|
public FactorEntry128(UInt128 value, int exponent)
|
||||||
{
|
{
|
||||||
Value = value;
|
Value = value;
|
||||||
Exponent = exponent;
|
Exponent = exponent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly UInt128 Value;
|
public UInt128 Value { get; }
|
||||||
|
|
||||||
public readonly int Exponent = 1;
|
public int Exponent { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly struct PartialFactorResult128
|
private readonly struct PartialFactorResult128
|
||||||
@@ -1088,13 +1092,13 @@ internal static partial class PrimeOrderCalculator
|
|||||||
Count = count;
|
Count = count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly FactorEntry128[]? Factors;
|
public FactorEntry128[]? Factors { get; }
|
||||||
|
|
||||||
public readonly UInt128 Cofactor;
|
public UInt128 Cofactor { get; }
|
||||||
|
|
||||||
public readonly bool FullyFactored;
|
public bool FullyFactored { get; }
|
||||||
|
|
||||||
public readonly int Count;
|
public int Count { get; }
|
||||||
|
|
||||||
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
|
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
|
||||||
|
|
||||||
@@ -1102,13 +1106,14 @@ internal static partial class PrimeOrderCalculator
|
|||||||
{
|
{
|
||||||
if (Factors is null)
|
if (Factors is null)
|
||||||
{
|
{
|
||||||
FactorEntry128[] local = [new FactorEntry128(prime)];
|
FactorEntry128[] local = new FactorEntry128[1];
|
||||||
|
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);
|
extended[Count] = new FactorEntry128(prime, 1);
|
||||||
return new PartialFactorResult128(extended, UInt128.One, true, Count + 1);
|
return new PartialFactorResult128(extended, UInt128.One, true, Count + 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
@@ -1,72 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -176,7 +176,7 @@ public sealed class PrimeTester(bool useInternal = false)
|
|||||||
DevicePrimes.View.CopyFromCPU(primes);
|
DevicePrimes.View.CopyFromCPU(primes);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class ScratchBuffers
|
internal sealed class ScratchBuffers : IDisposable
|
||||||
{
|
{
|
||||||
public MemoryBuffer1D<ulong, Stride1D.Dense> Input { get; private set; }
|
public MemoryBuffer1D<ulong, Stride1D.Dense> Input { get; private set; }
|
||||||
public MemoryBuffer1D<byte, Stride1D.Dense> Output { get; private set; }
|
public MemoryBuffer1D<byte, Stride1D.Dense> Output { get; private set; }
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ namespace PerfectNumbers.Core;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Provides thread-safe file writing for both text and binary data.
|
/// Provides thread-safe file writing for both text and binary data.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class TextFileWriter
|
public sealed class TextFileWriter : IDisposable
|
||||||
{
|
{
|
||||||
private readonly FileStream _stream;
|
private readonly FileStream _stream;
|
||||||
private StreamWriter? _textWriter;
|
private StreamWriter? _textWriter;
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -218,7 +218,7 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static bool IsBitSet(GpuUInt128 value, int bitIndex)
|
private static bool IsBitSet(in GpuUInt128 value, int bitIndex)
|
||||||
{
|
{
|
||||||
if (bitIndex >= 64)
|
if (bitIndex >= 64)
|
||||||
{
|
{
|
||||||
@@ -229,16 +229,18 @@ public static class UInt128Extensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static ulong ExtractWindowValue(GpuUInt128 exponent, int windowStart, int windowBitCount)
|
private static ulong ExtractWindowValue(in GpuUInt128 exponent, int windowStart, int windowBitCount)
|
||||||
{
|
{
|
||||||
ulong mask = (1UL << windowBitCount) - 1UL;
|
|
||||||
if (windowStart != 0)
|
if (windowStart != 0)
|
||||||
{
|
{
|
||||||
exponent.ShiftRight(windowStart);
|
GpuUInt128 shifted = exponent;
|
||||||
return exponent.Low & mask;
|
shifted.ShiftRight(windowStart);
|
||||||
|
ulong mask = (1UL << windowBitCount) - 1UL;
|
||||||
|
return shifted.Low & mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exponent.Low & mask;
|
ulong directMask = (1UL << windowBitCount) - 1UL;
|
||||||
|
return exponent.Low & directMask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using ILGPU.Algorithms;
|
using ILGPU.Algorithms;
|
||||||
@@ -12,17 +11,13 @@ public static class ULongExtensions
|
|||||||
private const int Pow2WindowSize = 8;
|
private const int Pow2WindowSize = 8;
|
||||||
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
private const ulong Pow2WindowFallbackThreshold = 32UL;
|
||||||
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSize - 1);
|
private const int Pow2WindowOddPowerCount = 1 << (Pow2WindowSize - 1);
|
||||||
private const int Pow2WindowSizeGpu = 5;
|
|
||||||
private const int Pow2WindowOddPowerCountGpu = 1 << (Pow2WindowSizeGpu - 1);
|
|
||||||
|
|
||||||
public static ulong CalculateOrder(this ulong q)
|
public static ulong CalculateOrder(this ulong q)
|
||||||
{
|
{
|
||||||
// Order computations only run on admissible divisors, so the small-prime guard remains commented out to keep the
|
if (q <= 2UL)
|
||||||
// path branch-free.
|
{
|
||||||
// if (q <= 2UL)
|
return 0UL;
|
||||||
// {
|
}
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong order = q - 1UL, prime, temp;
|
ulong order = q - 1UL, prime, temp;
|
||||||
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
uint[] smallPrimes = PrimesGenerator.SmallPrimes;
|
||||||
@@ -292,220 +287,26 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong MulMod64(this ulong a, ulong b, ulong modulus)
|
// TODO: Replace this fallback with the UInt128 Montgomery helper measured fastest in
|
||||||
{
|
// MulMod64Benchmarks so CPU callers stop paying for triple modulo operations.
|
||||||
// Scanner inputs always provide odd prime moduli, so the legacy modulus guard stays commented out to avoid extra
|
public static ulong MulMod64(this ulong a, ulong b, ulong modulus) => (ulong)(UInt128)(((a % modulus) * (b % modulus)) % modulus);
|
||||||
// branching.
|
|
||||||
// if (modulus <= 1UL)
|
|
||||||
// {
|
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong reducedA = a >= modulus ? a % modulus : a;
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
ulong reducedB = b >= modulus ? b % modulus : b;
|
public static ulong MulMod64GpuCompatible(this ulong a, ulong b, ulong modulus)
|
||||||
GpuUInt128 state = new(reducedA);
|
{
|
||||||
return state.MulMod(reducedB, modulus);
|
// TODO: Remove this GPU-compatible shim from production once callers migrate to MulMod64,
|
||||||
|
// which the benchmarks show is roughly 6-7× faster on dense 64-bit inputs.
|
||||||
|
GpuUInt128 state = new(a % modulus);
|
||||||
|
return state.MulMod(b, modulus);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2ModWindowedCpu(this ulong exponent, ulong modulus)
|
public static ulong MulMod64GpuCompatibleDeferred(this ulong a, ulong b, ulong modulus)
|
||||||
{
|
{
|
||||||
// The by-divisor pipeline only schedules odd prime moduli, so the defensive check below
|
// TODO: Move this deferred helper to the benchmark suite; the baseline MulMod64 avoids the
|
||||||
// would never trigger on that path. Leaving it commented documents the invariant without
|
// 5-40× slowdown seen across real-world operand distributions.
|
||||||
// forcing a branch in the hot loop.
|
GpuUInt128 state = new(a);
|
||||||
// if (modulus <= 1UL)
|
return state.MulModWithNativeModulo(b, modulus);
|
||||||
// {
|
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Exponents flow from the strictly positive candidate list on the by-divisor scanner, so
|
|
||||||
// this guard would be redundant there.
|
|
||||||
// if (exponent == 0UL)
|
|
||||||
// {
|
|
||||||
// return 1UL % modulus;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Even though the by-divisor pipeline only feeds very large exponents, the legacy diagnostic callers
|
|
||||||
// still hit this small-exponent fast path, so keep the fallback in place.
|
|
||||||
if (exponent <= Pow2WindowFallbackThreshold)
|
|
||||||
{
|
|
||||||
return Pow2ModBinaryFallback(exponent, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bitLength = GetPortableBitLength(exponent);
|
|
||||||
int windowSize = GetWindowSize(bitLength);
|
|
||||||
int oddPowerCount = 1 << (windowSize - 1);
|
|
||||||
|
|
||||||
Span<ulong> oddPowerStorage = oddPowerCount <= PerfectNumberConstants.MaxOddPowersCount
|
|
||||||
? stackalloc ulong[PerfectNumberConstants.MaxOddPowersCount]
|
|
||||||
: new ulong[oddPowerCount];
|
|
||||||
oddPowerStorage = oddPowerStorage[..oddPowerCount];
|
|
||||||
InitializePlainOddPowers(modulus, oddPowerStorage);
|
|
||||||
|
|
||||||
// With modulus guaranteed to exceed one on the scanning path, the modulo operation would
|
|
||||||
// always yield one, so keep the literal to highlight the intended base value.
|
|
||||||
ulong result = 1UL;
|
|
||||||
int index = bitLength - 1;
|
|
||||||
|
|
||||||
while (index >= 0)
|
|
||||||
{
|
|
||||||
if (((exponent >> index) & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
result = result.MulMod64(result, modulus);
|
|
||||||
index--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int windowStart = index - windowSize + 1;
|
|
||||||
if (windowStart < 0)
|
|
||||||
{
|
|
||||||
windowStart = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
windowStart++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int windowLength = index - windowStart + 1;
|
|
||||||
for (int square = 0; square < windowLength; square++)
|
|
||||||
{
|
|
||||||
result = result.MulMod64(result, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong windowValue = (exponent >> windowStart) & ((1UL << windowLength) - 1UL);
|
|
||||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
|
||||||
result = result.MulMod64(oddPowerStorage[tableIndex], modulus);
|
|
||||||
|
|
||||||
index = windowStart - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Pow2ModBinaryFallback(ulong exponent, ulong modulus)
|
|
||||||
{
|
|
||||||
// The scanning path keeps modulus strictly greater than one, but other utility callers still
|
|
||||||
// rely on this fallback for tiny exponents, so leave the modulo in place for safety.
|
|
||||||
ulong remainingExponent = exponent;
|
|
||||||
// Production moduli are always odd primes greater than two, so normalize the seed values directly.
|
|
||||||
ulong result = 1UL;
|
|
||||||
ulong baseValue = 2UL;
|
|
||||||
|
|
||||||
while (remainingExponent != 0UL)
|
|
||||||
{
|
|
||||||
if ((remainingExponent & 1UL) != 0UL)
|
|
||||||
{
|
|
||||||
result = result.MulMod64(baseValue, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingExponent >>= 1;
|
|
||||||
if (remainingExponent == 0UL)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
baseValue = baseValue.MulMod64(baseValue, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static ulong Pow2ModWindowedGpu(this ulong exponent, ulong modulus)
|
|
||||||
{
|
|
||||||
// The GPU pipeline feeds only odd prime moduli, so the removed guard would never fire for
|
|
||||||
// EvenPerfectBitScanner workloads.
|
|
||||||
// if (modulus <= 1UL)
|
|
||||||
// {
|
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// The GPU pipeline only emits positive exponents on production workloads, so skip the zero guard to avoid an extra branch.
|
|
||||||
// Diagnostic harnesses must normalize their inputs before invoking this helper.
|
|
||||||
// if (exponent == 0UL)
|
|
||||||
// {
|
|
||||||
// return 1UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// The GPU kernels mostly process large exponents as well, but the binary fallback still serves
|
|
||||||
// diagnostic harnesses that probe edge cases around the threshold.
|
|
||||||
if (exponent <= Pow2WindowFallbackThreshold)
|
|
||||||
{
|
|
||||||
return Pow2ModBinaryGpuFallback(exponent, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bitLength = GetPortableBitLength(exponent);
|
|
||||||
int windowSize = GetGpuWindowSize(bitLength);
|
|
||||||
int oddPowerCount = 1 << (windowSize - 1);
|
|
||||||
|
|
||||||
PlainOddPowerTableGpu oddPowers = new PlainOddPowerTableGpu(modulus, oddPowerCount);
|
|
||||||
|
|
||||||
ulong result = 1UL;
|
|
||||||
int index = bitLength - 1;
|
|
||||||
|
|
||||||
while (index >= 0)
|
|
||||||
{
|
|
||||||
if (((exponent >> index) & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
result = result.MulMod64(result, modulus);
|
|
||||||
index--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int windowStart = index - windowSize + 1;
|
|
||||||
if (windowStart < 0)
|
|
||||||
{
|
|
||||||
windowStart = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
windowStart++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int windowLength = index - windowStart + 1;
|
|
||||||
for (int square = 0; square < windowLength; square++)
|
|
||||||
{
|
|
||||||
result = result.MulMod64(result, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong windowValue = (exponent >> windowStart) & ((1UL << windowLength) - 1UL);
|
|
||||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
|
||||||
result = result.MulMod64(oddPowers.Get(tableIndex), modulus);
|
|
||||||
|
|
||||||
index = windowStart - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong Pow2ModBinaryGpuFallback(ulong exponent, ulong modulus)
|
|
||||||
{
|
|
||||||
ulong remainingExponent = exponent;
|
|
||||||
// Production moduli are always odd primes greater than two, so normalize the seed values directly.
|
|
||||||
ulong result = 1UL;
|
|
||||||
ulong baseValue = 2UL;
|
|
||||||
|
|
||||||
while (remainingExponent != 0UL)
|
|
||||||
{
|
|
||||||
if ((remainingExponent & 1UL) != 0UL)
|
|
||||||
{
|
|
||||||
result = result.MulMod64(baseValue, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
remainingExponent >>= 1;
|
|
||||||
if (remainingExponent == 0UL)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
baseValue = baseValue.MulMod64(baseValue, modulus);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -525,14 +326,12 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModWindowedCpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
public static ulong Pow2MontgomeryModWindowed(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
||||||
{
|
{
|
||||||
ulong modulus = divisor.Modulus;
|
ulong modulus = divisor.Modulus;
|
||||||
if (exponent == 0UL)
|
if (exponent == 0UL)
|
||||||
{
|
{
|
||||||
return keepMontgomery
|
return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus;
|
||||||
? divisor.MontgomeryOne
|
|
||||||
: 1UL; // Montgomery moduli stay above one on the scanning path, so the modulo would always yield one.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exponent <= Pow2WindowFallbackThreshold)
|
if (exponent <= Pow2WindowFallbackThreshold)
|
||||||
@@ -546,101 +345,7 @@ public static class ULongExtensions
|
|||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
ulong result = divisor.MontgomeryOne;
|
||||||
ulong nPrime = divisor.NPrime;
|
ulong nPrime = divisor.NPrime;
|
||||||
|
ulong[] oddPowers = InitializeMontgomeryOddPowers(divisor, modulus, nPrime, oddPowerCount);
|
||||||
Span<ulong> oddPowerStorage = oddPowerCount <= PerfectNumberConstants.MaxOddPowersCount
|
|
||||||
? stackalloc ulong[PerfectNumberConstants.MaxOddPowersCount]
|
|
||||||
: default;
|
|
||||||
ulong[]? pooledOddPowers = null;
|
|
||||||
|
|
||||||
if (oddPowerCount <= PerfectNumberConstants.MaxOddPowersCount)
|
|
||||||
{
|
|
||||||
oddPowerStorage = oddPowerStorage[..oddPowerCount];
|
|
||||||
}
|
|
||||||
else 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;
|
|
||||||
while (index >= 0)
|
|
||||||
{
|
|
||||||
if (((exponent >> index) & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
|
||||||
index--;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int windowStart = index - windowSize + 1;
|
|
||||||
if (windowStart < 0)
|
|
||||||
{
|
|
||||||
windowStart = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (((exponent >> windowStart) & 1UL) == 0UL)
|
|
||||||
{
|
|
||||||
windowStart++;
|
|
||||||
}
|
|
||||||
|
|
||||||
int windowLength = index - windowStart + 1;
|
|
||||||
for (int square = 0; square < windowLength; square++)
|
|
||||||
{
|
|
||||||
result = result.MontgomeryMultiply(result, modulus, nPrime);
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong mask = (1UL << windowLength) - 1UL;
|
|
||||||
ulong windowValue = (exponent >> windowStart) & mask;
|
|
||||||
int tableIndex = (int)((windowValue - 1UL) >> 1);
|
|
||||||
ulong multiplier = oddPowers[tableIndex];
|
|
||||||
result = result.MontgomeryMultiply(multiplier, modulus, nPrime);
|
|
||||||
|
|
||||||
index = windowStart - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pooledOddPowers is not null)
|
|
||||||
{
|
|
||||||
ArrayPool<ulong>.Shared.Return(pooledOddPowers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (keepMontgomery)
|
|
||||||
{
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.MontgomeryMultiply(1UL, modulus, nPrime);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static ulong Pow2MontgomeryModWindowedGpu(this ulong exponent, in MontgomeryDivisorData divisor, bool keepMontgomery)
|
|
||||||
{
|
|
||||||
ulong modulus = divisor.Modulus;
|
|
||||||
if (exponent == 0UL)
|
|
||||||
{
|
|
||||||
return keepMontgomery
|
|
||||||
? divisor.MontgomeryOne
|
|
||||||
: 1UL; // Montgomery moduli stay above one on the scanning path, so the modulo would always yield one.
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exponent <= Pow2WindowFallbackThreshold)
|
|
||||||
{
|
|
||||||
return Pow2MontgomeryModSingleBit(exponent, divisor, keepMontgomery);
|
|
||||||
}
|
|
||||||
|
|
||||||
int bitLength = GetPortableBitLength(exponent);
|
|
||||||
int windowSize = GetWindowSize(bitLength);
|
|
||||||
int oddPowerCount = 1 << (windowSize - 1);
|
|
||||||
|
|
||||||
ulong result = divisor.MontgomeryOne;
|
|
||||||
ulong nPrime = divisor.NPrime;
|
|
||||||
ulong[] oddPowers = InitializeMontgomeryOddPowersGpu(divisor, modulus, nPrime, oddPowerCount);
|
|
||||||
|
|
||||||
int index = bitLength - 1;
|
int index = bitLength - 1;
|
||||||
while (index >= 0)
|
while (index >= 0)
|
||||||
@@ -756,219 +461,7 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private static int GetGpuWindowSize(int bitLength)
|
private static ulong[] InitializeMontgomeryOddPowers(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
||||||
{
|
|
||||||
int window = GetWindowSize(bitLength);
|
|
||||||
return window > Pow2WindowSizeGpu ? Pow2WindowSizeGpu : window;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static void InitializePlainOddPowers(ulong modulus, Span<ulong> destination)
|
|
||||||
{
|
|
||||||
int oddPowerCount = destination.Length;
|
|
||||||
// The by-divisor kernels always request at least one odd power, but other helpers reuse this initializer
|
|
||||||
// for smaller workloads, so the early exit stays in place.
|
|
||||||
if (oddPowerCount == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong baseValue = 2UL; // Moduli arrive as odd primes greater than two, so the modulo would always return two.
|
|
||||||
destination[0] = baseValue;
|
|
||||||
if (oddPowerCount == 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong square = baseValue.MulMod64(baseValue, modulus);
|
|
||||||
for (int i = 1; i < oddPowerCount; i++)
|
|
||||||
{
|
|
||||||
// Reuse baseValue to accumulate successive odd powers and avoid allocating another local.
|
|
||||||
baseValue = baseValue.MulMod64(square, modulus);
|
|
||||||
destination[i] = baseValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct PlainOddPowerTableGpu
|
|
||||||
{
|
|
||||||
private readonly int _count;
|
|
||||||
private ulong _value0;
|
|
||||||
private ulong _value1;
|
|
||||||
private ulong _value2;
|
|
||||||
private ulong _value3;
|
|
||||||
private ulong _value4;
|
|
||||||
private ulong _value5;
|
|
||||||
private ulong _value6;
|
|
||||||
private ulong _value7;
|
|
||||||
private ulong _value8;
|
|
||||||
private ulong _value9;
|
|
||||||
private ulong _value10;
|
|
||||||
private ulong _value11;
|
|
||||||
private ulong _value12;
|
|
||||||
private ulong _value13;
|
|
||||||
private ulong _value14;
|
|
||||||
private ulong _value15;
|
|
||||||
|
|
||||||
public PlainOddPowerTableGpu(ulong modulus, int requestedCount)
|
|
||||||
{
|
|
||||||
_count = requestedCount > Pow2WindowOddPowerCountGpu ? Pow2WindowOddPowerCountGpu : requestedCount;
|
|
||||||
_value0 = 0UL;
|
|
||||||
_value1 = 0UL;
|
|
||||||
_value2 = 0UL;
|
|
||||||
_value3 = 0UL;
|
|
||||||
_value4 = 0UL;
|
|
||||||
_value5 = 0UL;
|
|
||||||
_value6 = 0UL;
|
|
||||||
_value7 = 0UL;
|
|
||||||
_value8 = 0UL;
|
|
||||||
_value9 = 0UL;
|
|
||||||
_value10 = 0UL;
|
|
||||||
_value11 = 0UL;
|
|
||||||
_value12 = 0UL;
|
|
||||||
_value13 = 0UL;
|
|
||||||
_value14 = 0UL;
|
|
||||||
_value15 = 0UL;
|
|
||||||
|
|
||||||
// Window planning always requests at least one odd power, so keep the zero-count check commented out to avoid
|
|
||||||
// branching here.
|
|
||||||
// if (_count == 0)
|
|
||||||
// {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
ulong baseValue = 2UL;
|
|
||||||
_value0 = baseValue;
|
|
||||||
if (_count == 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong square = baseValue.MulMod64(baseValue, modulus);
|
|
||||||
ulong current = baseValue;
|
|
||||||
for (int i = 1; i < _count; i++)
|
|
||||||
{
|
|
||||||
current = current.MulMod64(square, modulus);
|
|
||||||
SetValue(i, current);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public ulong Get(int index)
|
|
||||||
{
|
|
||||||
// Windowed lookups never exceed the populated table bounds on production workloads, so leave the check commented
|
|
||||||
// out to remove the branch.
|
|
||||||
// if ((uint)index >= (uint)_count)
|
|
||||||
// {
|
|
||||||
// return 0UL;
|
|
||||||
// }
|
|
||||||
|
|
||||||
return index switch
|
|
||||||
{
|
|
||||||
0 => _value0,
|
|
||||||
1 => _value1,
|
|
||||||
2 => _value2,
|
|
||||||
3 => _value3,
|
|
||||||
4 => _value4,
|
|
||||||
5 => _value5,
|
|
||||||
6 => _value6,
|
|
||||||
7 => _value7,
|
|
||||||
8 => _value8,
|
|
||||||
9 => _value9,
|
|
||||||
10 => _value10,
|
|
||||||
11 => _value11,
|
|
||||||
12 => _value12,
|
|
||||||
13 => _value13,
|
|
||||||
14 => _value14,
|
|
||||||
_ => _value15,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private void SetValue(int index, ulong value)
|
|
||||||
{
|
|
||||||
switch (index)
|
|
||||||
{
|
|
||||||
case 1:
|
|
||||||
_value1 = value;
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
_value2 = value;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
_value3 = value;
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
_value4 = value;
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
_value5 = value;
|
|
||||||
break;
|
|
||||||
case 6:
|
|
||||||
_value6 = value;
|
|
||||||
break;
|
|
||||||
case 7:
|
|
||||||
_value7 = value;
|
|
||||||
break;
|
|
||||||
case 8:
|
|
||||||
_value8 = value;
|
|
||||||
break;
|
|
||||||
case 9:
|
|
||||||
_value9 = value;
|
|
||||||
break;
|
|
||||||
case 10:
|
|
||||||
_value10 = value;
|
|
||||||
break;
|
|
||||||
case 11:
|
|
||||||
_value11 = value;
|
|
||||||
break;
|
|
||||||
case 12:
|
|
||||||
_value12 = value;
|
|
||||||
break;
|
|
||||||
case 13:
|
|
||||||
_value13 = value;
|
|
||||||
break;
|
|
||||||
case 14:
|
|
||||||
_value14 = value;
|
|
||||||
break;
|
|
||||||
case 15:
|
|
||||||
_value15 = value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void InitializeMontgomeryOddPowersCpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, Span<ulong> destination)
|
|
||||||
{
|
|
||||||
int oddPowerCount = destination.Length;
|
|
||||||
if (oddPowerCount == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool computedOnGpu = oddPowerCount <= PerfectNumberConstants.MaxOddPowersCount
|
|
||||||
&& MontgomeryOddPowerGpu.TryCompute(divisor, oddPowerCount, destination);
|
|
||||||
if (computedOnGpu)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
destination[0] = divisor.MontgomeryTwo;
|
|
||||||
if (oddPowerCount == 1)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong square = divisor.MontgomeryTwoSquared;
|
|
||||||
ulong current = destination[0];
|
|
||||||
for (int i = 1; i < oddPowerCount; i++)
|
|
||||||
{
|
|
||||||
current = current.MontgomeryMultiply(square, modulus, nPrime);
|
|
||||||
destination[i] = current;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static ulong[] InitializeMontgomeryOddPowersGpu(in MontgomeryDivisorData divisor, ulong modulus, ulong nPrime, int oddPowerCount)
|
|
||||||
{
|
{
|
||||||
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
ulong[] oddPowers = new ulong[PerfectNumberConstants.MaxOddPowersCount];
|
||||||
oddPowers[0] = divisor.MontgomeryTwo;
|
oddPowers[0] = divisor.MontgomeryTwo;
|
||||||
@@ -988,31 +481,17 @@ public static class ULongExtensions
|
|||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModWithCycleCpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
public static ulong Pow2MontgomeryModWithCycle(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
ulong rotationCount = exponent % cycleLength;
|
ulong rotationCount = exponent % cycleLength;
|
||||||
return Pow2MontgomeryModWindowedCpu(rotationCount, divisor, keepMontgomery: false);
|
return Pow2MontgomeryModWindowed(rotationCount, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ulong Pow2MontgomeryModFromCycleRemainderCpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
public static ulong Pow2MontgomeryModFromCycleRemainder(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
||||||
{
|
{
|
||||||
return Pow2MontgomeryModWindowedCpu(reducedExponent, divisor, keepMontgomery: false);
|
return Pow2MontgomeryModWindowed(reducedExponent, divisor, keepMontgomery: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static ulong Pow2MontgomeryModWithCycleGpu(this ulong exponent, ulong cycleLength, in MontgomeryDivisorData divisor)
|
|
||||||
{
|
|
||||||
ulong rotationCount = exponent % cycleLength;
|
|
||||||
return Pow2MontgomeryModWindowedGpu(rotationCount, divisor, keepMontgomery: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static ulong Pow2MontgomeryModFromCycleRemainderGpu(this ulong reducedExponent, in MontgomeryDivisorData divisor)
|
|
||||||
{
|
|
||||||
return Pow2MontgomeryModWindowedGpu(reducedExponent, divisor, keepMontgomery: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
public static UInt128 PowMod(this ulong exponent, UInt128 modulus)
|
||||||
{
|
{
|
||||||
|
|||||||
+4
-7
@@ -335,13 +335,10 @@ if (patchResults.Any(static r => !r.Success))
|
|||||||
|
|
||||||
static List<PatchFile> ParsePatchFromFile(string patchFilePath)
|
static List<PatchFile> ParsePatchFromFile(string patchFilePath)
|
||||||
{
|
{
|
||||||
var stream = File.OpenRead(patchFilePath);
|
using var stream = File.OpenRead(patchFilePath);
|
||||||
var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 16_384, leaveOpen: false);
|
using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 16_384, leaveOpen: false);
|
||||||
var lineReader = new PatchLineReader(reader);
|
using var lineReader = new PatchLineReader(reader);
|
||||||
var files = ParsePatch(lineReader);
|
var files = ParsePatch(lineReader);
|
||||||
lineReader.Dispose();
|
|
||||||
reader.Dispose();
|
|
||||||
stream.Dispose();
|
|
||||||
return files;
|
return files;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -503,7 +500,7 @@ static void FinalizePatchFile(
|
|||||||
files.Add(new PatchFile(originalPath, modifiedPath, hunks ?? new List<PatchHunk>(), type));
|
files.Add(new PatchFile(originalPath, modifiedPath, hunks ?? new List<PatchHunk>(), type));
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class PatchLineReader
|
private sealed class PatchLineReader : IDisposable
|
||||||
{
|
{
|
||||||
private const int ReadBufferSize = 4096;
|
private const int ReadBufferSize = 4096;
|
||||||
private const int InitialLineBufferSize = 256;
|
private const int InitialLineBufferSize = 256;
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:203:
|
|||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||||
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||||
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||||
@@ -265,6 +266,7 @@ todo_list.txt:121:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTeste
|
|||||||
todo_list.txt:122:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
todo_list.txt:122:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:207: // TODO: When divisorCycle is zero (cache miss), compute only that single cycle on the device
|
||||||
todo_list.txt:123:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
todo_list.txt:123:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:214: // TODO: Remove this no-cycle branch and require on-demand cycle computation (without
|
||||||
todo_list.txt:124:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
todo_list.txt:124:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:240: // TODO: Replace this linear increment with the batched divisor-cycle walker validated in the
|
||||||
|
todo_list.txt:125:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:262: : prime.Pow2MontgomeryMod(divisorData); // TODO: Switch to the upcoming ProcessEightBitWindows helper once the
|
||||||
todo_list.txt:126:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
todo_list.txt:126:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:283: // TODO: Replace this modulo with the ring-buffer style counter (subtract loop) used in the fast CLI
|
||||||
todo_list.txt:127:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
todo_list.txt:127:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:356: // TODO: Replace this guard with an on-demand CPU/GPU cycle computation path that skips
|
||||||
todo_list.txt:128:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
todo_list.txt:128:PerfectNumbers.Core\Cpu\MersenneNumberDivisorByDivisorCpuTester.cs:396: // TODO: Remove this no-cycle fallback once divisor cycles are mandatory; the per-prime pow2 ladder here
|
||||||
@@ -405,6 +407,17 @@ PerfectNumbers.Core\ModResidueTracker.cs:257: // TODO: Us
|
|||||||
PerfectNumbers.Core\ModResidueTracker.cs:263: // TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
PerfectNumbers.Core\ModResidueTracker.cs:263: // TODO: Consult MersenneDivisorCycles.Shared (or similar caches) here so we reuse precomputed cycle lengths
|
||||||
PerfectNumbers.Core\ModResidueTracker.cs:295: // TODO: Route this through the planned UInt128 windowed powmod so the inner MulMod adopts the
|
PerfectNumbers.Core\ModResidueTracker.cs:295: // TODO: Route this through the planned UInt128 windowed powmod so the inner MulMod adopts the
|
||||||
PerfectNumbers.Core\ModResidueTracker.cs:314: // TODO: Replace this double-and-add fallback with the UInt128 intrinsic-backed multiplier once the
|
PerfectNumbers.Core\ModResidueTracker.cs:314: // TODO: Replace this double-and-add fallback with the UInt128 intrinsic-backed multiplier once the
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:49: set => _useDivisorCycles = value; // TODO: Remove this flag once GPU runs unconditionally fetch cycle lengths so every divisor check benefits from the measured speedups.
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:298: // TODO: Stage MontgomeryDivisorData in a GPU-ready cache keyed by divisor so we can copy
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:372: // TODO: Replace the block lease with a direct single-cycle lookup so the GPU path
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:395: // TODO: Drop this block hand-off once the cache exposes a direct
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:409: // TODO: Replace this `%` with the cycle-cache helper that subtracts precomputed
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:425: // TODO: Trigger an immediate GPU computation for the single missing cycle without storing
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:476: // TODO: Preload ProcessEightBitWindows-friendly divisor metadata for GPU batches so this
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:563: hits[index] = exponent.Pow2MontgomeryMod(divisor) == 1UL ? (byte)1 : (byte)0; // TODO: Replace Pow2MontgomeryMod with the ProcessEightBitWindows helper once available so GPU by-divisor scans reuse the benchmarked fast pow2 ladder.
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:632: // TODO: Replace this guard with an on-demand cycle computation that runs on the device selected
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:787: // TODO: Replace this `%` with the divisor-cycle remainder helper so we reuse cached
|
||||||
|
PerfectNumbers.Core\Gpu\MersenneNumberDivisorByDivisorGpuTester.cs:858: results[index] = exponent.Pow2MontgomeryModMontgomery(divisor); // TODO: Swap this Montgomery-domain pow2 with the windowed helper slated to replace the slower ladder in Pow2Minus1Mod benchmarks.
|
||||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:43: // TODO: Swap this per-run enumerator creation for the shared PrimeIterator so by-divisor scans reuse the
|
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:43: // TODO: Swap this per-run enumerator creation for the shared PrimeIterator so by-divisor scans reuse the
|
||||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:219: // TODO: Swap Task.Factory.StartNew for the shared low-overhead work queue once the scanner's
|
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:219: // TODO: Swap Task.Factory.StartNew for the shared low-overhead work queue once the scanner's
|
||||||
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:240: bool useDivisorCycles = tester.UseDivisorCycles; // TODO: Remove the conditional path once all testers always enable divisor cycles to keep every divisor scan on the faster cached-length track.
|
PerfectNumbers.Core\MersenneNumberDivisorByDivisorTester.cs:240: bool useDivisorCycles = tester.UseDivisorCycles; // TODO: Remove the conditional path once all testers always enable divisor cycles to keep every divisor scan on the faster cached-length track.
|
||||||
|
|||||||
@@ -54,11 +54,10 @@ static ProbeResult ProbeBigInteger(Accelerator accelerator)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(BigIntegerKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(BigIntegerKernel);
|
||||||
var buffer = accelerator.Allocate1D<int>(1);
|
using var buffer = accelerator.Allocate1D<int>(1);
|
||||||
var extent = new Index1D((int)buffer.Length);
|
var extent = new Index1D((int)buffer.Length);
|
||||||
kernel(extent, buffer.View);
|
kernel(extent, buffer.View);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
buffer.Dispose();
|
|
||||||
return ProbeResult.Success(
|
return ProbeResult.Success(
|
||||||
"System.Numerics.BigInteger",
|
"System.Numerics.BigInteger",
|
||||||
"Addition, subtraction, multiplication, division, and modulo compile on the CPU accelerator.");
|
"Addition, subtraction, multiplication, division, and modulo compile on the CPU accelerator.");
|
||||||
@@ -74,10 +73,9 @@ static ProbeResult ProbeEInteger(Accelerator accelerator)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(EIntegerKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(EIntegerKernel);
|
||||||
var buffer = accelerator.Allocate1D<int>(1);
|
using var buffer = accelerator.Allocate1D<int>(1);
|
||||||
kernel(new Index1D((int)buffer.Length), buffer.View);
|
kernel(new Index1D((int)buffer.Length), buffer.View);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
buffer.Dispose();
|
|
||||||
return ProbeResult.Success(
|
return ProbeResult.Success(
|
||||||
"PeterO.Numbers.EInteger",
|
"PeterO.Numbers.EInteger",
|
||||||
"Addition, subtraction, multiplication, division, and remainder compile on the CPU accelerator.");
|
"Addition, subtraction, multiplication, division, and remainder compile on the CPU accelerator.");
|
||||||
@@ -95,7 +93,7 @@ static ProbeResult ProbeERational(Accelerator accelerator)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(ERationalKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(ERationalKernel);
|
||||||
var buffer = accelerator.Allocate1D<int>(1);
|
using var buffer = accelerator.Allocate1D<int>(1);
|
||||||
kernel(new Index1D((int)buffer.Length), buffer.View);
|
kernel(new Index1D((int)buffer.Length), buffer.View);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
|
|
||||||
@@ -103,7 +101,6 @@ static ProbeResult ProbeERational(Accelerator accelerator)
|
|||||||
details += hasRemainder
|
details += hasRemainder
|
||||||
? " ERational exposes a remainder API that requires further investigation."
|
? " ERational exposes a remainder API that requires further investigation."
|
||||||
: " ERational does not expose a remainder/modulo API.";
|
: " ERational does not expose a remainder/modulo API.";
|
||||||
buffer.Dispose();
|
|
||||||
return ProbeResult.Success("PeterO.Numbers.ERational", details);
|
return ProbeResult.Success("PeterO.Numbers.ERational", details);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -123,10 +120,9 @@ static ProbeResult ProbeOpenNumericPrimesIsPrime(Accelerator accelerator)
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(OpenNumericPrimesIsPrimeKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<int, Stride1D.Dense>>(OpenNumericPrimesIsPrimeKernel);
|
||||||
var buffer = accelerator.Allocate1D<int>(4);
|
using var buffer = accelerator.Allocate1D<int>(4);
|
||||||
kernel(new Index1D((int)buffer.Length), buffer.View);
|
kernel(new Index1D((int)buffer.Length), buffer.View);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
buffer.Dispose();
|
|
||||||
return ProbeResult.Success(
|
return ProbeResult.Success(
|
||||||
"Open.Numeric.Primes.Prime.Numbers.IsPrime",
|
"Open.Numeric.Primes.Prime.Numbers.IsPrime",
|
||||||
"IsPrime compiles and runs on the CPU accelerator kernel.");
|
"IsPrime compiles and runs on the CPU accelerator kernel.");
|
||||||
|
|||||||
@@ -8,7 +8,4 @@
|
|||||||
<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>
|
||||||
Reference in new issue
Block a user