Merge branch 'codex/analyze-gc-instances-in-checkdivisors-method' of https://github.com/mlemanczyk/even-perfect-numbers-scanner into codex/analyze-gc-instances-in-checkdivisors-method
This commit is contained in:
commit
2f461ee001
7 files changed
+390
-173
No files matched your search
@@ -121,7 +121,7 @@ public class GpuKernelCompilationTests
|
|||||||
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
|
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel =
|
||||||
DivisorCycleKernels.GpuDivisorCycleKernel;
|
DivisorCycleKernels.GpuDivisorCycleKernel;
|
||||||
|
|
||||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<byte>> PrimeTesterSmallPrimeKernel =
|
||||||
PrimeTesterKernels.SmallPrimeSieveKernel;
|
PrimeTesterKernels.SmallPrimeSieveKernel;
|
||||||
|
|
||||||
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSharesFactorKernel =
|
||||||
@@ -336,7 +336,7 @@ public class GpuKernelCompilationTests
|
|||||||
|
|
||||||
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
|
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
|
||||||
_ = kernel;
|
_ = kernel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ public class PrimeTesterIsPrimeTests
|
|||||||
[InlineData(97UL, true)]
|
[InlineData(97UL, true)]
|
||||||
[InlineData(133UL, false)]
|
[InlineData(133UL, false)]
|
||||||
[InlineData(137UL, true)]
|
[InlineData(137UL, true)]
|
||||||
|
[InlineData(143UL, false)]
|
||||||
|
[InlineData(199UL, true)]
|
||||||
|
[InlineData(209UL, false)]
|
||||||
|
[InlineData(403UL, false)]
|
||||||
[InlineData(341UL, false)]
|
[InlineData(341UL, false)]
|
||||||
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
public void IsPrime_returns_expected_results(ulong n, bool expected)
|
||||||
{
|
{
|
||||||
@@ -25,6 +29,20 @@ public class PrimeTesterIsPrimeTests
|
|||||||
tester.IsPrime(n, CancellationToken.None).Should().Be(expected);
|
tester.IsPrime(n, CancellationToken.None).Should().Be(expected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[Trait("Category", "Fast")]
|
||||||
|
[InlineData(139UL, true)]
|
||||||
|
[InlineData(189UL, false)]
|
||||||
|
[InlineData(193UL, true)]
|
||||||
|
[InlineData(279UL, false)]
|
||||||
|
public void IsPrimeGpu_uses_residue_specific_tables(ulong n, bool expected)
|
||||||
|
{
|
||||||
|
GpuContextPool.ForceCpu = false;
|
||||||
|
var tester = new PrimeTester();
|
||||||
|
|
||||||
|
tester.IsPrimeGpu(n, CancellationToken.None).Should().Be(expected);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
[Trait("Category", "Fast")]
|
[Trait("Category", "Fast")]
|
||||||
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
|
public void IsPrimeGpu_falls_back_to_cpu_when_forced()
|
||||||
|
|||||||
@@ -32,14 +32,24 @@ public class PrimesGeneratorTests
|
|||||||
var lastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
var lastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
|
||||||
var lastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
var lastSeven = PrimesGenerator.SmallPrimesLastSeven;
|
||||||
var lastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
|
var lastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
|
||||||
|
var lastThree = DivisorGenerator.SmallPrimesLastThree;
|
||||||
|
var lastThreePow2 = DivisorGenerator.SmallPrimesPow2LastThree;
|
||||||
|
var lastNine = DivisorGenerator.SmallPrimesLastNine;
|
||||||
|
var lastNinePow2 = DivisorGenerator.SmallPrimesPow2LastNine;
|
||||||
|
|
||||||
lastOne.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
lastOne.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||||
lastSeven.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
lastSeven.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||||
|
lastThree.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||||
|
lastNine.Length.Should().Be((int)PerfectNumberConstants.PrimesLimit);
|
||||||
lastOnePow2.Length.Should().Be(lastOne.Length);
|
lastOnePow2.Length.Should().Be(lastOne.Length);
|
||||||
lastSevenPow2.Length.Should().Be(lastSeven.Length);
|
lastSevenPow2.Length.Should().Be(lastSeven.Length);
|
||||||
|
lastThreePow2.Length.Should().Be(lastThree.Length);
|
||||||
|
lastNinePow2.Length.Should().Be(lastNine.Length);
|
||||||
|
|
||||||
lastOne[..5].Should().Equal([3U, 7U, 11U, 13U, 19U]);
|
lastOne[..5].Should().Equal([3U, 7U, 11U, 13U, 19U]);
|
||||||
lastSeven[..5].Should().Equal([3U, 7U, 11U, 13U, 17U]);
|
lastSeven[..5].Should().Equal([3U, 7U, 11U, 13U, 17U]);
|
||||||
|
lastThree[..6].Should().Equal([3U, 7U, 11U, 13U, 17U, 19U]);
|
||||||
|
lastNine[..6].Should().Equal([3U, 7U, 11U, 13U, 17U, 19U]);
|
||||||
|
|
||||||
for (int i = 0; i < lastOne.Length; i++)
|
for (int i = 0; i < lastOne.Length; i++)
|
||||||
{
|
{
|
||||||
@@ -58,6 +68,23 @@ public class PrimesGeneratorTests
|
|||||||
.Should().BeTrue();
|
.Should().BeTrue();
|
||||||
lastSevenPow2[i].Should().Be(prime * (ulong)prime);
|
lastSevenPow2[i].Should().Be(prime * (ulong)prime);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
for (int i = 0; i < lastThree.Length; i++)
|
||||||
|
{
|
||||||
|
uint prime = lastThree[i];
|
||||||
|
uint mod10 = prime % 10U;
|
||||||
|
(mod10 == 3U || mod10 == 7U || prime == 11U || prime == 19U)
|
||||||
|
.Should().BeTrue();
|
||||||
|
lastThreePow2[i].Should().Be(prime * (ulong)prime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < lastNine.Length; i++)
|
||||||
|
{
|
||||||
|
uint prime = lastNine[i];
|
||||||
|
uint mod10 = prime % 10U;
|
||||||
|
(mod10 == 3U || mod10 == 7U || mod10 == 9U || prime == 11U)
|
||||||
|
.Should().BeTrue();
|
||||||
|
lastNinePow2[i].Should().Be(prime * (ulong)prime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,228 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
internal static class DivisorGenerator
|
internal static class DivisorGenerator
|
||||||
{
|
{
|
||||||
|
public static readonly uint[] SmallPrimes;
|
||||||
|
public static readonly ulong[] SmallPrimesPow2;
|
||||||
|
public static readonly uint[] SmallPrimesLastOne;
|
||||||
|
public static readonly ulong[] SmallPrimesPow2LastOne;
|
||||||
|
public static readonly uint[] SmallPrimesLastSeven;
|
||||||
|
public static readonly ulong[] SmallPrimesPow2LastSeven;
|
||||||
|
public static readonly uint[] SmallPrimesLastThree;
|
||||||
|
public static readonly ulong[] SmallPrimesPow2LastThree;
|
||||||
|
public static readonly uint[] SmallPrimesLastNine;
|
||||||
|
public static readonly ulong[] SmallPrimesPow2LastNine;
|
||||||
|
|
||||||
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
private const ushort DecimalMaskWhenLastIsSeven = (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
|
private const ushort DecimalMaskWhenLastIsThree = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
|
private const ushort DecimalMaskWhenLastIsNine = (1 << 1) | (1 << 3) | (1 << 7) | (1 << 9);
|
||||||
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
|
private const ushort DecimalMaskOtherwise = (1 << 1) | (1 << 3) | (1 << 9);
|
||||||
|
|
||||||
|
static DivisorGenerator()
|
||||||
|
{
|
||||||
|
BuildSmallPrimes(
|
||||||
|
out SmallPrimes,
|
||||||
|
out SmallPrimesPow2,
|
||||||
|
out SmallPrimesLastOne,
|
||||||
|
out SmallPrimesPow2LastOne,
|
||||||
|
out SmallPrimesLastSeven,
|
||||||
|
out SmallPrimesPow2LastSeven,
|
||||||
|
out SmallPrimesLastThree,
|
||||||
|
out SmallPrimesPow2LastThree,
|
||||||
|
out SmallPrimesLastNine,
|
||||||
|
out SmallPrimesPow2LastNine);
|
||||||
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static ushort GetDecimalMask(LastDigit lastDigit)
|
public static ushort GetDecimalMask(LastDigit lastDigit)
|
||||||
{
|
{
|
||||||
return lastDigit == LastDigit.Seven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
|
return lastDigit switch
|
||||||
|
{
|
||||||
|
LastDigit.Seven => DecimalMaskWhenLastIsSeven,
|
||||||
|
LastDigit.Three => DecimalMaskWhenLastIsThree,
|
||||||
|
LastDigit.Nine => DecimalMaskWhenLastIsNine,
|
||||||
|
_ => DecimalMaskOtherwise,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void BuildSmallPrimes(
|
||||||
|
out uint[] all,
|
||||||
|
out ulong[] allPow2,
|
||||||
|
out uint[] lastOne,
|
||||||
|
out ulong[] lastOnePow2,
|
||||||
|
out uint[] lastSeven,
|
||||||
|
out ulong[] lastSevenPow2,
|
||||||
|
out uint[] lastThree,
|
||||||
|
out ulong[] lastThreePow2,
|
||||||
|
out uint[] lastNine,
|
||||||
|
out ulong[] lastNinePow2)
|
||||||
|
{
|
||||||
|
uint target = PerfectNumberConstants.PrimesLimit;
|
||||||
|
int targetInt = checked((int)target);
|
||||||
|
all = new uint[targetInt];
|
||||||
|
allPow2 = new ulong[targetInt];
|
||||||
|
lastOne = new uint[targetInt];
|
||||||
|
lastOnePow2 = new ulong[targetInt];
|
||||||
|
lastSeven = new uint[targetInt];
|
||||||
|
lastSevenPow2 = new ulong[targetInt];
|
||||||
|
lastThree = new uint[targetInt];
|
||||||
|
lastThreePow2 = new ulong[targetInt];
|
||||||
|
lastNine = new uint[targetInt];
|
||||||
|
lastNinePow2 = new ulong[targetInt];
|
||||||
|
|
||||||
|
List<uint> primes = new(targetInt * 4 / 3 + 16);
|
||||||
|
uint candidate = 2U;
|
||||||
|
int allCount = 0;
|
||||||
|
int lastOneCount = 0;
|
||||||
|
int lastSevenCount = 0;
|
||||||
|
int lastThreeCount = 0;
|
||||||
|
int lastNineCount = 0;
|
||||||
|
|
||||||
|
while (allCount < targetInt || lastOneCount < targetInt || lastSevenCount < targetInt || lastThreeCount < targetInt || lastNineCount < targetInt)
|
||||||
|
{
|
||||||
|
bool isPrime = true;
|
||||||
|
int primesCount = primes.Count;
|
||||||
|
for (int i = 0; i < primesCount; i++)
|
||||||
|
{
|
||||||
|
uint p = primes[i];
|
||||||
|
// TODO: Reuse a single squared local for `p` within this loop so we avoid recalculating
|
||||||
|
// `p * p` repeatedly without storing every squared prime globally (the cache must remain
|
||||||
|
// limited to the small-divisor tables to honor the memory constraints).
|
||||||
|
if (p * p > candidate)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Replace this trial-division `%` with the sieve-based generator that avoids
|
||||||
|
// per-candidate modulo work so building the small-prime tables stops dominating
|
||||||
|
// startup time for large scans.
|
||||||
|
if (candidate % p == 0U)
|
||||||
|
{
|
||||||
|
isPrime = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPrime)
|
||||||
|
{
|
||||||
|
primes.Add(candidate);
|
||||||
|
if (allCount < targetInt)
|
||||||
|
{
|
||||||
|
// TODO: Reuse a single cached `candidateSquared` per iteration so we avoid issuing
|
||||||
|
// three separate 64-bit multiplications when populating the pow2 arrays here and
|
||||||
|
// in the last-one/last-seven branches below.
|
||||||
|
all[allCount] = candidate;
|
||||||
|
allPow2[allCount] = checked((ulong)candidate * candidate);
|
||||||
|
allCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate != 2U)
|
||||||
|
{
|
||||||
|
LastDigit lastDigit = GetLastDigit(candidate);
|
||||||
|
|
||||||
|
if (lastOneCount < targetInt && IsAllowedForLastOne(candidate, lastDigit))
|
||||||
|
{
|
||||||
|
lastOne[lastOneCount] = candidate;
|
||||||
|
lastOnePow2[lastOneCount] = checked((ulong)candidate * candidate);
|
||||||
|
lastOneCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSevenCount < targetInt && IsAllowedForLastSeven(candidate, lastDigit))
|
||||||
|
{
|
||||||
|
lastSeven[lastSevenCount] = candidate;
|
||||||
|
lastSevenPow2[lastSevenCount] = checked((ulong)candidate * candidate);
|
||||||
|
lastSevenCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastThreeCount < targetInt && IsAllowedForLastThree(candidate, lastDigit))
|
||||||
|
{
|
||||||
|
lastThree[lastThreeCount] = candidate;
|
||||||
|
lastThreePow2[lastThreeCount] = checked((ulong)candidate * candidate);
|
||||||
|
lastThreeCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastNineCount < targetInt && IsAllowedForLastNine(candidate, lastDigit))
|
||||||
|
{
|
||||||
|
lastNine[lastNineCount] = candidate;
|
||||||
|
lastNinePow2[lastNineCount] = checked((ulong)candidate * candidate);
|
||||||
|
lastNineCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Switch this increment to the Mod6 stepping table identified in the Mod6ComparisonBenchmarks
|
||||||
|
// so we skip the composite residues without relying on per-iteration `%` checks.
|
||||||
|
candidate = candidate == 2U ? 3U : candidate + 2U;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static LastDigit GetLastDigit(uint prime)
|
||||||
|
{
|
||||||
|
return (prime % 10U) switch
|
||||||
|
{
|
||||||
|
1U => LastDigit.One,
|
||||||
|
3U => LastDigit.Three,
|
||||||
|
5U => LastDigit.Five,
|
||||||
|
7U => LastDigit.Seven,
|
||||||
|
9U => LastDigit.Nine,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(prime)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit)
|
||||||
|
{
|
||||||
|
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
|
||||||
|
// reuses the benchmarked residue helper instead of repeated divisions.
|
||||||
|
return lastDigit switch
|
||||||
|
{
|
||||||
|
LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
|
||||||
|
LastDigit.Seven => prime == 7U,
|
||||||
|
_ => prime == 11U,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
|
||||||
|
{
|
||||||
|
// TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster
|
||||||
|
// residue helper used elsewhere in the scanner.
|
||||||
|
return lastDigit switch
|
||||||
|
{
|
||||||
|
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
|
||||||
|
_ => prime == 11U,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsAllowedForLastThree(uint prime, LastDigit lastDigit)
|
||||||
|
{
|
||||||
|
// TODO: Replace the `% 10` residue classification with ULongExtensions.Mod10 once the helper
|
||||||
|
// migrates to UInt32 so we can reuse the optimized decimal residue path everywhere.
|
||||||
|
return lastDigit switch
|
||||||
|
{
|
||||||
|
LastDigit.Three or LastDigit.Seven => true,
|
||||||
|
LastDigit.One => prime == 11U,
|
||||||
|
LastDigit.Nine => prime == 19U,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private static bool IsAllowedForLastNine(uint prime, LastDigit lastDigit)
|
||||||
|
{
|
||||||
|
// TODO: Replace the `% 10` residue classification with ULongExtensions.Mod10 when the helper
|
||||||
|
// supports UInt32 inputs so the residue tables share the optimized decimal path.
|
||||||
|
return lastDigit switch
|
||||||
|
{
|
||||||
|
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
|
||||||
|
LastDigit.One => prime == 11U,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,22 +7,42 @@ namespace PerfectNumbers.Core.Gpu;
|
|||||||
internal static class PrimeTesterKernels
|
internal static class PrimeTesterKernels
|
||||||
{
|
{
|
||||||
// GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise.
|
// GPU kernel: small-prime sieve only. Returns 1 if passes sieve (probable prime), 0 otherwise.
|
||||||
public static void SmallPrimeSieveKernel(Index1D index, ArrayView<ulong> numbers, ArrayView<uint> smallPrimes, ArrayView<byte> results)
|
public static void SmallPrimeSieveKernel(
|
||||||
|
Index1D index,
|
||||||
|
ArrayView<ulong> numbers,
|
||||||
|
ArrayView<uint> smallPrimes,
|
||||||
|
ArrayView<uint> smallPrimesLastOne,
|
||||||
|
ArrayView<uint> smallPrimesLastSeven,
|
||||||
|
ArrayView<uint> smallPrimesLastThree,
|
||||||
|
ArrayView<uint> smallPrimesLastNine,
|
||||||
|
ArrayView<byte> results)
|
||||||
{
|
{
|
||||||
ulong n = numbers[index];
|
ulong n = numbers[index];
|
||||||
byte result = 1;
|
byte result = 1;
|
||||||
|
|
||||||
// EvenPerfectBitScanner filters candidates so production GPU launches only see:
|
ArrayView<uint> primes = smallPrimes;
|
||||||
// - n >= 31 (the scanner starts at 136,279,841 and test mode seeds 31)
|
ulong lastDigit = n % 10UL;
|
||||||
// - n odd (candidate generators and ModResidueTracker eliminate even exponents)
|
switch (lastDigit)
|
||||||
// - n not divisible by 5 (residue prefiltering and AddPrimes transform cover these composites)
|
{
|
||||||
// Tests should honor the same constraints so the host wrapper can remain branchless.
|
case 1UL:
|
||||||
|
primes = smallPrimesLastOne;
|
||||||
|
break;
|
||||||
|
case 3UL:
|
||||||
|
primes = smallPrimesLastThree;
|
||||||
|
break;
|
||||||
|
case 7UL:
|
||||||
|
primes = smallPrimesLastSeven;
|
||||||
|
break;
|
||||||
|
case 9UL:
|
||||||
|
primes = smallPrimesLastNine;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
long length = smallPrimes.Length;
|
int length = (int)primes.Length;
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
ulong prime = smallPrimes[i];
|
ulong prime = primes[i];
|
||||||
ulong primeSquare = (ulong)prime * prime;
|
ulong primeSquare = prime * prime;
|
||||||
if (primeSquare > n)
|
if (primeSquare > n)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -48,9 +48,31 @@ public sealed class PrimeTester
|
|||||||
|
|
||||||
if (result)
|
if (result)
|
||||||
{
|
{
|
||||||
var smallPrimeDivisorsLength = PrimesGenerator.SmallPrimes.Length;
|
|
||||||
uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes;
|
uint[] smallPrimeDivisors = PrimesGenerator.SmallPrimes;
|
||||||
ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2;
|
ulong[] smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2;
|
||||||
|
|
||||||
|
ulong nMod10 = n.Mod10();
|
||||||
|
switch (nMod10)
|
||||||
|
{
|
||||||
|
case 1UL:
|
||||||
|
smallPrimeDivisors = PrimesGenerator.SmallPrimesLastOne;
|
||||||
|
smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2LastOne;
|
||||||
|
break;
|
||||||
|
case 3UL:
|
||||||
|
smallPrimeDivisors = DivisorGenerator.SmallPrimesLastThree;
|
||||||
|
smallPrimeDivisorsMul = DivisorGenerator.SmallPrimesPow2LastThree;
|
||||||
|
break;
|
||||||
|
case 7UL:
|
||||||
|
smallPrimeDivisors = PrimesGenerator.SmallPrimesLastSeven;
|
||||||
|
smallPrimeDivisorsMul = PrimesGenerator.SmallPrimesPow2LastSeven;
|
||||||
|
break;
|
||||||
|
case 9UL:
|
||||||
|
smallPrimeDivisors = DivisorGenerator.SmallPrimesLastNine;
|
||||||
|
smallPrimeDivisorsMul = DivisorGenerator.SmallPrimesPow2LastNine;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
int smallPrimeDivisorsLength = smallPrimeDivisors.Length;
|
||||||
for (int i = 0; i < smallPrimeDivisorsLength; i++)
|
for (int i = 0; i < smallPrimeDivisorsLength; i++)
|
||||||
{
|
{
|
||||||
if (smallPrimeDivisorsMul[i] > n)
|
if (smallPrimeDivisorsMul[i] > n)
|
||||||
@@ -110,8 +132,8 @@ public sealed class PrimeTester
|
|||||||
{
|
{
|
||||||
var limiter = GpuPrimeWorkLimiter.Acquire();
|
var limiter = GpuPrimeWorkLimiter.Acquire();
|
||||||
var gpu = PrimeTesterGpuContextPool.Rent();
|
var gpu = PrimeTesterGpuContextPool.Rent();
|
||||||
|
var state = gpu.State;
|
||||||
var accelerator = gpu.Accelerator;
|
var accelerator = gpu.Accelerator;
|
||||||
var state = GpuKernelState.GetOrCreate(accelerator);
|
|
||||||
int totalLength = values.Length;
|
int totalLength = values.Length;
|
||||||
int batchSize = Math.Max(1, GpuBatchSize);
|
int batchSize = Math.Max(1, GpuBatchSize);
|
||||||
|
|
||||||
@@ -132,7 +154,15 @@ public sealed class PrimeTester
|
|||||||
values.Slice(pos, count).CopyTo(temp);
|
values.Slice(pos, count).CopyTo(temp);
|
||||||
input.View.CopyFromCPU(ref temp[0], count);
|
input.View.CopyFromCPU(ref temp[0], count);
|
||||||
|
|
||||||
state.Kernel(count, input.View, state.DevicePrimes.View, output.View);
|
state.Kernel(
|
||||||
|
count,
|
||||||
|
input.View,
|
||||||
|
state.DevicePrimesDefault.View,
|
||||||
|
state.DevicePrimesLastOne.View,
|
||||||
|
state.DevicePrimesLastSeven.View,
|
||||||
|
state.DevicePrimesLastThree.View,
|
||||||
|
state.DevicePrimesLastNine.View,
|
||||||
|
output.View);
|
||||||
accelerator.Synchronize();
|
accelerator.Synchronize();
|
||||||
output.View.CopyToCPU(ref results[pos], count);
|
output.View.CopyToCPU(ref results[pos], count);
|
||||||
|
|
||||||
@@ -151,22 +181,40 @@ public sealed class PrimeTester
|
|||||||
{
|
{
|
||||||
internal sealed class PooledContext
|
internal sealed class PooledContext
|
||||||
{
|
{
|
||||||
|
|
||||||
public Context Context { get; }
|
public Context Context { get; }
|
||||||
|
|
||||||
public Accelerator Accelerator { get; }
|
public Accelerator Accelerator { get; }
|
||||||
|
|
||||||
public object ExecutionLock { get; } = new();
|
public object ExecutionLock { get; } = new();
|
||||||
|
|
||||||
|
private KernelState? _kernelState;
|
||||||
|
|
||||||
public PooledContext()
|
public PooledContext()
|
||||||
{
|
{
|
||||||
Context = Context.CreateDefault();
|
Context = Context.CreateDefault();
|
||||||
Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context);
|
Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context);
|
||||||
|
_kernelState = GpuKernelState.GetOrCreate(Accelerator);
|
||||||
|
}
|
||||||
|
|
||||||
|
public KernelState KernelState
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var state = _kernelState;
|
||||||
|
if (state is null || state.IsDisposed)
|
||||||
|
{
|
||||||
|
state = GpuKernelState.GetOrCreate(Accelerator);
|
||||||
|
_kernelState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
return state;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
PrimeTester.ClearGpuCaches(Accelerator);
|
ClearGpuCaches(Accelerator);
|
||||||
|
_kernelState = null;
|
||||||
Accelerator.Dispose();
|
Accelerator.Dispose();
|
||||||
Context.Dispose();
|
Context.Dispose();
|
||||||
}
|
}
|
||||||
@@ -215,6 +263,8 @@ public sealed class PrimeTester
|
|||||||
|
|
||||||
public object ExecutionLock => _ctx!.ExecutionLock;
|
public object ExecutionLock => _ctx!.ExecutionLock;
|
||||||
|
|
||||||
|
public KernelState State => _ctx!.KernelState;
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
Return(_ctx ?? throw new InvalidOperationException("GPU context lease is not initialized."));
|
Return(_ctx ?? throw new InvalidOperationException("GPU context lease is not initialized."));
|
||||||
@@ -225,10 +275,14 @@ public sealed class PrimeTester
|
|||||||
// Per-accelerator GPU state for prime sieve (kernel + uploaded primes).
|
// Per-accelerator GPU state for prime sieve (kernel + uploaded primes).
|
||||||
internal sealed class KernelState
|
internal sealed class KernelState
|
||||||
{
|
{
|
||||||
public Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>> Kernel { get; }
|
public Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<byte>> Kernel { get; }
|
||||||
public Action<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>> HeuristicTrialDivisionKernel { get; }
|
public Action<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>> HeuristicTrialDivisionKernel { get; }
|
||||||
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimes { get; }
|
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesDefault { get; }
|
||||||
private readonly Accelerator _accel;
|
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne { get; }
|
||||||
|
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven { get; }
|
||||||
|
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree { get; }
|
||||||
|
public MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine { get; }
|
||||||
|
public bool IsDisposed { get; private set; }
|
||||||
private readonly System.Collections.Concurrent.ConcurrentBag<ScratchBuffers> _scratchPool = [];
|
private readonly System.Collections.Concurrent.ConcurrentBag<ScratchBuffers> _scratchPool = [];
|
||||||
// TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in
|
// TODO: Replace this ConcurrentBag with the lock-free ring buffer variant validated in
|
||||||
// GpuModularArithmeticBenchmarks so renting scratch buffers stops contending on the bag's internal locks when
|
// GpuModularArithmeticBenchmarks so renting scratch buffers stops contending on the bag's internal locks when
|
||||||
@@ -236,14 +290,30 @@ public sealed class PrimeTester
|
|||||||
|
|
||||||
public KernelState(Accelerator accelerator)
|
public KernelState(Accelerator accelerator)
|
||||||
{
|
{
|
||||||
_accel = accelerator;
|
IsDisposed = false;
|
||||||
// Compile once per accelerator and upload primes once.
|
// Compile once per accelerator and upload primes once.
|
||||||
Kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
|
Kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel);
|
||||||
HeuristicTrialDivisionKernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>>(PrimeTesterKernels.HeuristicTrialDivisionKernel);
|
HeuristicTrialDivisionKernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ulong, ArrayView<byte>>(PrimeTesterKernels.HeuristicTrialDivisionKernel);
|
||||||
|
|
||||||
var primes = PrimesGenerator.SmallPrimes;
|
var primesDefault = DivisorGenerator.SmallPrimes;
|
||||||
DevicePrimes = accelerator.Allocate1D<uint>(primes.Length);
|
DevicePrimesDefault = accelerator.Allocate1D<uint>(primesDefault.Length);
|
||||||
DevicePrimes.View.CopyFromCPU(primes);
|
DevicePrimesDefault.View.CopyFromCPU(primesDefault);
|
||||||
|
|
||||||
|
var primesLastOne = DivisorGenerator.SmallPrimesLastOne;
|
||||||
|
DevicePrimesLastOne = accelerator.Allocate1D<uint>(primesLastOne.Length);
|
||||||
|
DevicePrimesLastOne.View.CopyFromCPU(primesLastOne);
|
||||||
|
|
||||||
|
var primesLastSeven = DivisorGenerator.SmallPrimesLastSeven;
|
||||||
|
DevicePrimesLastSeven = accelerator.Allocate1D<uint>(primesLastSeven.Length);
|
||||||
|
DevicePrimesLastSeven.View.CopyFromCPU(primesLastSeven);
|
||||||
|
|
||||||
|
var primesLastThree = DivisorGenerator.SmallPrimesLastThree;
|
||||||
|
DevicePrimesLastThree = accelerator.Allocate1D<uint>(primesLastThree.Length);
|
||||||
|
DevicePrimesLastThree.View.CopyFromCPU(primesLastThree);
|
||||||
|
|
||||||
|
var primesLastNine = DivisorGenerator.SmallPrimesLastNine;
|
||||||
|
DevicePrimesLastNine = accelerator.Allocate1D<uint>(primesLastNine.Length);
|
||||||
|
DevicePrimesLastNine.View.CopyFromCPU(primesLastNine);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal sealed class ScratchBuffers
|
internal sealed class ScratchBuffers
|
||||||
@@ -291,12 +361,22 @@ public sealed class PrimeTester
|
|||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
|
if (IsDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (_scratchPool.TryTake(out var sb))
|
while (_scratchPool.TryTake(out var sb))
|
||||||
{
|
{
|
||||||
sb.Dispose();
|
sb.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
DevicePrimes.Dispose();
|
DevicePrimesDefault.Dispose();
|
||||||
|
DevicePrimesLastOne.Dispose();
|
||||||
|
DevicePrimesLastSeven.Dispose();
|
||||||
|
DevicePrimesLastThree.Dispose();
|
||||||
|
DevicePrimesLastNine.Dispose();
|
||||||
|
IsDisposed = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,151 +1,11 @@
|
|||||||
using System;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace PerfectNumbers.Core;
|
namespace PerfectNumbers.Core;
|
||||||
|
|
||||||
public static class PrimesGenerator
|
public static class PrimesGenerator
|
||||||
{
|
{
|
||||||
public static readonly uint[] SmallPrimes;
|
public static readonly uint[] SmallPrimes = DivisorGenerator.SmallPrimes;
|
||||||
public static readonly ulong[] SmallPrimesPow2;
|
public static readonly ulong[] SmallPrimesPow2 = DivisorGenerator.SmallPrimesPow2;
|
||||||
public static readonly uint[] SmallPrimesLastOne;
|
public static readonly uint[] SmallPrimesLastOne = DivisorGenerator.SmallPrimesLastOne;
|
||||||
public static readonly ulong[] SmallPrimesPow2LastOne;
|
public static readonly ulong[] SmallPrimesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne;
|
||||||
public static readonly uint[] SmallPrimesLastSeven;
|
public static readonly uint[] SmallPrimesLastSeven = DivisorGenerator.SmallPrimesLastSeven;
|
||||||
public static readonly ulong[] SmallPrimesPow2LastSeven;
|
public static readonly ulong[] SmallPrimesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven;
|
||||||
|
|
||||||
static PrimesGenerator()
|
|
||||||
{
|
|
||||||
BuildSmallPrimes(
|
|
||||||
out SmallPrimes,
|
|
||||||
out SmallPrimesPow2,
|
|
||||||
out SmallPrimesLastOne,
|
|
||||||
out SmallPrimesPow2LastOne,
|
|
||||||
out SmallPrimesLastSeven,
|
|
||||||
out SmallPrimesPow2LastSeven);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void BuildSmallPrimes(
|
|
||||||
out uint[] all,
|
|
||||||
out ulong[] allPow2,
|
|
||||||
out uint[] lastOne,
|
|
||||||
out ulong[] lastOnePow2,
|
|
||||||
out uint[] lastSeven,
|
|
||||||
out ulong[] lastSevenPow2)
|
|
||||||
{
|
|
||||||
uint target = PerfectNumberConstants.PrimesLimit;
|
|
||||||
int targetInt = checked((int)target);
|
|
||||||
all = new uint[targetInt];
|
|
||||||
allPow2 = new ulong[targetInt];
|
|
||||||
lastOne = new uint[targetInt];
|
|
||||||
lastOnePow2 = new ulong[targetInt];
|
|
||||||
lastSeven = new uint[targetInt];
|
|
||||||
lastSevenPow2 = new ulong[targetInt];
|
|
||||||
|
|
||||||
List<uint> primes = new(targetInt * 4 / 3 + 16);
|
|
||||||
uint candidate = 2U;
|
|
||||||
int allCount = 0;
|
|
||||||
int lastOneCount = 0;
|
|
||||||
int lastSevenCount = 0;
|
|
||||||
|
|
||||||
while (allCount < targetInt || lastOneCount < targetInt || lastSevenCount < targetInt)
|
|
||||||
{
|
|
||||||
bool isPrime = true;
|
|
||||||
int primesCount = primes.Count;
|
|
||||||
for (int i = 0; i < primesCount; i++)
|
|
||||||
{
|
|
||||||
uint p = primes[i];
|
|
||||||
// TODO: Reuse a single squared local for `p` within this loop so we avoid recalculating
|
|
||||||
// `p * p` repeatedly without storing every squared prime globally (the cache must remain
|
|
||||||
// limited to the small-divisor tables to honor the memory constraints).
|
|
||||||
if (p * p > candidate)
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Replace this trial-division `%` with the sieve-based generator that avoids
|
|
||||||
// per-candidate modulo work so building the small-prime tables stops dominating
|
|
||||||
// startup time for large scans.
|
|
||||||
if (candidate % p == 0U)
|
|
||||||
{
|
|
||||||
isPrime = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isPrime)
|
|
||||||
{
|
|
||||||
primes.Add(candidate);
|
|
||||||
if (allCount < targetInt)
|
|
||||||
{
|
|
||||||
// TODO: Reuse a single cached `candidateSquared` per iteration so we avoid issuing
|
|
||||||
// three separate 64-bit multiplications when populating the pow2 arrays here and
|
|
||||||
// in the last-one/last-seven branches below.
|
|
||||||
all[allCount] = candidate;
|
|
||||||
allPow2[allCount] = checked((ulong)candidate * candidate);
|
|
||||||
allCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (candidate != 2U)
|
|
||||||
{
|
|
||||||
LastDigit lastDigit = GetLastDigit(candidate);
|
|
||||||
|
|
||||||
if (lastOneCount < targetInt && IsAllowedForLastOne(candidate, lastDigit))
|
|
||||||
{
|
|
||||||
lastOne[lastOneCount] = candidate;
|
|
||||||
lastOnePow2[lastOneCount] = checked((ulong)candidate * candidate);
|
|
||||||
lastOneCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastSevenCount < targetInt && IsAllowedForLastSeven(candidate, lastDigit))
|
|
||||||
{
|
|
||||||
lastSeven[lastSevenCount] = candidate;
|
|
||||||
lastSevenPow2[lastSevenCount] = checked((ulong)candidate * candidate);
|
|
||||||
lastSevenCount++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Switch this increment to the Mod6 stepping table identified in the Mod6ComparisonBenchmarks
|
|
||||||
// so we skip the composite residues without relying on per-iteration `%` checks.
|
|
||||||
candidate = candidate == 2U ? 3U : candidate + 2U;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static LastDigit GetLastDigit(uint prime)
|
|
||||||
{
|
|
||||||
return (prime % 10U) switch
|
|
||||||
{
|
|
||||||
1U => LastDigit.One,
|
|
||||||
3U => LastDigit.Three,
|
|
||||||
5U => LastDigit.Five,
|
|
||||||
7U => LastDigit.Seven,
|
|
||||||
9U => LastDigit.Nine,
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(prime)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit)
|
|
||||||
{
|
|
||||||
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
|
|
||||||
// reuses the benchmarked residue helper instead of repeated divisions.
|
|
||||||
return lastDigit switch
|
|
||||||
{
|
|
||||||
LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
|
|
||||||
LastDigit.Seven => prime == 7U,
|
|
||||||
_ => prime == 11U,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
|
|
||||||
{
|
|
||||||
// TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster
|
|
||||||
// residue helper used elsewhere in the scanner.
|
|
||||||
return lastDigit switch
|
|
||||||
{
|
|
||||||
LastDigit.Three or LastDigit.Seven or LastDigit.Nine => true,
|
|
||||||
_ => prime == 11U,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user