Compare commits

..
Author SHA1 Message Date
Marek Lemańczyk 6027a73839 Split GPU heuristic tables by scope 2025-11-01 09:22:44 +01:00
127 changed files with 8082 additions and 8876 deletions

No files matched your search

+1 -3
View File
@@ -9,7 +9,5 @@
"Mersenne", "Mersenne",
"primality", "primality",
"suff" "suff"
], ]
"geminicodeassist.project": "evenperfectbitscanner",
"todo-tree.tree.showCountsInTree": true
} }
@@ -1,3 +1,4 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,5 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using PerfectNumbers.Core;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,3 +1,5 @@
using System;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
public enum ModNumericType public enum ModNumericType
@@ -1,3 +1,4 @@
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -1,8 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,3 +1,4 @@
using System;
using System.Buffers.Binary; using System.Buffers.Binary;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
@@ -100,7 +101,7 @@ public class Pow2ModBenchmarks
for (int i = 0; i < SampleCount; i++) for (int i = 0; i < SampleCount; i++)
{ {
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]); checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
} }
return checksum; return checksum;
@@ -77,7 +77,7 @@ public class Pow2MontgomeryModBenchmarks
for (int i = 0; i < SampleCount; i++) for (int i = 0; i < SampleCount; i++)
{ {
checksum ^= exponents[i].Pow2MontgomeryModWindowedConvertGpu(divisors[i]); checksum ^= exponents[i].Pow2MontgomeryModWindowedGpu(divisors[i], keepMontgomery: false);
} }
return checksum; return checksum;
@@ -1,3 +1,4 @@
using System;
using PerfectNumbers.Core; using PerfectNumbers.Core;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -1,3 +1,4 @@
using System;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -33,7 +34,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
for (int i = 0; i < cases.Length; i++) for (int i = 0; i < cases.Length; i++)
{ {
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedConvertGpu(current.Divisor); checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: false);
} }
return checksum; return checksum;
@@ -51,7 +52,7 @@ public class Pow2MontgomeryModWindowedKeepMontgomeryGpuBenchmarks
for (int i = 0; i < cases.Length; i++) for (int i = 0; i < cases.Length; i++)
{ {
ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i]; ref readonly Pow2MontgomeryModWindowedBenchmarkCase current = ref cases[i];
checksum ^= current.Exponent.Pow2MontgomeryModWindowedKeepGpu(current.Divisor); checksum ^= current.Exponent.Pow2MontgomeryModWindowedGpu(current.Divisor, keepMontgomery: true);
} }
return checksum; return checksum;
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Threading;
using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Jobs;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace EvenPerfectBitScanner.Benchmarks; namespace EvenPerfectBitScanner.Benchmarks;
@@ -10,6 +12,7 @@ namespace EvenPerfectBitScanner.Benchmarks;
[MemoryDiagnoser] [MemoryDiagnoser]
public class PrimeTesterBenchmarks public class PrimeTesterBenchmarks
{ {
private static readonly HeuristicCombinedPrimeTester _heuristicCombinedTester = new();
private static readonly HeuristicPrimeTester _heuristicTester = new(); private static readonly HeuristicPrimeTester _heuristicTester = new();
private ulong[] _candidates = []; private ulong[] _candidates = [];
@@ -26,14 +29,12 @@ public class PrimeTesterBenchmarks
return; return;
} }
PrimeOrderCalculatorAccelerator.WarmUp(); _ = _heuristicTester.IsPrimeCpu(_candidates[0]);
// HeuristicGroupABPrimeTesterAccelerator.WarmUp(); _ = _heuristicTester.IsPrimeGpu(_candidates[0]);
_ = HeuristicPrimeTester.IsPrimeCpu(_candidates[0]);
_ = HeuristicPrimeTester.IsPrimeGpu(_candidates[0]);
_ = Prime.Numbers.IsPrime(_candidates[0]); _ = Prime.Numbers.IsPrime(_candidates[0]);
} }
// [Benchmark] [Benchmark(Baseline = true)]
public int HeuristicCpu() public int HeuristicCpu()
{ {
HeuristicPrimeTester tester = _heuristicTester; HeuristicPrimeTester tester = _heuristicTester;
@@ -42,7 +43,7 @@ public class PrimeTesterBenchmarks
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (HeuristicPrimeTester.IsPrimeCpu(values[i])) if (tester.IsPrimeCpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -60,7 +61,7 @@ public class PrimeTesterBenchmarks
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (HeuristicPrimeTester.IsPrimeGpu(values[i])) if (tester.IsPrimeGpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -103,15 +104,18 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
private static readonly PrimeTester _nonHeuristicTester = new();
[Benchmark] [Benchmark]
public int NonHeuristicGpu() public int NonHeuristicGpu()
{ {
PrimeTester tester = _nonHeuristicTester;
ulong[] values = _candidates; ulong[] values = _candidates;
int primeCount = 0; int primeCount = 0;
for (int i = 0; i < values.Length; i++) for (int i = 0; i < values.Length; i++)
{ {
if (PrimeTester.IsPrimeGpu(values[i])) if (tester.IsPrimeGpu(values[i]))
{ {
primeCount++; primeCount++;
} }
@@ -120,7 +124,7 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
[Benchmark(Baseline = true)] [Benchmark]
public int NonHeuristicCpu() public int NonHeuristicCpu()
{ {
ulong[] values = _candidates; ulong[] values = _candidates;
@@ -137,7 +141,7 @@ public class PrimeTesterBenchmarks
return primeCount; return primeCount;
} }
// [Benchmark] [Benchmark]
public int OpenNumericPrimes() public int OpenNumericPrimes()
{ {
ulong[] values = _candidates; ulong[] values = _candidates;
@@ -19,7 +19,7 @@ internal static class Program
"/help" "/help"
}; };
private const string DefaultHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests"; private const string DefaultHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
private static int Main(string[] args) private static int Main(string[] args)
{ {
@@ -3,9 +3,13 @@ using Xunit;
using System.Reflection; using System.Reflection;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using EvenPerfectBitScanner;
using EvenPerfectBitScanner.Candidates; using EvenPerfectBitScanner.Candidates;
using EvenPerfectBitScanner.Candidates.Transforms; using EvenPerfectBitScanner.Candidates.Transforms;
using System.Numerics; using System.Numerics;
using System.Threading;
using System.Threading.Tasks;
namespace EvenPerfectBitScanner.Tests; namespace EvenPerfectBitScanner.Tests;
@@ -98,11 +102,13 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!; var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) }); candidatesField.SetValue(null, new (ulong, uint)[] { (7UL, 3U), (23UL, 11U) });
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -116,6 +122,7 @@ public class ProgramTests
candidatesField.SetValue(null, null); candidatesField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -128,11 +135,13 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!; var candidatesField = typeof(MersenneNumberDivisorGpuTester).GetField("_divisorCandidates", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
candidatesField.SetValue(null, Array.Empty<(ulong, uint)>()); candidatesField.SetValue(null, Array.Empty<(ulong, uint)>());
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -146,6 +155,7 @@ public class ProgramTests
candidatesField.SetValue(null, null); candidatesField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -158,10 +168,12 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -174,6 +186,7 @@ public class ProgramTests
testerField.SetValue(null, null); testerField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -186,10 +199,12 @@ public class ProgramTests
var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!; var testerField = typeof(Program).GetField("_divisorTester", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
testerField.SetValue(null, new MersenneNumberDivisorGpuTester()); testerField.SetValue(null, new MersenneNumberDivisorGpuTester());
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -206,6 +221,7 @@ public class ProgramTests
testerField.SetValue(null, null); testerField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -217,10 +233,12 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: true, maxK: 1_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -237,6 +255,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -248,6 +267,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -255,6 +275,7 @@ public class ProgramTests
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -271,6 +292,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -283,6 +305,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -290,6 +313,7 @@ public class ProgramTests
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -306,6 +330,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -317,6 +342,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -324,6 +350,7 @@ public class ProgramTests
maxK: 5_000_000UL), trackAllValues: true)); maxK: 5_000_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -340,6 +367,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -352,6 +380,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true, useIncremental: true,
@@ -361,6 +390,7 @@ public class ProgramTests
maxK: 1_024UL), trackAllValues: true)); maxK: 1_024UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -372,6 +402,7 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -386,6 +417,7 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester( mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: false, useIncremental: false,
@@ -396,6 +428,7 @@ public class ProgramTests
maxK: configuration.ResidueMaxK), trackAllValues: true)); maxK: configuration.ResidueMaxK), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, exponent, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try try
{ {
@@ -407,7 +440,9 @@ public class ProgramTests
mersenneField.SetValue(null, null); mersenneField.SetValue(null, null);
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
GpuContextPool.DisposeAll();
} }
} }
@@ -471,10 +506,12 @@ public class ProgramTests
var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var mersenneField = typeof(Program).GetField("MersenneTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!; var primeField = typeof(Program).GetField("PrimeTesters", BindingFlags.NonPublic | BindingFlags.Static)!;
var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!; var compositeField = typeof(Program).GetField("_lastCompositeP", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true)); mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(useIncremental: true, useResidue: false, maxK: 1_000UL), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true)); primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true)); CandidatesCalculator.OverrideResidueTrackers(new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, true);
try try
{ {
@@ -495,6 +532,7 @@ public class ProgramTests
primeField.SetValue(null, null); primeField.SetValue(null, null);
CandidatesCalculator.ResetResidueTrackers(); CandidatesCalculator.ResetResidueTrackers();
compositeField.SetValue(null, false); compositeField.SetValue(null, false);
forceCpuProp!.SetValue(null, false);
ResetCliArguments(); ResetCliArguments();
} }
} }
@@ -1,3 +1,6 @@
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -1,13 +1,15 @@
using System.Buffers.Text; using System.Buffers.Text;
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading;
using PerfectNumbers.Core; using PerfectNumbers.Core;
namespace EvenPerfectBitScanner; namespace EvenPerfectBitScanner;
internal static class CalculationResultHandler internal static class CalculationResultHandler
{ {
private const string ResultsHeader = "p,divisor,searchedMersenne,detailedCheck,passedAllTests"; private const string ResultsHeader = "p,searchedMersenne,detailedCheck,passedAllTests";
private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)"; private const string PrimeFoundSuffix = " (FOUND VALID CANDIDATES)";
internal const int DefaultWriteBatchSize = 100; internal const int DefaultWriteBatchSize = 100;
@@ -93,19 +95,19 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void HandleResult(ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite) internal static void HandleResult(ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests, bool lastWasComposite)
{ {
// Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here. // Program flow ensures Initialize(...) and InitializeOutputBuffer() ran before this call. Do not add initialization guards here.
Span<byte> stackBuffer = stackalloc byte[128]; Span<byte> stackBuffer = stackalloc byte[128];
byte[]? rentedBuffer = null; byte[]? rentedBuffer = null;
Span<byte> utf8Span = stackBuffer; Span<byte> utf8Span = stackBuffer;
int byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); int byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
rentedBuffer = ThreadStaticPools.BytePool.Rent(256); rentedBuffer = ThreadStaticPools.BytePool.Rent(256);
utf8Span = rentedBuffer; utf8Span = rentedBuffer;
byteCount = TryFormatResultLine(utf8Span, currentP, divisor, searchedMersenne, detailedCheck, passedAllTests); byteCount = TryFormatResultLine(utf8Span, currentP, searchedMersenne, detailedCheck, passedAllTests);
if (byteCount < 0) if (byteCount < 0)
{ {
ThreadStaticPools.BytePool.Return(rentedBuffer); ThreadStaticPools.BytePool.Return(rentedBuffer);
@@ -229,7 +231,7 @@ internal static class CalculationResultHandler
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int TryFormatResultLine(Span<byte> destination, ulong currentP, ulong divisor, bool searchedMersenne, bool detailedCheck, bool passedAllTests) private static int TryFormatResultLine(Span<byte> destination, ulong currentP, bool searchedMersenne, bool detailedCheck, bool passedAllTests)
{ {
int offset = 0; int offset = 0;
@@ -246,19 +248,6 @@ internal static class CalculationResultHandler
destination[offset++] = (byte)','; destination[offset++] = (byte)',';
if (!Utf8Formatter.TryFormat(divisor, destination[offset..], out written))
{
return -1;
}
offset += written;
if (offset >= destination.Length)
{
return -1;
}
destination[offset++] = (byte)',';
if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written)) if (!Utf8Formatter.TryFormat(searchedMersenne, destination[offset..], out written))
{ {
return -1; return -1;
@@ -1,4 +1,5 @@
using System.Globalization; using System.Globalization;
using System.IO;
namespace EvenPerfectBitScanner.Candidates; namespace EvenPerfectBitScanner.Candidates;
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using EvenPerfectBitScanner.Candidates.Transforms; using EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,4 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using EvenPerfectBitScanner.Candidates;
using Open.Numeric.Primes; using Open.Numeric.Primes;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,4 +1,6 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
using EvenPerfectBitScanner.Candidates;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading;
namespace EvenPerfectBitScanner.Candidates.Transforms; namespace EvenPerfectBitScanner.Candidates.Transforms;
+2
View File
@@ -1,4 +1,6 @@
using System;
using System.Globalization; using System.Globalization;
using System.Numerics;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
@@ -1,6 +1,7 @@
using Open.Collections; using Open.Collections;
using PerfectNumbers.Core; using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using EvenPerfectBitScanner;
namespace EvenPerfectBitScanner.IO; namespace EvenPerfectBitScanner.IO;
@@ -100,7 +101,6 @@ internal static class CalculationResultsFile
int first = 0; int first = 0;
int second = 0; int second = 0;
int third = 0; int third = 0;
int fourth = 0;
ulong parsedP = 0UL; ulong parsedP = 0UL;
ReadOnlySpan<char> detailedSpan = default; ReadOnlySpan<char> detailedSpan = default;
ReadOnlySpan<char> passedAllTestsSpan = default; ReadOnlySpan<char> passedAllTestsSpan = default;
@@ -140,15 +140,8 @@ internal static class CalculationResultsFile
continue; continue;
} }
span = span[(third + 1)..]; detailedSpan = span[..third];
fourth = span.IndexOf(','); passedAllTestsSpan = span[(third + 1)..];
if (fourth < 0)
{
continue;
}
detailedSpan = span[..fourth];
passedAllTestsSpan = span[(fourth + 1)..];
if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests)) if (Utf8CliParser.TryParseBoolean(detailedSpan, out detailed) && Utf8CliParser.TryParseBoolean(passedAllTestsSpan, out passedAllTests))
{ {
+13 -13
View File
@@ -12,7 +12,7 @@ namespace EvenPerfectBitScanner;
internal static class Program internal static class Program
{ {
// private static ThreadLocal<PrimeTester> PrimeTesters = null!; private static ThreadLocal<PrimeTester> PrimeTesters = null!;
private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!; private static ThreadLocal<MersenneNumberTester> MersenneTesters = null!;
private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP; private const ulong InitialP = PerfectNumberConstants.BiggestKnownEvenPerfectP;
private static MersenneNumberDivisorGpuTester? _divisorTester; private static MersenneNumberDivisorGpuTester? _divisorTester;
@@ -58,17 +58,14 @@ internal static class Program
bool startPrimeProvided = _cliArguments.StartPrimeProvided; bool startPrimeProvided = _cliArguments.StartPrimeProvided;
int threadCount = Math.Max(1, _cliArguments.ThreadCount); int threadCount = Math.Max(1, _cliArguments.ThreadCount);
int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch); int gpuPrimeBatch = Math.Max(1, _cliArguments.GpuPrimeBatch);
// int gpuPrimeThreads = SharedGpuContext.Device.MaxNumThreads;
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
UnboundedTaskScheduler.ConfigureThreadCount(threadCount); UnboundedTaskScheduler.ConfigureThreadCount(threadCount);
PrimeTester.GpuBatchSize = gpuPrimeBatch; PrimeTester.GpuBatchSize = gpuPrimeBatch;
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads); GpuContextPool.WarmUpPool(threadCount);
Console.WriteLine("Warming up GPU kernels"); PrimeTester.WarmUpGpuKernels(threadCount);
PrimeTester.WarmUpGpuKernels(1024);
// PrimeTester.WarmUpGpuKernels(gpuPrimeThreads >> 4);
Console.WriteLine("Starting up threads..."); Console.WriteLine("Starting up threads...");
_ = UnboundedTaskScheduler.Instance; _ = UnboundedTaskScheduler.Instance;
int blockSize = Math.Max(1, _cliArguments.BlockSize); int blockSize = Math.Max(1, _cliArguments.BlockSize);
int gpuPrimeThreads = Math.Max(1, _cliArguments.GpuPrimeThreads);
GpuKernelType kernelType = _cliArguments.KernelType; GpuKernelType kernelType = _cliArguments.KernelType;
bool useBitTransform = _cliArguments.UseBitTransform; bool useBitTransform = _cliArguments.UseBitTransform;
bool useOrder = _cliArguments.UseOrder; bool useOrder = _cliArguments.UseOrder;
@@ -120,6 +117,9 @@ internal static class Program
return; return;
} }
// Apply GPU prime sieve runtime configuration
GpuPrimeWorkLimiter.SetLimit(gpuPrimeThreads);
PrimeTester.GpuBatchSize = gpuPrimeBatch;
MersenneDivisorCycles mersenneDivisorCycles = new(); MersenneDivisorCycles mersenneDivisorCycles = new();
if (!File.Exists(cyclesPath) || continueCyclesGeneration) if (!File.Exists(cyclesPath) || continueCyclesGeneration)
@@ -178,7 +178,7 @@ internal static class Program
blockSize = useByDivisor ? 1 : blockSize; blockSize = useByDivisor ? 1 : blockSize;
CandidatesCalculator.Initialize(currentP); CandidatesCalculator.Initialize(currentP);
// PrimeTesters = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true); PrimeTesters = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
// Note: --primes-device controls default device for library kernels; p primality remains CPU here. // Note: --primes-device controls default device for library kernels; p primality remains CPU here.
// Initialize per-thread p residue tracker (Identity model) at currentP // Initialize per-thread p residue tracker (Identity model) at currentP
if (!useDivisor && !_cliArguments.UseByDivisor) if (!useDivisor && !_cliArguments.UseByDivisor)
@@ -422,7 +422,7 @@ internal static class Program
_byDivisorStartPrime, _byDivisorStartPrime,
static () => _lastCompositeP = true, static () => _lastCompositeP = true,
static () => _lastCompositeP = false, static () => _lastCompositeP = false,
static (candidate, searchedMersenne, detailedCheck, passedAllTests, divisor) => CalculationResultHandler.HandleResult(candidate, divisor, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP), static (candidate, searchedMersenne, detailedCheck, passedAllTests) => CalculationResultHandler.HandleResult(candidate, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP),
threadCount, threadCount,
byDivisorPrimeRange); byDivisorPrimeRange);
@@ -483,7 +483,7 @@ internal static class Program
{ {
p = buffer[j]; p = buffer[j];
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
} }
continue; continue;
@@ -506,7 +506,7 @@ internal static class Program
} }
passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck); passedAllTests = IsEvenPerfectCandidate(p, divisorCyclesSearchLimit, out searchedMersenne, out detailedCheck);
CalculationResultHandler.HandleResult(p, 0UL, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP); CalculationResultHandler.HandleResult(p, searchedMersenne, detailedCheck, passedAllTests, _lastCompositeP);
if (p == maxP) if (p == maxP)
{ {
@@ -628,7 +628,7 @@ internal static class Program
{ {
if (!(_runPrimesOnCpu if (!(_runPrimesOnCpu
? PrimeTester.IsPrime(p) ? PrimeTester.IsPrime(p)
: HeuristicCombinedPrimeTester.IsPrimeGpu(p))) : PrimeTesters.Value!.IsPrimeGpu(p)))
{ {
_lastCompositeP = true; _lastCompositeP = true;
return false; return false;
@@ -649,7 +649,7 @@ internal static class Program
if (_cliArguments.UseByDivisor) if (_cliArguments.UseByDivisor)
{ {
// Windowed pow2mod kernel handles by-divisor scans. // Windowed pow2mod kernel handles by-divisor scans.
return _byDivisorTester!.IsPrime(p, out detailedCheck, out _); return _byDivisorTester!.IsPrime(p, out detailedCheck);
} }
detailedCheck = MersenneTesters.Value!.IsMersennePrime(p); detailedCheck = MersenneTesters.Value!.IsMersennePrime(p);
+1
View File
@@ -1,3 +1,4 @@
using System;
using System.Buffers; using System.Buffers;
using System.Buffers.Text; using System.Buffers.Text;
using System.Text; using System.Text;
@@ -1,3 +1,7 @@
using FluentAssertions;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -1,8 +1,8 @@
using System.Reflection; using System.Reflection;
using FluentAssertions;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -114,10 +114,14 @@ public class GpuKernelCompilationTests
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static) typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found."); ?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModKernel not found.");
private static readonly MethodInfo PrimeOrderGetPow2ModWideKernelMethod =
typeof(PrimeOrderGpuHeuristics).GetMethod("GetPow2ModWideKernel", BindingFlags.NonPublic | BindingFlags.Static)
?? throw new InvalidOperationException("PrimeOrderGpuHeuristics.GetPow2ModWideKernel not found.");
private static readonly Action<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> MersenneDivisorCyclesKernel = 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<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> PrimeTesterSmallPrimeKernel = private static readonly Action<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, 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 =
@@ -321,67 +325,56 @@ public class GpuKernelCompilationTests
private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator) private static void CompilePrimeOrderPow2ModWideKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderGpuHeuristics.Pow2ModKernelWide); _ = PrimeOrderGetPow2ModWideKernelMethod.Invoke(null, new object[] { accelerator });
_ = kernel;
} }
private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator) private static void CompileMersenneDivisorCyclesKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel(MersenneDivisorCyclesKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(MersenneDivisorCyclesKernel);
_ = kernel; _ = kernel;
} }
private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator) private static void CompilePrimeTesterSmallPrimeKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSmallPrimeKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSmallPrimeKernel);
_ = kernel; _ = kernel;
} }
private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator) private static void CompilePrimeTesterSharesFactorKernel(Accelerator accelerator)
{ {
var kernel = accelerator.LoadAutoGroupedStreamKernel(PrimeTesterSharesFactorKernel); var kernel = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterSharesFactorKernel);
_ = kernel; _ = kernel;
} }
private static KernelType _kernelType = KernelType.Pow2ModKernelScan | KernelType.OrderKernelScan | KernelType.IncrementalKernelScan | KernelType.IncrementalOrderKernelScan | KernelType.Pow2ModOrderKernelScan; private static void CompileGpuKernelPoolKernels(Accelerator accelerator)
private static void CompileGpuKernelPoolKernels(Accelerator accelerator) {
{ _ = accelerator;
var lease = GpuKernelPool.GetKernel(useGpuOrder: false);
try
{
_ = lease.OrderKernel;
_ = lease.IncrementalKernel;
_ = lease.Pow2ModKernel;
_ = lease.IncrementalOrderKernel;
_ = lease.Pow2ModOrderKernel;
}
finally
{
lease.Dispose();
}
GpuPrimeWorkLimiter.Acquire(); var orderLease = GpuKernelPool.GetKernel(useGpuOrder: true);
int acceleratorIndex = AcceleratorPool.Shared.Rent(); try
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex); {
try _ = orderLease.OrderKernel;
{ _ = orderLease.IncrementalKernel;
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType); _ = orderLease.Pow2ModKernel;
_ = kernels.Order; _ = orderLease.IncrementalOrderKernel;
_ = kernels.Incremental; _ = orderLease.Pow2ModOrderKernel;
_ = kernels.Pow2Mod; }
_ = kernels.IncrementalOrder; finally
_ = kernels.Pow2ModOrder; {
} orderLease.Dispose();
finally }
{ }
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
}
stream = AcceleratorStreamPool.Rent(acceleratorIndex);
GpuPrimeWorkLimiter.Acquire();
try
{
var kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, _kernelType);
_ = kernels.Order;
_ = kernels.Incremental;
_ = kernels.Pow2Mod;
_ = kernels.IncrementalOrder;
_ = kernels.Pow2ModOrder;
}
finally
{
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
}
}
} }
@@ -18,19 +18,19 @@ public class GpuPrimeLimiterTests
var startedSecond = new TaskCompletionSource<bool>(); var startedSecond = new TaskCompletionSource<bool>();
var sw = new Stopwatch(); var sw = new Stopwatch();
GpuPrimeWorkLimiter.Acquire(); var first = GpuPrimeWorkLimiter.Acquire();
sw.Start(); sw.Start();
var secondTask = Task.Run(() => var secondTask = Task.Run(() =>
{ {
GpuPrimeWorkLimiter.Acquire(); var second = GpuPrimeWorkLimiter.Acquire();
try try
{ {
startedSecond.TrySetResult(true); startedSecond.TrySetResult(true);
} }
finally finally
{ {
GpuPrimeWorkLimiter.Release(); second.Dispose();
} }
}); });
@@ -43,7 +43,7 @@ public class GpuPrimeLimiterTests
finally finally
{ {
// Release first after the initial wait so the second task can proceed. // Release first after the initial wait so the second task can proceed.
GpuPrimeWorkLimiter.Release(); first.Dispose();
} }
await secondTask.WaitAsync(TimeSpan.FromSeconds(1)); await secondTask.WaitAsync(TimeSpan.FromSeconds(1));
+343 -328
View File
@@ -13,352 +13,367 @@ namespace PerfectNumbers.Core.Tests.Gpu;
[Collection("GpuNtt")] [Collection("GpuNtt")]
public class NttGpuMathTests public class NttGpuMathTests
{ {
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void ForwardGpu_reference_matches_cpu_forward() public void ForwardGpu_reference_matches_cpu_forward()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Reference; NttGpuMath.GpuTransformBackend = NttBackend.Reference;
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
cpu[0] = new GpuUInt128(0UL, 1UL); cpu[0] = new GpuUInt128(0UL, 1UL);
cpu[1] = new GpuUInt128(0UL, 2UL); cpu[1] = new GpuUInt128(0UL, 2UL);
cpu[2] = new GpuUInt128(0UL, 3UL); cpu[2] = new GpuUInt128(0UL, 3UL);
cpu[3] = new GpuUInt128(0UL, 4UL); cpu[3] = new GpuUInt128(0UL, 4UL);
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
cpu.CopyTo(gpu); cpu.CopyTo(gpu);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
NttGpuMath.Forward(cpu, modulus, primitiveRoot); NttGpuMath.Forward(cpu, modulus, primitiveRoot);
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot); NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
for (int i = 0; i < cpu.Length; i++) for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void ForwardGpu_staged_matches_cpu_forward() public void ForwardGpu_staged_matches_cpu_forward()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Staged; NttGpuMath.GpuTransformBackend = NttBackend.Staged;
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
cpu[0] = new GpuUInt128(0UL, 1UL); cpu[0] = new GpuUInt128(0UL, 1UL);
cpu[1] = new GpuUInt128(0UL, 2UL); cpu[1] = new GpuUInt128(0UL, 2UL);
cpu[2] = new GpuUInt128(0UL, 3UL); cpu[2] = new GpuUInt128(0UL, 3UL);
cpu[3] = new GpuUInt128(0UL, 4UL); cpu[3] = new GpuUInt128(0UL, 4UL);
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
cpu.CopyTo(gpu); cpu.CopyTo(gpu);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
NttGpuMath.Forward(cpu, modulus, primitiveRoot); NttGpuMath.Forward(cpu, modulus, primitiveRoot);
NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot); NttGpuMath.ForwardGpu(gpu, modulus, primitiveRoot);
for (int i = 0; i < cpu.Length; i++) for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void InverseGpu_reference_matches_cpu_inverse() public void InverseGpu_reference_matches_cpu_inverse()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Reference; NttGpuMath.GpuTransformBackend = NttBackend.Reference;
Span<GpuUInt128> input = stackalloc GpuUInt128[4]; Span<GpuUInt128> input = stackalloc GpuUInt128[4];
input[0] = new GpuUInt128(0UL, 5UL); input[0] = new GpuUInt128(0UL, 5UL);
input[1] = new GpuUInt128(0UL, 7UL); input[1] = new GpuUInt128(0UL, 7UL);
input[2] = new GpuUInt128(0UL, 9UL); input[2] = new GpuUInt128(0UL, 9UL);
input[3] = new GpuUInt128(0UL, 11UL); input[3] = new GpuUInt128(0UL, 11UL);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4]; Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
input.CopyTo(transformed); input.CopyTo(transformed);
NttGpuMath.Forward(transformed, modulus, primitiveRoot); NttGpuMath.Forward(transformed, modulus, primitiveRoot);
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
transformed.CopyTo(cpu); transformed.CopyTo(cpu);
NttGpuMath.Inverse(cpu, modulus, primitiveRoot); NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
transformed.CopyTo(gpu); transformed.CopyTo(gpu);
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot); NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
for (int i = 0; i < input.Length; i++) for (int i = 0; i < input.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void InverseGpu_staged_matches_cpu_inverse() public void InverseGpu_staged_matches_cpu_inverse()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Staged; NttGpuMath.GpuTransformBackend = NttBackend.Staged;
Span<GpuUInt128> input = stackalloc GpuUInt128[4]; Span<GpuUInt128> input = stackalloc GpuUInt128[4];
input[0] = new GpuUInt128(0UL, 5UL); input[0] = new GpuUInt128(0UL, 5UL);
input[1] = new GpuUInt128(0UL, 7UL); input[1] = new GpuUInt128(0UL, 7UL);
input[2] = new GpuUInt128(0UL, 9UL); input[2] = new GpuUInt128(0UL, 9UL);
input[3] = new GpuUInt128(0UL, 11UL); input[3] = new GpuUInt128(0UL, 11UL);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
Span<GpuUInt128> transformed = stackalloc GpuUInt128[4]; Span<GpuUInt128> transformed = stackalloc GpuUInt128[4];
input.CopyTo(transformed); input.CopyTo(transformed);
NttGpuMath.Forward(transformed, modulus, primitiveRoot); NttGpuMath.Forward(transformed, modulus, primitiveRoot);
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
transformed.CopyTo(cpu); transformed.CopyTo(cpu);
NttGpuMath.Inverse(cpu, modulus, primitiveRoot); NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
transformed.CopyTo(gpu); transformed.CopyTo(gpu);
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot); NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
for (int i = 0; i < input.Length; i++) for (int i = 0; i < input.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void Montgomery_twiddle_inverses_multiply_to_r2() public void Montgomery_twiddle_inverses_multiply_to_r2()
{ {
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
var accelerator = SharedGpuContext.CreateAccelerator(); GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
var stream = accelerator.CreateStream(); try
{
var accelerator = gpu.Accelerator;
var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static); var method = typeof(NttGpuMath).GetMethod("GetSquareCache", BindingFlags.NonPublic | BindingFlags.Static);
method.Should().NotBeNull(); method.Should().NotBeNull();
var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!; var cache = method!.Invoke(null, new object[] { accelerator, 8, modulus, primitiveRoot })!;
var cacheType = cache.GetType(); var cacheType = cache.GetType();
var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!; var twiddlesMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesMont")!.GetValue(cache)!;
var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!; var twiddlesInvMont = (MemoryBuffer1D<GpuUInt128, Stride1D.Dense>)cacheType.GetProperty("TwiddlesInvMont")!.GetValue(cache)!;
var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!; var r2 = (ulong)cacheType.GetProperty("MontR2Mod64")!.GetValue(cache)!;
var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!; var mod = (ulong)cacheType.GetProperty("ModulusLow")!.GetValue(cache)!;
int count = 7; // length 8 -> 7 twiddles int count = 7; // length 8 -> 7 twiddles
var forward = new GpuUInt128[count]; var forward = new GpuUInt128[count];
var inverse = new GpuUInt128[count]; var inverse = new GpuUInt128[count];
twiddlesMont.CopyToCPU(stream, forward); twiddlesMont.CopyToCPU(forward);
twiddlesInvMont.CopyToCPU(stream, inverse); twiddlesInvMont.CopyToCPU(inverse);
stream.Synchronize();
stream.Dispose();
accelerator.Dispose();
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod); ulong prod = (ulong)((((BigInteger)forward[i].Low) * inverse[i].Low) % mod);
prod.Should().Be(r2); prod.Should().Be(r2);
} }
} }
finally
{
gpu.Dispose();
}
}
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void ConvolveGpu_reference_matches_cpu_convolve() public void ConvolveGpu_reference_matches_cpu_convolve()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Reference; NttGpuMath.GpuTransformBackend = NttBackend.Reference;
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
leftCpu[0] = new GpuUInt128(0UL, 1UL); leftCpu[0] = new GpuUInt128(0UL, 1UL);
leftCpu[1] = new GpuUInt128(0UL, 2UL); leftCpu[1] = new GpuUInt128(0UL, 2UL);
leftCpu[2] = new GpuUInt128(0UL, 3UL); leftCpu[2] = new GpuUInt128(0UL, 3UL);
leftCpu[3] = new GpuUInt128(0UL, 4UL); leftCpu[3] = new GpuUInt128(0UL, 4UL);
rightCpu[0] = new GpuUInt128(0UL, 5UL); rightCpu[0] = new GpuUInt128(0UL, 5UL);
rightCpu[1] = new GpuUInt128(0UL, 6UL); rightCpu[1] = new GpuUInt128(0UL, 6UL);
rightCpu[2] = new GpuUInt128(0UL, 7UL); rightCpu[2] = new GpuUInt128(0UL, 7UL);
rightCpu[3] = new GpuUInt128(0UL, 8UL); rightCpu[3] = new GpuUInt128(0UL, 8UL);
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
leftCpu.CopyTo(leftGpu); leftCpu.CopyTo(leftGpu);
rightCpu.CopyTo(rightGpu); rightCpu.CopyTo(rightGpu);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot); NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot); NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
for (int i = 0; i < leftCpu.Length; i++) for (int i = 0; i < leftCpu.Length; i++)
{ {
leftGpu[i].Should().Be(leftCpu[i]); leftGpu[i].Should().Be(leftCpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void ConvolveGpu_staged_matches_cpu_convolve() public void ConvolveGpu_staged_matches_cpu_convolve()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Staged; NttGpuMath.GpuTransformBackend = NttBackend.Staged;
Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> leftCpu = stackalloc GpuUInt128[4];
Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> rightCpu = stackalloc GpuUInt128[4];
leftCpu[0] = new GpuUInt128(0UL, 1UL); leftCpu[0] = new GpuUInt128(0UL, 1UL);
leftCpu[1] = new GpuUInt128(0UL, 2UL); leftCpu[1] = new GpuUInt128(0UL, 2UL);
leftCpu[2] = new GpuUInt128(0UL, 3UL); leftCpu[2] = new GpuUInt128(0UL, 3UL);
leftCpu[3] = new GpuUInt128(0UL, 4UL); leftCpu[3] = new GpuUInt128(0UL, 4UL);
rightCpu[0] = new GpuUInt128(0UL, 5UL); rightCpu[0] = new GpuUInt128(0UL, 5UL);
rightCpu[1] = new GpuUInt128(0UL, 6UL); rightCpu[1] = new GpuUInt128(0UL, 6UL);
rightCpu[2] = new GpuUInt128(0UL, 7UL); rightCpu[2] = new GpuUInt128(0UL, 7UL);
rightCpu[3] = new GpuUInt128(0UL, 8UL); rightCpu[3] = new GpuUInt128(0UL, 8UL);
Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> leftGpu = stackalloc GpuUInt128[4];
Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> rightGpu = stackalloc GpuUInt128[4];
leftCpu.CopyTo(leftGpu); leftCpu.CopyTo(leftGpu);
rightCpu.CopyTo(rightGpu); rightCpu.CopyTo(rightGpu);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot); NttGpuMath.Convolve(leftCpu, rightCpu, modulus, primitiveRoot);
NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot); NttGpuMath.ConvolveGpu(leftGpu, rightGpu, modulus, primitiveRoot);
for (int i = 0; i < leftCpu.Length; i++) for (int i = 0; i < leftCpu.Length; i++)
{ {
leftGpu[i].Should().Be(leftCpu[i]); leftGpu[i].Should().Be(leftCpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void SquareGpu_reference_matches_cpu_square() public void SquareGpu_reference_matches_cpu_square()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Reference; NttGpuMath.GpuTransformBackend = NttBackend.Reference;
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
cpu[0] = new GpuUInt128(0UL, 1UL); cpu[0] = new GpuUInt128(0UL, 1UL);
cpu[1] = new GpuUInt128(0UL, 2UL); cpu[1] = new GpuUInt128(0UL, 2UL);
cpu[2] = new GpuUInt128(0UL, 3UL); cpu[2] = new GpuUInt128(0UL, 3UL);
cpu[3] = new GpuUInt128(0UL, 4UL); cpu[3] = new GpuUInt128(0UL, 4UL);
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
cpu.CopyTo(gpu); cpu.CopyTo(gpu);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
NttGpuMath.Square(cpu, modulus, primitiveRoot); NttGpuMath.Square(cpu, modulus, primitiveRoot);
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot); NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
for (int i = 0; i < cpu.Length; i++) for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void SquareGpu_staged_matches_cpu_square() public void SquareGpu_staged_matches_cpu_square()
{ {
var original = NttGpuMath.GpuTransformBackend; var original = NttGpuMath.GpuTransformBackend;
try try
{ {
NttGpuMath.GpuTransformBackend = NttBackend.Staged; NttGpuMath.GpuTransformBackend = NttBackend.Staged;
Span<GpuUInt128> cpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[4];
cpu[0] = new GpuUInt128(0UL, 1UL); cpu[0] = new GpuUInt128(0UL, 1UL);
cpu[1] = new GpuUInt128(0UL, 2UL); cpu[1] = new GpuUInt128(0UL, 2UL);
cpu[2] = new GpuUInt128(0UL, 3UL); cpu[2] = new GpuUInt128(0UL, 3UL);
cpu[3] = new GpuUInt128(0UL, 4UL); cpu[3] = new GpuUInt128(0UL, 4UL);
Span<GpuUInt128> gpu = stackalloc GpuUInt128[4]; Span<GpuUInt128> gpu = stackalloc GpuUInt128[4];
cpu.CopyTo(gpu); cpu.CopyTo(gpu);
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
NttGpuMath.Square(cpu, modulus, primitiveRoot); NttGpuMath.Square(cpu, modulus, primitiveRoot);
NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot); NttGpuMath.SquareGpu(gpu, modulus, primitiveRoot);
for (int i = 0; i < cpu.Length; i++) for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
} }
finally finally
{ {
NttGpuMath.GpuTransformBackend = original; NttGpuMath.GpuTransformBackend = original;
} }
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void BitReverseGpu_matches_cpu() public void BitReverseGpu_matches_cpu()
{ {
GpuUInt128[] cpu = new GpuUInt128[8]; GpuUInt128[] cpu = new GpuUInt128[8];
for (ulong i = 0; i < (ulong)cpu.Length; i++) for (ulong i = 0; i < (ulong)cpu.Length; i++)
{ {
cpu[i] = new GpuUInt128(0UL, i); cpu[i] = new GpuUInt128(0UL, i);
} }
GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone(); GpuUInt128[] gpu = (GpuUInt128[])cpu.Clone();
NttGpuMath.BitReverse(cpu); NttGpuMath.BitReverse(cpu);
NttGpuMath.BitReverseGpu(gpu); NttGpuMath.BitReverseGpu(gpu);
gpu.Should().Equal(cpu); gpu.Should().Equal(cpu);
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void DisposeAll_clears_ntt_caches() public void DisposeAll_clears_ntt_caches()
{ {
// Ensure the cache starts empty. // Ensure the cache starts empty.
NttGpuMath.ClearCaches(); NttGpuMath.ClearCaches();
Span<GpuUInt128> values = stackalloc GpuUInt128[2]; Span<GpuUInt128> values = stackalloc GpuUInt128[2];
values[0] = new GpuUInt128(0UL, 1UL); values[0] = new GpuUInt128(0UL, 1UL);
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);
Accelerator accelerator = SharedGpuContext.CreateAccelerator(); GpuContextPool.GpuContextLease gpu = GpuContextPool.Rent();
var stream = accelerator.CreateStream(); try
var buffer = accelerator.Allocate1D<GpuUInt128>(values.Length); {
buffer.View.CopyFromCPU(stream, ref values[0], values.Length); var buffer = gpu.Accelerator.Allocate1D<GpuUInt128>(values.Length);
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot); try
stream.Synchronize(); {
stream.Dispose(); buffer.View.CopyFromCPU(ref values[0], values.Length);
buffer.Dispose(); NttGpuMath.SquareDevice(gpu.Accelerator, buffer.View, modulus, primitiveRoot);
accelerator.Dispose(); }
finally
{
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)!;
var cacheObj = field.GetValue(null)!; var cacheObj = field.GetValue(null)!;
int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!; int count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
count.Should().BeGreaterThan(0); count.Should().BeGreaterThan(0);
count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!; GpuContextPool.DisposeAll();
count.Should().Be(0);
} count = (int)cacheObj.GetType().GetProperty("Count")!.GetValue(cacheObj)!;
count.Should().Be(0);
}
} }
@@ -9,83 +9,86 @@ namespace PerfectNumbers.Core.Tests.Gpu;
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public class NttMontgomeryTests public class NttMontgomeryTests
{ {
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
public void SquareDevice_montgomery64_matches_cpu_square_on_small_input() public void SquareDevice_montgomery64_matches_cpu_square_on_small_input()
{ {
// Small 64-bit modulus and tiny input to keep runtime trivial. // Small 64-bit modulus and tiny input to keep runtime trivial.
var modulus = new GpuUInt128(0UL, 17UL); var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL); var primitiveRoot = new GpuUInt128(0UL, 3UL);
Span<GpuUInt128> cpu = stackalloc GpuUInt128[8]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[8];
for (ulong i = 0; i < (ulong)cpu.Length; i++) for (ulong i = 0; i < (ulong)cpu.Length; i++)
{ {
cpu[(int)i] = new GpuUInt128(0UL, i + 1UL); cpu[(int)i] = new GpuUInt128(0UL, i + 1UL);
} }
Span<GpuUInt128> gpu = stackalloc GpuUInt128[16]; Span<GpuUInt128> gpu = stackalloc GpuUInt128[16];
cpu.CopyTo(gpu); cpu.CopyTo(gpu);
for (int i = cpu.Length; i < gpu.Length; i++) for (int i = cpu.Length; i < gpu.Length; i++)
{ {
gpu[i] = new GpuUInt128(0UL, 0UL); gpu[i] = new GpuUInt128(0UL, 0UL);
} }
// CPU reference // CPU reference
NttGpuMath.Square(cpu, modulus, primitiveRoot); NttGpuMath.Square(cpu, modulus, primitiveRoot);
// Device execution // Device execution
var accelerator = SharedGpuContext.CreateAccelerator(); GpuContextPool.GpuContextLease context = GpuContextPool.Rent();
var stream = accelerator.CreateStream(); try
using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length); {
buffer.View.CopyFromCPU(stream, ref gpu[0], gpu.Length); var accelerator = context.Accelerator;
NttGpuMath.SquareDevice(accelerator, stream, buffer.View, modulus, primitiveRoot); using var buffer = accelerator.Allocate1D<GpuUInt128>(gpu.Length);
buffer.View.CopyToCPU(stream, ref gpu[0], gpu.Length); buffer.View.CopyFromCPU(ref gpu[0], gpu.Length);
stream.Synchronize(); NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot);
stream.Dispose(); buffer.View.CopyToCPU(ref gpu[0], gpu.Length);
accelerator.Dispose();
for (int i = 0; i < cpu.Length; i++) for (int i = 0; i < cpu.Length; i++)
{ {
gpu[i].Should().Be(cpu[i]); gpu[i].Should().Be(cpu[i]);
} }
}
finally
{
context.Dispose();
}
}
} [Fact]
[Trait("Category", "Fast")]
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input()
{
var modulus = new GpuUInt128(0UL, 17UL);
var primitiveRoot = new GpuUInt128(0UL, 3UL);
[Fact] Span<GpuUInt128> input = stackalloc GpuUInt128[8];
[Trait("Category", "Fast")] for (ulong i = 0; i < (ulong)input.Length; i++)
public void InverseDevice_montgomery64_matches_cpu_inverse_on_small_input() {
{ input[(int)i] = new GpuUInt128(0UL, i + 1UL);
var modulus = new GpuUInt128(0UL, 17UL); }
var primitiveRoot = new GpuUInt128(0UL, 3UL);
Span<GpuUInt128> input = stackalloc GpuUInt128[8]; Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length];
for (ulong i = 0; i < (ulong)input.Length; i++) input.CopyTo(transformed);
{ NttGpuMath.Forward(transformed, modulus, primitiveRoot);
input[(int)i] = new GpuUInt128(0UL, i + 1UL);
}
Span<GpuUInt128> transformed = stackalloc GpuUInt128[input.Length]; Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length];
input.CopyTo(transformed); transformed.CopyTo(cpu);
NttGpuMath.Forward(transformed, modulus, primitiveRoot); NttGpuMath.Inverse(cpu, modulus, primitiveRoot);
Span<GpuUInt128> cpu = stackalloc GpuUInt128[input.Length]; var original = NttGpuMath.GpuTransformBackend;
transformed.CopyTo(cpu); try
NttGpuMath.Inverse(cpu, modulus, primitiveRoot); {
NttGpuMath.GpuTransformBackend = NttBackend.Staged;
var original = NttGpuMath.GpuTransformBackend; Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length];
try transformed.CopyTo(gpu);
{ NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot);
NttGpuMath.GpuTransformBackend = NttBackend.Staged; for (int i = 0; i < input.Length; i++)
Span<GpuUInt128> gpu = stackalloc GpuUInt128[input.Length]; {
transformed.CopyTo(gpu); gpu[i].Should().Be(cpu[i]);
NttGpuMath.InverseGpu(gpu, modulus, primitiveRoot); }
for (int i = 0; i < input.Length; i++) }
{ finally
gpu[i].Should().Be(cpu[i]); {
} NttGpuMath.GpuTransformBackend = original;
} }
finally }
{
NttGpuMath.GpuTransformBackend = original;
}
}
} }
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -9,23 +10,37 @@ public class PrimeTesterGpuTests
[Fact] [Fact]
public void IsPrimeGpu_accepts_known_primes() public void IsPrimeGpu_accepts_known_primes()
{ {
var tester = new PrimeTester(); try
ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL]; {
foreach (ulong prime in primes) var tester = new PrimeTester();
{ ulong[] primes = [31UL, 61UL, 89UL, 107UL, 127UL, 521UL];
PrimeTester.IsPrimeGpu(prime).Should().BeTrue(); foreach (ulong prime in primes)
} {
tester.IsPrimeGpu(prime).Should().BeTrue();
}
}
finally
{
GpuContextPool.DisposeAll();
}
} }
[Fact] [Fact]
public void IsPrimeGpu_rejects_composites() public void IsPrimeGpu_rejects_composites()
{ {
var tester = new PrimeTester(); try
ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL]; {
foreach (ulong composite in composites) var tester = new PrimeTester();
{ ulong[] composites = [33UL, 39UL, 51UL, 77UL, 91UL];
PrimeTester.IsPrimeGpu(composite).Should().BeFalse(); foreach (ulong composite in composites)
} {
tester.IsPrimeGpu(composite).Should().BeFalse();
}
}
finally
{
GpuContextPool.DisposeAll();
}
} }
} }
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using Xunit; using Xunit;
namespace PerfectNumbers.Core.Tests.Gpu; namespace PerfectNumbers.Core.Tests.Gpu;
@@ -1,3 +1,7 @@
using System;
using System.Numerics;
using System.Collections.Generic;
using PerfectNumbers.Core;
using FluentAssertions; using FluentAssertions;
using Xunit; using Xunit;
@@ -11,7 +11,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
{ {
var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL }; var candidates = new List<ulong> { 5UL, 7UL, 11UL, 13UL };
var tester = new FakeTester(); var tester = new FakeTester();
var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed, ulong Divisor)>(); var results = new List<(ulong Prime, bool Searched, bool Detailed, bool Passed)>();
int compositeMarks = 0; int compositeMarks = 0;
int clearedMarks = 0; int clearedMarks = 0;
@@ -22,7 +22,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
startPrime: 0UL, startPrime: 0UL,
markComposite: () => compositeMarks++, markComposite: () => compositeMarks++,
clearComposite: () => clearedMarks++, clearComposite: () => clearedMarks++,
printResult: (prime, searched, detailed, passed, divisor) => results.Add((prime, searched, detailed, passed, divisor)), printResult: (prime, searched, detailed, passed) => results.Add((prime, searched, detailed, passed)),
threadCount: 1, threadCount: 1,
primesPerTask: 2); primesPerTask: 2);
@@ -32,10 +32,10 @@ public class MersenneNumberDivisorByDivisorTesterTests
results.Should().Equal( results.Should().Equal(
new[] new[]
{ {
(5UL, true, true, true, 0UL), (5UL, true, true, true),
(7UL, true, true, false, 7UL), (7UL, true, true, false),
(11UL, true, true, false, 11UL), (11UL, true, true, false),
(13UL, true, true, true, 0UL), (13UL, true, true, true),
}); });
} }
@@ -57,23 +57,10 @@ public class MersenneNumberDivisorByDivisorTesterTests
return DivisorLimit; return DivisorLimit;
} }
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) public bool IsPrime(ulong prime, out bool divisorsExhausted)
{ {
divisorsExhausted = true; divisorsExhausted = true;
if (prime % 7UL == 0UL) return prime % 7UL != 0UL && prime % 11UL != 0UL;
{
divisor = 7UL;
return false;
}
if (prime % 11UL == 0UL)
{
divisor = 11UL;
return false;
}
divisor = 0UL;
return true;
} }
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
@@ -96,7 +83,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
throw new NotSupportedException(); throw new NotSupportedException();
} }
public void Return() public void Dispose()
{ {
} }
} }
@@ -13,17 +13,14 @@ public class MersenneNumberDivisorCpuTesterTests
var tester = new MersenneNumberDivisorByDivisorCpuTester(); var tester = new MersenneNumberDivisorByDivisorCpuTester();
tester.ConfigureFromMaxPrime(11UL); tester.ConfigureFromMaxPrime(11UL);
tester.IsPrime(5UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); tester.IsPrime(5UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(7UL, out divisorsExhausted, out divisor).Should().BeTrue(); tester.IsPrime(7UL, out divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(11UL, out divisorsExhausted, out divisor).Should().BeFalse(); tester.IsPrime(11UL, out divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -50,7 +47,7 @@ public class MersenneNumberDivisorCpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
@@ -75,7 +72,7 @@ public class MersenneNumberDivisorCpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
@@ -100,7 +97,7 @@ public class MersenneNumberDivisorCpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
} }
@@ -73,17 +73,14 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(31UL, out bool exhausted, out ulong divisor).Should().BeTrue(); tester.IsPrime(31UL, out bool exhausted).Should().BeTrue();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().Be(0UL);
tester.IsPrime(37UL, out exhausted, out divisor).Should().BeFalse(); tester.IsPrime(37UL, out exhausted).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
tester.IsPrime(43UL, out exhausted, out divisor).Should().BeFalse(); tester.IsPrime(43UL, out exhausted).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -108,15 +105,12 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(41UL); tester.ConfigureFromMaxPrime(41UL);
tester.IsPrime(31UL, out _, out ulong divisor).Should().BeTrue(); tester.IsPrime(31UL, out _).Should().BeTrue();
divisor.Should().Be(0UL); tester.IsPrime(37UL, out bool exhausted).Should().BeFalse();
tester.IsPrime(37UL, out bool exhausted, out divisor).Should().BeFalse();
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
tester.IsPrime(41UL, out bool divisorsExhausted, out divisor).Should().BeFalse(); tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
@@ -130,16 +124,8 @@ public class MersenneNumberDivisorGpuTesterTests
var tester = new MersenneNumberDivisorByDivisorGpuTester(); var tester = new MersenneNumberDivisorByDivisorGpuTester();
tester.ConfigureFromMaxPrime(exponent); tester.ConfigureFromMaxPrime(exponent);
tester.IsPrime(exponent, out bool exhausted, out ulong divisor).Should().Be(expectedPrime); tester.IsPrime(exponent, out bool exhausted).Should().Be(expectedPrime);
exhausted.Should().BeTrue(); exhausted.Should().BeTrue();
if (expectedPrime)
{
divisor.Should().Be(0UL);
}
else
{
divisor.Should().BeGreaterThan(0UL);
}
} }
[Fact] [Fact]
[Trait("Category", "Fast")] [Trait("Category", "Fast")]
@@ -152,9 +138,8 @@ public class MersenneNumberDivisorGpuTesterTests
tester.ConfigureFromMaxPrime(43UL); tester.ConfigureFromMaxPrime(43UL);
tester.IsPrime(41UL, out bool divisorsExhausted, out ulong divisor).Should().BeFalse(); tester.IsPrime(41UL, out bool divisorsExhausted).Should().BeFalse();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().BeGreaterThan(0UL);
} }
[Fact] [Fact]
@@ -176,7 +161,7 @@ public class MersenneNumberDivisorGpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
@@ -204,7 +189,7 @@ public class MersenneNumberDivisorGpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
@@ -232,7 +217,7 @@ public class MersenneNumberDivisorGpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
@@ -261,7 +246,7 @@ public class MersenneNumberDivisorGpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
@@ -290,7 +275,7 @@ public class MersenneNumberDivisorGpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
@@ -324,15 +309,15 @@ public class MersenneNumberDivisorGpuTesterTests
.GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)! .GetField("_divisorLimit", BindingFlags.NonPublic | BindingFlags.Instance)!
.SetValue(tester, 200UL); .SetValue(tester, 200UL);
tester.IsPrime(19UL, out bool divisorsExhausted, out ulong divisor).Should().BeTrue(); tester.IsPrime(19UL, out bool divisorsExhausted).Should().BeTrue();
divisorsExhausted.Should().BeTrue(); divisorsExhausted.Should().BeTrue();
divisor.Should().Be(0UL);
} }
finally finally
{ {
tableField.SetValue(cycles, originalTable); tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, originalSmall); smallCyclesField.SetValue(cycles, originalSmall);
DivisorCycleCache.Shared.RefreshSnapshot(); DivisorCycleCache.Shared.RefreshSnapshot();
GpuContextPool.DisposeAll();
} }
} }
@@ -360,7 +345,7 @@ public class MersenneNumberDivisorGpuTesterTests
} }
finally finally
{ {
session.Return(); session.Dispose();
} }
} }
} }
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,5 +1,6 @@
using System.Reflection; using System.Reflection;
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -54,6 +55,7 @@ public class MersenneNumberOrderGpuTesterTests
ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone(); ulong[]? backupSmall = originalSmall is null ? null : (ulong[])originalSmall.Clone();
cycles.LoadFrom(tempPath); cycles.LoadFrom(tempPath);
GpuContextPool.DisposeAll();
try try
{ {
@@ -73,6 +75,7 @@ public class MersenneNumberOrderGpuTesterTests
{ {
tableField.SetValue(cycles, originalTable); tableField.SetValue(cycles, originalTable);
smallCyclesField.SetValue(cycles, backupSmall); smallCyclesField.SetValue(cycles, backupSmall);
GpuContextPool.DisposeAll();
} }
} }
finally finally
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Cpu; using PerfectNumbers.Core.Cpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -1,4 +1,5 @@
using FluentAssertions; using FluentAssertions;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using Xunit; using Xunit;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
@@ -39,7 +39,7 @@ public class PrimeTesterIsPrimeTests
{ {
var tester = new PrimeTester(); var tester = new PrimeTester();
PrimeTester.IsPrimeGpu(n).Should().Be(expected); tester.IsPrimeGpu(n).Should().Be(expected);
} }
[Fact] [Fact]
@@ -64,7 +64,7 @@ public class PrimeTesterIsPrimeTests
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true); var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
Parallel.For(0, primes.Length, i => Parallel.For(0, primes.Length, i =>
{ {
results[i] = PrimeTester.IsPrimeGpu(primes[i]); results[i] = testers.Value!.IsPrimeGpu(primes[i]);
}); });
results.Should().AllBeEquivalentTo(true); results.Should().AllBeEquivalentTo(true);
@@ -73,7 +73,7 @@ public class PrimeTesterIsPrimeTests
{ {
foreach (ulong prime in primes) foreach (ulong prime in primes)
{ {
PrimeTester.IsPrimeGpu(prime).Should().BeTrue(); testers.Value!.IsPrimeGpu(prime).Should().BeTrue();
} }
}); });
} }
@@ -62,7 +62,7 @@ internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDi
} }
} }
public void Return() public void Dispose()
{ {
ThreadStaticPools.ReturnMersenneCpuDivisorSession(this); ThreadStaticPools.ReturnMersenneCpuDivisorSession(this);
} }
@@ -1,510 +1,487 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using PerfectNumbers.Core;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{ {
private ulong _divisorLimit; private ulong _divisorLimit;
private int _batchSize = 1_024; private int _batchSize = 1_024;
[ThreadStatic] [ThreadStatic]
private static GpuUInt128WorkSet _divisorScanGpuWorkSet; private static GpuUInt128WorkSet _divisorScanGpuWorkSet;
private struct GpuUInt128WorkSet private struct GpuUInt128WorkSet
{ {
public GpuUInt128 Step; public GpuUInt128 Step;
public GpuUInt128 Divisor; public GpuUInt128 Divisor;
public GpuUInt128 Limit; public GpuUInt128 Limit;
} }
public int BatchSize public int BatchSize
{ {
get => _batchSize; get => _batchSize;
set => _batchSize = Math.Max(1, value); set => _batchSize = Math.Max(1, value);
} }
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
{ {
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime); _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
} }
public ulong DivisorLimit public ulong DivisorLimit
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
return _divisorLimit; return _divisorLimit;
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit); public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
public bool IsPrime(ulong prime, out bool divisorsExhausted, out ulong divisor) public bool IsPrime(ulong prime, out bool divisorsExhausted)
{ {
ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit); ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
// The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes. // The CPU by-divisor run always hands us primes with enormous divisor limits, so the fallback below never executes.
// if (allowedMax < 3UL) // if (allowedMax < 3UL)
// { // {
// // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees // // EvenPerfectBitScanner routes primes below the small-divisor cutoff to the GPU path, so the CPU path still sees
// // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow. // // trivial candidates during targeted tests. Short-circuit here to keep those runs aligned with the production flow.
// divisorsExhausted = true; // divisorsExhausted = true;
// return true; // return true;
// } // }
bool processedAll; bool processedAll;
bool composite = CheckDivisors( bool composite = CheckDivisors(
prime, prime,
allowedMax, allowedMax,
out processedAll, out processedAll);
out divisor);
if (composite)
if (composite) {
{ divisorsExhausted = true;
divisorsExhausted = true; return false;
return false; }
}
divisorsExhausted = processedAll || composite;
divisorsExhausted = processedAll || composite; return true;
divisor = 0UL; }
return true;
} public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{
public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues) ulong divisorLimit = _divisorLimit;
{ int length = primes.Length;
ulong divisorLimit = _divisorLimit; for (int index = 0; index < length; index++)
int length = primes.Length; {
for (int index = 0; index < length; index++) allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
{ }
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit); }
}
} public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
{
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession();
{ if (session is not null)
MersenneCpuDivisorScanSession? session = ThreadStaticPools.RentMersenneCpuDivisorSession(); {
if (session is not null) return session;
{ }
return session;
} return new MersenneCpuDivisorScanSession();
}
return new MersenneCpuDivisorScanSession();
} private bool CheckDivisors(
ulong prime,
private bool CheckDivisors( ulong allowedMax,
ulong prime, out bool processedAll)
ulong allowedMax, {
out bool processedAll, processedAll = false;
out ulong foundDivisor)
{ // The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
foundDivisor = 0UL; // Keeping the guard commented out documents the reasoning for benchmarks and tests.
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // if (allowedMax < 3UL)
// Keeping the guard commented out documents the reasoning for benchmarks and tests. // {
// if (allowedMax < 3UL) // return false;
// { // }
// return false;
// } ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet;
ref GpuUInt128WorkSet workSet = ref _divisorScanGpuWorkSet; ref GpuUInt128 step = ref workSet.Step;
step.High = 0UL;
ref GpuUInt128 step = ref workSet.Step; step.Low = prime;
step.High = 0UL; step.ShiftLeft(1);
step.Low = prime;
step.ShiftLeft(1); ref GpuUInt128 limit = ref workSet.Limit;
limit.High = 0UL;
ref GpuUInt128 limit = ref workSet.Limit; limit.Low = allowedMax;
limit.High = 0UL;
limit.Low = allowedMax; ref GpuUInt128 divisor = ref workSet.Divisor;
divisor.High = step.High;
ref GpuUInt128 divisor = ref workSet.Divisor; divisor.Low = step.Low;
divisor.High = step.High; divisor.Add(1UL);
divisor.Low = step.Low; if (divisor.CompareTo(limit) > 0)
divisor.Add(1UL); {
if (divisor.CompareTo(limit) > 0) processedAll = true;
{ return false;
processedAll = true; }
foundDivisor = 0UL;
return false; // Intentionally recomputes factorizations without a per-thread cache.
} // The previous factor cache recorded virtually no hits and only slowed down the scan.
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
// Intentionally recomputes factorizations without a per-thread cache.
// The previous factor cache recorded virtually no hits and only slowed down the scan. ulong stepHigh = step.High;
_ = DivisorCycleCache.Shared; ulong stepLow = step.Low;
ulong limitHigh = limit.High;
ulong stepHigh = step.High; ulong divisorLow = divisor.Low;
ulong stepLow = step.Low;
ulong limitHigh = limit.High; // Keep the divisibility filters aligned with the divisor-cycle generator so the
ulong divisorLow = divisor.Low; // CPU path never requests cycles that were skipped during cache creation.
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One;
// Keep the divisibility filters aligned with the divisor-cycle generator so the ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit);
// CPU path never requests cycles that were skipped during cache creation.
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; byte step10;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit); byte step8;
byte step3;
byte step10; byte step7;
byte step8; byte step11;
byte step3;
byte step7; byte remainder10;
byte step11; byte remainder8;
byte remainder3;
byte remainder10; byte remainder7;
byte remainder8; byte remainder11;
byte remainder3;
byte remainder7; if (stepHigh == 0UL && limitHigh == 0UL)
byte remainder11; {
step10 = (byte)(stepLow % 10UL);
if (stepHigh == 0UL && limitHigh == 0UL) step8 = (byte)(stepLow & 7UL);
{ step3 = (byte)(stepLow % 3UL);
step10 = (byte)(stepLow % 10UL); step7 = (byte)(stepLow % 7UL);
step8 = (byte)(stepLow & 7UL); step11 = (byte)(stepLow % 11UL);
step3 = (byte)(stepLow % 3UL);
step7 = (byte)(stepLow % 7UL); remainder10 = (byte)(divisorLow % 10UL);
step11 = (byte)(stepLow % 11UL); remainder8 = (byte)(divisorLow & 7UL);
remainder3 = (byte)(divisorLow % 3UL);
remainder10 = (byte)(divisorLow % 10UL); remainder7 = (byte)(divisorLow % 7UL);
remainder8 = (byte)(divisorLow & 7UL); remainder11 = (byte)(divisorLow % 11UL);
remainder3 = (byte)(divisorLow % 3UL);
remainder7 = (byte)(divisorLow % 7UL); return CheckDivisors64(
remainder11 = (byte)(divisorLow % 11UL); prime,
stepLow,
return CheckDivisors64( limit.Low,
prime, divisorLow,
stepLow, decimalMask,
limit.Low, step10,
divisorLow, step8,
decimalMask, step3,
step10, step7,
step8, step11,
step3, remainder10,
step7, remainder8,
step11, remainder3,
remainder10, remainder7,
remainder8, remainder11,
remainder3, out processedAll);
remainder7, }
remainder11,
out processedAll, ulong divisorHigh = divisor.High;
out foundDivisor); step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL);
} step8 = (byte)(stepLow % 8UL);
step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL);
ulong divisorHigh = divisor.High; step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL);
step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL); step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL);
step8 = (byte)(stepLow % 8UL);
step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL); remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL);
step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL); remainder8 = (byte)(divisorLow % 8UL);
step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL); remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL);
remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL);
remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL); remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL);
remainder8 = (byte)(divisorLow % 8UL);
remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL); while (divisor.CompareTo(limit) <= 0)
remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL); {
remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL); bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0;
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); {
ulong candidate = divisor.Low;
while (divisor.CompareTo(limit) <= 0) MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate);
{ ulong divisorCycle;
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0; // Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs.
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0) // if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
{ // {
ulong candidate = divisor.Low; // divisorCycle = cycleCache.GetCycleLength(candidate);
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(candidate); // }
ulong divisorCycle; // else
// Divisors generated from 2 * k * p + 1 exceed the small-cycle snapshot when p >= 138,000,000, so the short path below never runs. {
// if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles) if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
// { candidate,
// divisorCycle = cycleCache.GetCycleLength(candidate); prime,
// } divisorData,
// else out ulong computedCycle,
{ out bool primeOrderFailed) || computedCycle == 0UL)
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu( {
gpu, // Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
candidate, // for the exponents scanned here, so skip the unused cache fallback and compute directly.
prime, divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
divisorData, candidate,
out ulong computedCycle, divisorData,
out bool primeOrderFailed) || computedCycle == 0UL) skipPrimeOrderHeuristic: primeOrderFailed);
{ }
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles else
// for the exponents scanned here, so skip the unused cache fallback and compute directly. {
divisorCycle = MersenneDivisorCycles.CalculateCycleLength( divisorCycle = computedCycle;
candidate, }
divisorData, }
skipPrimeOrderHeuristic: primeOrderFailed);
} if (divisorCycle == prime)
else {
{ // A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides
divisorCycle = computedCycle; // the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p.
} processedAll = true;
} return true;
}
if (divisorCycle == prime)
{ if (divisorCycle == 0UL)
// A cycle equal to the tested exponent (which is prime in this path) guarantees that the candidate divides {
// the corresponding Mersenne number because the order of 2 modulo the divisor is exactly p. Console.WriteLine($"Divisor cycle was not calculated for {prime}");
foundDivisor = candidate; }
processedAll = true; }
PrimeOrderCalculatorAccelerator.Return(gpu);
return true; divisor.Add(step);
} remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8);
if (divisorCycle == 0UL) remainder3 = AddMod3(remainder3, step3);
{ remainder7 = AddMod7(remainder7, step7);
Console.WriteLine($"Divisor cycle was not calculated for {prime}"); remainder11 = AddMod11(remainder11, step11);
} }
} processedAll = true;
return false;
divisor.Add(step); }
remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8); private static bool CheckDivisors64(
remainder3 = AddMod3(remainder3, step3); ulong prime,
remainder7 = AddMod7(remainder7, step7); ulong step,
remainder11 = AddMod11(remainder11, step11); ulong limit,
} ulong divisor,
ushort decimalMask,
PrimeOrderCalculatorAccelerator.Return(gpu); byte step10,
processedAll = true; byte step8,
foundDivisor = 0UL; byte step3,
return false; byte step7,
} byte step11,
byte remainder10,
private static bool CheckDivisors64( byte remainder8,
ulong prime, byte remainder3,
ulong step, byte remainder7,
ulong limit, byte remainder11,
ulong divisor, out bool processedAll)
ushort decimalMask, {
byte step10, processedAll = false;
byte step8,
byte step3, bool canAdvance = step <= limit;
byte step7, if (!canAdvance)
byte step11, {
byte remainder10, if (divisor <= limit)
byte remainder8, {
byte remainder3, if (remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
byte remainder7, {
byte remainder11, MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
out bool processedAll, ulong divisorCycle;
out ulong foundDivisor) if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
{ divisor,
processedAll = false; prime,
foundDivisor = 0UL; divisorData,
out ulong computedCycle,
bool canAdvance = step <= limit; out bool primeOrderFailed) || computedCycle == 0UL)
var gpu = PrimeOrderCalculatorAccelerator.Rent(1); {
if (!canAdvance) divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
{ divisor,
if (divisor <= limit) divisorData,
{ skipPrimeOrderHeuristic: primeOrderFailed);
if (remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0) }
{ else
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); {
ulong divisorCycle; divisorCycle = computedCycle;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu( }
gpu,
divisor, if (divisorCycle == prime)
prime, {
divisorData, processedAll = true;
out ulong computedCycle, return true;
out bool primeOrderFailed) || computedCycle == 0UL) }
{
divisorCycle = MersenneDivisorCycles.CalculateCycleLength( if (divisorCycle == 0UL)
divisor, {
divisorData, Console.WriteLine($"Divisor cycle was not calculated for {prime}");
skipPrimeOrderHeuristic: primeOrderFailed); }
} }
else }
{
divisorCycle = computedCycle; processedAll = true;
} return false;
}
if (divisorCycle == prime)
{ if (divisor > limit)
foundDivisor = divisor; {
processedAll = true; processedAll = true;
PrimeOrderCalculatorAccelerator.Return(gpu); return false;
return true; }
}
ulong remainingIterations = ((limit - divisor) / step) + 1UL;
if (divisorCycle == 0UL)
{ while (true)
Console.WriteLine($"Divisor cycle was not calculated for {prime}"); {
} bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0;
} if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
} {
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
processedAll = true; ulong divisorCycle;
foundDivisor = 0UL; if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu(
return false; divisor,
} prime,
divisorData,
if (divisor > limit) out ulong computedCycle,
{ out bool primeOrderFailed) || computedCycle == 0UL)
processedAll = true; {
foundDivisor = 0UL; divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
return false; divisor,
} divisorData,
skipPrimeOrderHeuristic: primeOrderFailed);
ulong remainingIterations = ((limit - divisor) / step) + 1UL; }
else
while (true) {
{ divisorCycle = computedCycle;
bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0; }
if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0)
{ if (divisorCycle == prime)
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); {
ulong divisorCycle; processedAll = true;
if (!MersenneDivisorCycles.TryCalculateCycleLengthForExponentCpu( return true;
gpu, }
divisor,
prime, if (divisorCycle == 0UL)
divisorData, {
out ulong computedCycle, Console.WriteLine($"Divisor cycle was not calculated for {prime}");
out bool primeOrderFailed) || computedCycle == 0UL) }
{ }
divisorCycle = MersenneDivisorCycles.CalculateCycleLength(
divisor, remainingIterations--;
divisorData, if (remainingIterations == 0UL)
skipPrimeOrderHeuristic: primeOrderFailed); {
} processedAll = true;
else return false;
{ }
divisorCycle = computedCycle;
} divisor += step;
remainder10 = AddMod10(remainder10, step10);
if (divisorCycle == prime) remainder8 = AddMod8(remainder8, step8);
{ remainder3 = AddMod3(remainder3, step3);
foundDivisor = divisor; remainder7 = AddMod7(remainder7, step7);
processedAll = true; remainder11 = AddMod11(remainder11, step11);
PrimeOrderCalculatorAccelerator.Return(gpu); }
return true; }
}
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData)
if (divisorCycle == 0UL) {
{ ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
Console.WriteLine($"Divisor cycle was not calculated for {prime}"); return residue == 1UL ? (byte)1 : (byte)0;
} }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
remainingIterations--; private static byte AddMod3(byte value, byte delta)
if (remainingIterations == 0UL) {
{ const int Modulus = 3;
processedAll = true; int sum = value + delta;
foundDivisor = 0UL;
return false; if (sum >= Modulus)
} {
sum -= Modulus;
divisor += step; }
remainder10 = AddMod10(remainder10, step10);
remainder8 = AddMod8(remainder8, step8); return (byte)sum;
remainder3 = AddMod3(remainder3, step3); }
remainder7 = AddMod7(remainder7, step7);
remainder11 = AddMod11(remainder11, step11);
} [MethodImpl(MethodImplOptions.AggressiveInlining)]
} private static byte AddMod7(byte value, byte delta)
{
private static byte CheckDivisor(ulong prime, ulong divisorCycle, in MontgomeryDivisorData divisorData) const int Modulus = 7;
{ int sum = value + delta;
ulong residue = prime.Pow2MontgomeryModWithCycleCpu(divisorCycle, divisorData);
return residue == 1UL ? (byte)1 : (byte)0; if (sum >= Modulus)
} {
sum -= Modulus;
[MethodImpl(MethodImplOptions.AggressiveInlining)] }
private static byte AddMod3(byte value, byte delta)
{ return (byte)sum;
const int Modulus = 3; }
int sum = value + delta;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
if (sum >= Modulus) private static byte AddMod10(byte value, byte delta)
{ {
sum -= Modulus; const int Modulus = 10;
} int sum = value + delta;
return (byte)sum; if (sum >= Modulus)
} {
sum -= Modulus;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod7(byte value, byte delta) return (byte)sum;
{ }
const int Modulus = 7;
int sum = value + delta; [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod11(byte value, byte delta)
if (sum >= Modulus) {
{ const int Modulus = 11;
sum -= Modulus; int sum = value + delta;
}
if (sum >= Modulus)
return (byte)sum; {
} sum -= Modulus;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod10(byte value, byte delta) return (byte)sum;
{ }
const int Modulus = 10;
int sum = value + delta; [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static byte AddMod8(byte value, byte delta)
if (sum >= Modulus) {
{ return (byte)((value + delta) & 7);
sum -= Modulus; }
}
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime)
return (byte)sum; {
} // The by-divisor CPU configuration only supplies primes greater than 1, so the guard below never trips.
// if (maxPrime <= 1UL)
[MethodImpl(MethodImplOptions.AggressiveInlining)] // {
private static byte AddMod11(byte value, byte delta) // return 0UL;
{ // }
const int Modulus = 11; if (maxPrime - 1UL >= 64UL)
int sum = value + delta; {
return ulong.MaxValue;
if (sum >= Modulus) }
{
sum -= Modulus; return (1UL << (int)(maxPrime - 1UL)) - 1UL;
} }
return (byte)sum; private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
} {
// Production by-divisor scans only handle primes, so inputs never fall below 2.
[MethodImpl(MethodImplOptions.AggressiveInlining)] // if (prime <= 1UL)
private static byte AddMod8(byte value, byte delta) // {
{ // return 0UL;
return (byte)((value + delta) & 7); // }
} if (prime - 1UL >= 64UL)
{
private static ulong ComputeDivisorLimitFromMaxPrime(ulong maxPrime) return divisorLimit;
{ }
// The by-divisor CPU configuration only supplies primes greater than 1, so the guard below never trips.
// if (maxPrime <= 1UL) return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
// { }
// return 0UL;
// }
if (maxPrime - 1UL >= 64UL)
{
return ulong.MaxValue;
}
return (1UL << (int)(maxPrime - 1UL)) - 1UL;
}
private static ulong ComputeAllowedMaxDivisor(ulong prime, ulong divisorLimit)
{
// Production by-divisor scans only handle primes, so inputs never fall below 2.
// if (prime <= 1UL)
// {
// return 0UL;
// }
if (prime - 1UL >= 64UL)
{
return divisorLimit;
}
return Math.Min((1UL << (int)(prime - 1UL)) - 1UL, divisorLimit);
}
} }
@@ -1,6 +1,6 @@
namespace PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core;
internal static class DeterministicRandomCpu internal static class DeterministicRandom
{ {
[ThreadStatic] [ThreadStatic]
private static ulong s_threadState; private static ulong s_threadState;
+267 -315
View File
@@ -1,377 +1,329 @@
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu; using PerfectNumbers.Core.Gpu;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
public sealed class DivisorCycleCache public sealed class DivisorCycleCache
{ {
private const byte ByteZero = 0; private const byte ByteZero = 0;
private const byte ByteOne = 1; private const byte ByteOne = 1;
private const int StackBufferThreshold = 128; private const int StackBufferThreshold = 128;
private readonly Accelerator _accelerator;
private readonly int _acceleratorIndex;
private readonly Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> _gpuKernel;
private volatile ulong[] _snapshot;
private volatile bool _useGpuGeneration = true;
private readonly int _divisorCyclesBatchSize;
private static int _sharedDivisorCyclesBatchSize = GpuConstants.GpuCycleStepsPerInvocation;
private const int CycleCacheTrackingLimit = 500_000; private readonly ConcurrentDictionary<Accelerator, Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>> _gpuKernelCache = new(AcceleratorReferenceComparer.Instance);
private static readonly HashSet<ulong> CycleCacheTrackedDivisors = new(); private volatile ulong[] _snapshot;
private static readonly object CycleCacheTrackingLock = new(); private volatile bool _useGpuGeneration = true;
private static ulong CycleCacheHitCount; private readonly int _divisorCyclesBatchSize;
private static bool CycleCacheTrackingDisabled; private static int _sharedDivisorCyclesBatchSize = GpuConstants.GpuCycleStepsPerInvocation;
public static void SetDivisorCyclesBatchSize(int divisorCyclesBatchSize) private const int CycleCacheTrackingLimit = 500_000;
{ private static readonly HashSet<ulong> CycleCacheTrackedDivisors = new();
_sharedDivisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize); private static readonly object CycleCacheTrackingLock = new();
} private static ulong CycleCacheHitCount;
private static bool CycleCacheTrackingDisabled;
public static DivisorCycleCache Shared { get; } = new DivisorCycleCache(_sharedDivisorCyclesBatchSize); public static void SetDivisorCyclesBatchSize(int divisorCyclesBatchSize)
{
_sharedDivisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
}
public int PreferredBatchSize => _divisorCyclesBatchSize; public static DivisorCycleCache Shared { get; } = new DivisorCycleCache(_sharedDivisorCyclesBatchSize);
// private static readonly Accelerator[] _accelerators; public int PreferredBatchSize => _divisorCyclesBatchSize;
private DivisorCycleCache(int divisorCyclesBatchSize) private DivisorCycleCache(int divisorCyclesBatchSize)
{ {
_divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize); _divisorCyclesBatchSize = Math.Max(1, divisorCyclesBatchSize);
var acceleratorIndex = AcceleratorPool.Shared.Rent(); _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
_acceleratorIndex = acceleratorIndex; }
Accelerator accelerator = AcceleratorPool.Shared.Accelerators[acceleratorIndex];
_accelerator = accelerator;
_gpuKernel = LoadKernel(accelerator);
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
}
public void ConfigureGeneratorDevice(bool useGpu) public void ConfigureGeneratorDevice(bool useGpu)
{ {
_useGpuGeneration = useGpu; _useGpuGeneration = useGpu;
} }
public void RefreshSnapshot() public void RefreshSnapshot()
{ {
_snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); _snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
} }
public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false) public ulong GetCycleLength(ulong divisor, bool skipPrimeOrderHeuristic = false)
{ {
if (!skipPrimeOrderHeuristic) if (!skipPrimeOrderHeuristic)
{ {
Span<ulong> result = stackalloc ulong[1]; Span<ulong> result = stackalloc ulong[1];
ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor }; ReadOnlySpan<ulong> singleDivisor = stackalloc ulong[1] { divisor };
GetCycleLengths(singleDivisor, result); GetCycleLengths(singleDivisor, result);
return result[0]; return result[0];
} }
// The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner, // The skipPrimeOrderHeuristic path is only used for divisors produced by the CPU by-divisor scanner,
// which always generates values greater than one. Keep this guard disabled here to avoid repeating the // which always generates values greater than one. Keep this guard disabled here to avoid repeating the
// validation already performed by the general GetCycleLengths path. // validation already performed by the general GetCycleLengths path.
// if (divisor <= 1UL) // if (divisor <= 1UL)
// { // {
// throw new InvalidDataException("Divisor must be > 1"); // throw new InvalidDataException("Divisor must be > 1");
// } // }
ulong[] snapshot = _snapshot; ulong[] snapshot = _snapshot;
if (divisor < (ulong)snapshot.Length) if (divisor < (ulong)snapshot.Length)
{ {
ulong cached = snapshot[divisor]; ulong cached = snapshot[divisor];
if (cached == 0UL) if (cached == 0UL)
{ {
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
return cached; // ObserveCycleCacheDivisor(divisor);
} return cached;
}
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true); return MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData, skipPrimeOrderHeuristic: true);
} }
public void GetCycleLengths(ReadOnlySpan<ulong> divisors, Span<ulong> cycles) public void GetCycleLengths(ReadOnlySpan<ulong> divisors, Span<ulong> cycles)
{ {
ulong[] snapshot = _snapshot; ulong[] snapshot = _snapshot;
int length = divisors.Length; int length = divisors.Length;
int[]? rentedMissing = null; int[]? rentedMissing = null;
Span<int> missingBuffer = length <= StackBufferThreshold Span<int> missingBuffer = length <= StackBufferThreshold
? stackalloc int[length] ? stackalloc int[length]
: new Span<int>(rentedMissing = ThreadStaticPools.IntPool.Rent(length), 0, length); : new Span<int>(rentedMissing = ArrayPool<int>.Shared.Rent(length), 0, length);
int missingCount = 0; int missingCount = 0;
for (int i = 0; i < length; i++) for (int i = 0; i < length; i++)
{ {
ulong divisor = divisors[i]; ulong divisor = divisors[i];
if (divisor <= 1UL) if (divisor <= 1UL)
{ {
throw new InvalidDataException("Divisor must be > 1"); throw new InvalidDataException("Divisor must be > 1");
} }
if (divisor < (ulong)snapshot.Length) if (divisor < (ulong)snapshot.Length)
{ {
ulong cached = snapshot[divisor]; ulong cached = snapshot[divisor];
if (cached == 0UL) if (cached == 0UL)
{ {
throw new InvalidDataException($"Divisor cycle is missing for {divisor}"); throw new InvalidDataException($"Divisor cycle is missing for {divisor}");
} }
cycles[i] = cached; // ObserveCycleCacheDivisor(divisor);
} cycles[i] = cached;
else }
{ else
missingBuffer[missingCount++] = i; {
} missingBuffer[missingCount++] = i;
} }
}
if (missingCount != 0) if (missingCount != 0)
{ {
ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount]; ReadOnlySpan<int> missingIndices = missingBuffer[..missingCount];
if (_useGpuGeneration) if (_useGpuGeneration)
{ {
ComputeCyclesGpu(divisors, cycles, missingIndices); ComputeCyclesGpu(divisors, cycles, missingIndices);
} }
else else
{ {
ComputeCyclesCpu(divisors, cycles, missingIndices); ComputeCyclesCpu(divisors, cycles, missingIndices);
} }
} }
if (rentedMissing is not null) if (rentedMissing is not null)
{ {
ThreadStaticPools.IntPool.Return(rentedMissing, clearArray: false); ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
} }
} }
private static void ObserveCycleCacheDivisor(ulong divisor) private static void ObserveCycleCacheDivisor(ulong divisor)
{ {
bool logHit = false; bool logHit = false;
ulong hitNumber = 0UL; ulong hitNumber = 0UL;
bool trackingDisabledNow = false; bool trackingDisabledNow = false;
lock (CycleCacheTrackingLock) lock (CycleCacheTrackingLock)
{ {
if (CycleCacheTrackingDisabled) if (CycleCacheTrackingDisabled)
{ {
return; return;
} }
if (!CycleCacheTrackedDivisors.Add(divisor)) if (!CycleCacheTrackedDivisors.Add(divisor))
{ {
CycleCacheHitCount++; CycleCacheHitCount++;
hitNumber = CycleCacheHitCount; hitNumber = CycleCacheHitCount;
logHit = true; logHit = true;
} }
else if (CycleCacheTrackedDivisors.Count >= CycleCacheTrackingLimit) else if (CycleCacheTrackedDivisors.Count >= CycleCacheTrackingLimit)
{ {
CycleCacheTrackingDisabled = true; CycleCacheTrackingDisabled = true;
CycleCacheTrackedDivisors.Clear(); CycleCacheTrackedDivisors.Clear();
trackingDisabledNow = true; trackingDisabledNow = true;
} }
} }
if (logHit) if (logHit)
{ {
Console.WriteLine($"Cycle cache hit for divisor {divisor} ({hitNumber})"); Console.WriteLine($"Cycle cache hit for divisor {divisor} ({hitNumber})");
} }
else if (trackingDisabledNow) else if (trackingDisabledNow)
{ {
Console.WriteLine("Cycle cache hit tracking disabled after exceeding the tracking limit."); Console.WriteLine("Cycle cache hit tracking disabled after exceeding the tracking limit.");
} }
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices) private static void ComputeCyclesCpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
{ {
for (int i = 0; i < indices.Length; i++) for (int i = 0; i < indices.Length; i++)
{ {
int targetIndex = indices[i]; int targetIndex = indices[i];
ulong divisor = divisors[targetIndex]; ulong divisor = divisors[targetIndex];
MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor);
cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData); cycles[targetIndex] = MersenneDivisorCycles.CalculateCycleLength(divisor, divisorData);
} }
} }
private void ComputeCyclesGpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices) private void ComputeCyclesGpu(ReadOnlySpan<ulong> divisors, Span<ulong> cycles, ReadOnlySpan<int> indices)
{ {
int count = indices.Length; int count = indices.Length;
ulong[]? rentedDivisors = null; ulong[]? rentedDivisors = null;
Span<ulong> workDivisors = count <= StackBufferThreshold Span<ulong> workDivisors = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedDivisors = ThreadStaticPools.UlongPool.Rent(count), 0, count); : new Span<ulong>(rentedDivisors = ArrayPool<ulong>.Shared.Rent(count), 0, count);
ulong[]? rentedResults = null; ulong[]? rentedResults = null;
Span<ulong> workResults = count <= StackBufferThreshold Span<ulong> workResults = count <= StackBufferThreshold
? stackalloc ulong[count] ? stackalloc ulong[count]
: new Span<ulong>(rentedResults = ThreadStaticPools.UlongPool.Rent(count), 0, count); : new Span<ulong>(rentedResults = ArrayPool<ulong>.Shared.Rent(count), 0, count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
workDivisors[i] = divisors[indices[i]]; workDivisors[i] = divisors[indices[i]];
} }
ComputeCyclesGpuCore(workDivisors, workResults); ComputeCyclesGpuCore(workDivisors, workResults);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
cycles[indices[i]] = workResults[i]; cycles[indices[i]] = workResults[i];
} }
if (rentedDivisors is not null) if (rentedDivisors is not null)
{ {
ThreadStaticPools.UlongPool.Return(rentedDivisors, clearArray: false); ArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedResults!, clearArray: false); ArrayPool<ulong>.Shared.Return(rentedResults!, clearArray: false);
} }
} }
private const int DefaultCapacity = 128; private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination)
{
int length = divisors.Length;
var gpuLease = GpuKernelPool.GetKernel(useGpuOrder: true);
[ThreadStatic] Accelerator accelerator = gpuLease.Accelerator;
private static MemoryBuffer1D<ulong, Stride1D.Dense>? divisorBuffer; var kernel = _gpuKernelCache.GetOrAdd(accelerator, LoadKernel);
[ThreadStatic] using MemoryBuffer1D<ulong, Stride1D.Dense> divisorBuffer = accelerator.Allocate1D<ulong>(length);
private static MemoryBuffer1D<ulong, Stride1D.Dense>? powBuffer; using MemoryBuffer1D<ulong, Stride1D.Dense> powBuffer = accelerator.Allocate1D<ulong>(length);
using MemoryBuffer1D<ulong, Stride1D.Dense> orderBuffer = accelerator.Allocate1D<ulong>(length);
using MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = accelerator.Allocate1D<ulong>(length);
using MemoryBuffer1D<byte, Stride1D.Dense> statusBuffer = accelerator.Allocate1D<byte>(length);
[ThreadStatic] ulong[]? rentedPow = null;
private static MemoryBuffer1D<ulong, Stride1D.Dense>? orderBuffer; Span<ulong> powSpan = length <= StackBufferThreshold
? stackalloc ulong[length]
: new Span<ulong>(rentedPow = ArrayPool<ulong>.Shared.Rent(length), 0, length);
[ThreadStatic] ulong[]? rentedOrder = null;
private static MemoryBuffer1D<ulong, Stride1D.Dense>? resultBuffer; Span<ulong> orderSpan = length <= StackBufferThreshold
? stackalloc ulong[length]
: new Span<ulong>(rentedOrder = ArrayPool<ulong>.Shared.Rent(length), 0, length);
[ThreadStatic] ulong[]? rentedResult = null;
private static MemoryBuffer1D<byte, Stride1D.Dense>? statusBuffer; Span<ulong> resultSpan = length <= StackBufferThreshold
? stackalloc ulong[length]
: new Span<ulong>(rentedResult = ArrayPool<ulong>.Shared.Rent(length), 0, length);
private void EnsureCapacity(int requiredCapacity) byte[]? rentedStatus = null;
{ Span<byte> statusSpan = length <= StackBufferThreshold
if (requiredCapacity <= (divisorBuffer?.Length ?? 0)) ? stackalloc byte[length]
{ : new Span<byte>(rentedStatus = ArrayPool<byte>.Shared.Rent(length), 0, length);
return;
}
if (divisorBuffer != null) ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
{ divisorBuffer.View.CopyFromCPU(ref divisorRef, length);
divisorBuffer.Dispose();
powBuffer!.Dispose();
orderBuffer!.Dispose();
resultBuffer!.Dispose();
statusBuffer!.Dispose();
}
Accelerator accelerator = _accelerator; for (int i = 0; i < length; i++)
lock(accelerator) {
{ ulong divisor = divisors[i];
divisorBuffer = accelerator.Allocate1D<ulong>(requiredCapacity); ulong initialPow = 2UL;
powBuffer = accelerator.Allocate1D<ulong>(requiredCapacity); if (initialPow >= divisor)
orderBuffer = accelerator.Allocate1D<ulong>(requiredCapacity); {
resultBuffer = accelerator.Allocate1D<ulong>(requiredCapacity); initialPow -= divisor;
statusBuffer = accelerator.Allocate1D<byte>(requiredCapacity); }
}
}
private void ComputeCyclesGpuCore(ReadOnlySpan<ulong> divisors, Span<ulong> destination) powSpan[i] = initialPow;
{ orderSpan[i] = 1UL;
int length = divisors.Length; resultSpan[i] = 0UL;
statusSpan[i] = ByteZero;
}
EnsureCapacity(length); powBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(powSpan), length);
orderBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(orderSpan), length);
resultBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(resultSpan), length);
statusBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(statusSpan), length);
ulong[]? rentedPow = null; int pending;
Span<ulong> powSpan = length <= StackBufferThreshold do
? stackalloc ulong[length] {
: new Span<ulong>(rentedPow = ThreadStaticPools.UlongPool.Rent(length), 0, length); kernel(length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View);
accelerator.Synchronize();
ulong[]? rentedOrder = null; statusBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(statusSpan), length);
Span<ulong> orderSpan = length <= StackBufferThreshold pending = 0;
? stackalloc ulong[length] for (int i = 0; i < length; i++)
: new Span<ulong>(rentedOrder = ThreadStaticPools.UlongPool.Rent(length), 0, length); {
if (statusSpan[i] == ByteZero)
{
pending++;
}
}
}
while (pending > 0);
ulong[]? rentedResult = null; resultBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(resultSpan), length);
Span<ulong> resultSpan = length <= StackBufferThreshold resultSpan.CopyTo(destination);
? stackalloc ulong[length] if (rentedPow is not null)
: new Span<ulong>(rentedResult = ThreadStaticPools.UlongPool.Rent(length), 0, length); {
ArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false);
ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false);
}
byte[]? rentedStatus = null; gpuLease.Dispose();
Span<byte> statusSpan = length <= StackBufferThreshold }
? stackalloc byte[length]
: new Span<byte>(rentedStatus = ThreadStaticPools.BytePool.Rent(length), 0, length);
for (int i = 0; i < length; i++) private static Action<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator)
{ {
ulong divisor = divisors[i]; return accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel);
ulong initialPow = 2UL; }
if (initialPow >= divisor)
{
initialPow -= divisor;
}
powSpan[i] = initialPow; private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
orderSpan[i] = 1UL; {
resultSpan[i] = 0UL; internal static AcceleratorReferenceComparer Instance { get; } = new();
statusSpan[i] = ByteZero;
}
// GpuPrimeWorkLimiter.Acquire(); public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
int pending;
var kernel = _gpuKernel;
var acceleratorIndex = _acceleratorIndex;
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
divisorBuffer!.View.CopyFromCPU(stream, divisors); public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
powBuffer!.View.CopyFromCPU(stream, powSpan); }
orderBuffer!.View.CopyFromCPU(stream, orderSpan);
resultBuffer!.View.CopyFromCPU(stream, resultSpan);
statusBuffer!.View.CopyFromCPU(stream, statusSpan);
do
{
kernel(stream, length, _divisorCyclesBatchSize, divisorBuffer.View, powBuffer.View, orderBuffer.View, resultBuffer.View, statusBuffer.View);
statusBuffer.View.CopyToCPU(stream, statusSpan);
stream.Synchronize();
pending = 0;
for (int i = 0; i < length; i++)
{
if (statusSpan[i] == ByteZero)
{
pending++;
}
}
}
while (pending > 0);
resultBuffer.View.CopyToCPU(stream, resultSpan);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
resultSpan.CopyTo(destination);
if (rentedPow is not null)
{
ThreadStaticPools.UlongPool.Return(rentedPow, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedOrder!, clearArray: false);
ThreadStaticPools.UlongPool.Return(rentedResult!, clearArray: false);
ThreadStaticPools.BytePool.Return(rentedStatus!, clearArray: false);
}
// GpuPrimeWorkLimiter.Release();
}
private static Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>> LoadKernel(Accelerator accelerator)
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>(DivisorCycleKernels.GpuAdvanceDivisorCyclesKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>>>();
}
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
internal static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
}
} }
+2
View File
@@ -1,3 +1,5 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core; namespace PerfectNumbers.Core;
@@ -1,61 +0,0 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu
{
public struct AcceleratorPool
{
internal readonly Context Context;
internal readonly Device Device;
internal readonly Accelerator[] Accelerators;
private int _index;
public int Rent()
{
Atomic.CompareExchange(ref _index, Capacity, 0);
var index = Atomic.Add(ref _index, 1);
if (index >= Capacity)
{
index -= Capacity;
}
return index;
}
public void Return(int acceleratorIndex)
{
// Intentionally left empty - there's nothing to do here. We're not really renting anything.
}
public static readonly AcceleratorPool Shared = new(PerfectNumberConstants.RollingAccelerators);
private readonly int Capacity;
public AcceleratorPool(int capacity)
{
Console.WriteLine("Creating accelerators");
Capacity = capacity;
var context = Context.CreateDefault();
var device = context.GetPreferredDevice(false);
Context = context;
Device = device;
int acceleratorsInContext = 0;
int acceleratorIndex = 0;
Accelerators = [.. Enumerable.Range(0, capacity).Select(_ =>
{
Console.WriteLine($"Preparing accelerator {acceleratorIndex++}");
Accelerator accelerator = device.CreateAccelerator(context);
acceleratorsInContext++;
if (acceleratorsInContext >= 128)
{
context = Context.CreateDefault();
device = context.GetPreferredDevice(false);
acceleratorsInContext = 0;
}
return accelerator;
})];
}
}
}
@@ -1,13 +0,0 @@
using System.Runtime.CompilerServices;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
internal static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
}
@@ -1,34 +0,0 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public static class AcceleratorExtensions
{
public static MemoryBuffer1D<ulong, Stride1D.Dense> CopySpanToDevice(this Accelerator accelerator, AcceleratorStream stream, in ulong[] span)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? buffer;
buffer = accelerator.Allocate1D(stream, span);
return buffer;
}
public static MemoryBuffer1D<ulong, Stride1D.Dense> CopyUintSpanToDevice(this Accelerator accelerator, AcceleratorStream stream, ReadOnlySpan<uint> span)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? buffer;
buffer = accelerator.Allocate1D<ulong>(span.Length);
if (!span.IsEmpty)
{
var converted = new ulong[span.Length];
for (int i = 0; i < span.Length; i++)
{
converted[i] = span[i];
}
buffer.View.CopyFromCPU(stream, converted);
}
return buffer;
}
}
@@ -1,42 +0,0 @@
using System.Collections.Concurrent;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public static class AcceleratorStreamPool
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly SemaphoreSlim[] _locks = new SemaphoreSlim[PerfectNumberConstants.RollingAccelerators];
private static readonly ConcurrentQueue<AcceleratorStream>[] _streams = new ConcurrentQueue<AcceleratorStream> [PerfectNumberConstants.RollingAccelerators];
private static SemaphoreSlim CreateLock() => new(PerfectNumberConstants.ThreadsByAccelerator);
// private static int _rented;
public static AcceleratorStream Rent(int acceleratorIndex)
{
var streamLock = _locks[acceleratorIndex];
var queue = _streams[acceleratorIndex];
streamLock.Wait();
return queue.TryDequeue(out var stream)
? stream
: _accelerators[acceleratorIndex].CreateStream();
}
public static void Return(int acceleratorIndex, AcceleratorStream stream)
{
var streamLock = _locks[acceleratorIndex];
var queue = _streams[acceleratorIndex];
queue.Enqueue(stream);
streamLock.Release();
}
public static void WarmUp(int acceleratorIndex)
{
_streams[acceleratorIndex] = new();
_locks[acceleratorIndex] = CreateLock();
}
}
@@ -1,69 +0,0 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal readonly struct HeuristicCombinedGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly HeuristicCombinedGpuTables[] _sharedTables = new HeuristicCombinedGpuTables[PerfectNumberConstants.RollingAccelerators];
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new(_accelerators[acceleratorIndex], stream);
internal static HeuristicCombinedGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
internal HeuristicCombinedGpuTables(Accelerator accelerator, AcceleratorStream stream)
{
var combinedEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1;
HeuristicCombinedDivisorsEnding1 = accelerator.CopySpanToDevice(stream, combinedEnding1);
var combinedSquaresEnding1 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding1Squares;
HeuristicCombinedDivisorSquaresEnding1 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding1);
var combinedEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3;
HeuristicCombinedDivisorsEnding3 = accelerator.CopySpanToDevice(stream, combinedEnding3);
var combinedSquaresEnding3 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding3Squares;
HeuristicCombinedDivisorSquaresEnding3 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding3);
var combinedEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7;
HeuristicCombinedDivisorsEnding7 = accelerator.CopySpanToDevice(stream, combinedEnding7);
var combinedSquaresEnding7 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding7Squares;
HeuristicCombinedDivisorSquaresEnding7 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding7);
var combinedEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9;
HeuristicCombinedDivisorsEnding9 = accelerator.CopySpanToDevice(stream, combinedEnding9);
var combinedSquaresEnding9 = HeuristicCombinedPrimeTester.CombinedDivisorsEnding9Squares;
HeuristicCombinedDivisorSquaresEnding9 = accelerator.CopySpanToDevice(stream, combinedSquaresEnding9);
}
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding3;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding3;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorsEnding9;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicCombinedDivisorSquaresEnding9;
internal HeuristicCombinedGpuViews CreateViews() => new(
HeuristicCombinedDivisorsEnding1.View,
HeuristicCombinedDivisorsEnding3.View,
HeuristicCombinedDivisorsEnding7.View,
HeuristicCombinedDivisorsEnding9.View,
HeuristicCombinedDivisorSquaresEnding1.View,
HeuristicCombinedDivisorSquaresEnding3.View,
HeuristicCombinedDivisorSquaresEnding7.View,
HeuristicCombinedDivisorSquaresEnding9.View);
internal void Dispose()
{
HeuristicCombinedDivisorsEnding1.Dispose();
HeuristicCombinedDivisorSquaresEnding1.Dispose();
HeuristicCombinedDivisorsEnding3.Dispose();
HeuristicCombinedDivisorSquaresEnding3.Dispose();
HeuristicCombinedDivisorsEnding7.Dispose();
HeuristicCombinedDivisorSquaresEnding7.Dispose();
HeuristicCombinedDivisorsEnding9.Dispose();
HeuristicCombinedDivisorSquaresEnding9.Dispose();
}
}
@@ -1,45 +0,0 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public readonly struct HeuristicCombinedGpuViews(
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding3,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding9,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding1,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding3,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding7,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorSquaresEnding9)
{
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1 = combinedDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3 = combinedDivisorsEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7 = combinedDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9 = combinedDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding1 = combinedDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding3 = combinedDivisorSquaresEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding7 = combinedDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorSquaresEnding9 = combinedDivisorSquaresEnding9;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(byte ending) => ending switch
{
1 => CombinedDivisorsEnding1,
3 => CombinedDivisorsEnding3,
7 => CombinedDivisorsEnding7,
9 => CombinedDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisorSquares(byte ending) => ending switch
{
1 => CombinedDivisorSquaresEnding1,
3 => CombinedDivisorSquaresEnding3,
7 => CombinedDivisorSquaresEnding7,
9 => CombinedDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
@@ -1,79 +0,0 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal sealed class HeuristicGroupABGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly HeuristicGroupABGpuTables[] _sharedTables = new HeuristicGroupABGpuTables[PerfectNumberConstants.RollingAccelerators];
private static readonly SemaphoreSlim[] _locks = [..Enumerable.Range(1, PerfectNumberConstants.RollingAccelerators).Select(_ => new SemaphoreSlim(1))];
internal static HeuristicGroupABGpuTables EnsureStaticTables(int acceleratorIndex, AcceleratorStream stream)
{
var @lock = _locks[acceleratorIndex];
@lock.Wait();
if (_sharedTables[acceleratorIndex] is {} existing)
{
@lock.Release();
return existing;
}
var accelerator = _accelerators[acceleratorIndex];
existing = new(accelerator, stream);
_sharedTables[acceleratorIndex] = existing;
@lock.Release();
return existing;
}
internal HeuristicGroupABGpuTables(Accelerator accelerator, AcceleratorStream stream)
{
var heuristicGroupA = HeuristicPrimeSieves.GroupADivisorsStorage;
HeuristicGroupADivisors = accelerator.CopySpanToDevice(stream, heuristicGroupA);
var heuristicGroupASquares = HeuristicPrimeSieves.GroupADivisorSquaresStorage;
HeuristicGroupADivisorSquares = accelerator.CopySpanToDevice(stream, heuristicGroupASquares);
HeuristicGroupBDivisorsEnding1 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastOneWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding1 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree);
HeuristicGroupBDivisorsEnding7 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding7 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree);
HeuristicGroupBDivisorsEnding9 = accelerator.CopyUintSpanToDevice(stream, DivisorGenerator.SmallPrimesLastNineWithoutLastThree);
HeuristicGroupBDivisorSquaresEnding9 = accelerator.CopySpanToDevice(stream, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree);
}
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9;
internal HeuristicGroupABGpuViews CreateViews() => new(
HeuristicGroupADivisors.View,
HeuristicGroupADivisorSquares.View,
HeuristicGroupBDivisorsEnding1.View,
HeuristicGroupBDivisorSquaresEnding1.View,
HeuristicGroupBDivisorsEnding7.View,
HeuristicGroupBDivisorSquaresEnding7.View,
HeuristicGroupBDivisorsEnding9.View,
HeuristicGroupBDivisorSquaresEnding9.View);
internal void Dispose()
{
HeuristicGroupADivisors.Dispose();
HeuristicGroupADivisorSquares.Dispose();
HeuristicGroupBDivisorsEnding1.Dispose();
HeuristicGroupBDivisorSquaresEnding1.Dispose();
HeuristicGroupBDivisorsEnding7.Dispose();
HeuristicGroupBDivisorSquaresEnding7.Dispose();
HeuristicGroupBDivisorsEnding9.Dispose();
HeuristicGroupBDivisorSquaresEnding9.Dispose();
}
}
@@ -1,63 +0,0 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public readonly struct HeuristicGroupABGpuViews(
ArrayView1D<ulong, Stride1D.Dense> groupADivisors,
ArrayView1D<ulong, Stride1D.Dense> groupADivisorSquares,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding9,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorSquaresEnding9)
{
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisors = groupADivisors;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisorSquares = groupADivisorSquares;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1 = groupBDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding1 = groupBDivisorSquaresEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7 = groupBDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding7 = groupBDivisorSquaresEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9 = groupBDivisorsEnding9;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorSquaresEnding9 = groupBDivisorSquaresEnding9;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupB(byte ending) => ending switch
{
1 => GroupBDivisorsEnding1,
7 => GroupBDivisorsEnding7,
9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupBSquares(byte ending) => ending switch
{
1 => GroupBDivisorSquaresEnding1,
7 => GroupBDivisorSquaresEnding7,
9 => GroupBDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(HeuristicGpuDivisorTableKind kind) => kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisors,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisorSquares(HeuristicGpuDivisorTableKind kind) => kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisorSquares,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorSquaresEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorSquaresEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorSquaresEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
@@ -1,174 +0,0 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal sealed class HeuristicGroupABPrimeTesterAccelerator
{
#region Pool
[ThreadStatic]
private static Queue<HeuristicGroupABPrimeTesterAccelerator>? Pool;
internal static void WarmUp()
{
Accelerator[]? accelerators = AcceleratorPool.Shared.Accelerators;
int acceleratorCount = accelerators.Length;
for (var i = 0; i < acceleratorCount; i++)
{
Console.WriteLine($"Preparing heuristic accelerator {i}...");
var accelerator = accelerators[i];
AcceleratorStream stream = accelerator.CreateStream();
_ = HeuristicGroupABGpuTables.EnsureStaticTables(i, stream);
// _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None);
// KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream);
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
stream.Synchronize();
stream.Dispose();
Console.WriteLine($"Heuristic accelerator {i} is ready");
}
}
internal static HeuristicGroupABPrimeTesterAccelerator Rent(int minBufferCapacity = 1)
{
var pool = Pool ??= new();
if (!pool.TryDequeue(out var lease))
{
lease = new HeuristicGroupABPrimeTesterAccelerator(minBufferCapacity);
}
else
{
lease.EnsureCapacity(minBufferCapacity);
}
return lease;
}
internal static void Return(HeuristicGroupABPrimeTesterAccelerator lease)
{
var pool = Pool ??= new();
pool.Enqueue(lease);
}
internal static void DisposeAll()
{
if (Pool != null)
{
while (Pool.TryDequeue(out var lease))
{
lease.DisposeResources();
}
}
}
internal static void Clear()
{
if (Pool != null)
{
var retained = new List<HeuristicGroupABPrimeTesterAccelerator>();
while (Pool.TryDequeue(out var lease))
{
lease.DisposeResources();
}
}
}
#endregion
public readonly HeuristicGroupABGpuViews DivisorTables;
internal readonly Context Context;
public readonly Accelerator Accelerator;
[ThreadStatic]
public static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>? SmallPrimeSieveKernel;
internal static Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> GetSmallPrimeSieveKernel(Accelerator accelerator)
{
var pool = SmallPrimeSieveKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
cached = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>();
pool[accelerator] = cached;
return cached;
}
public readonly Kernel HeuristicTrialDivisionKernel;
public MemoryBuffer1D<ulong, Stride1D.Dense>? Input = null;
public MemoryBuffer1D<byte, Stride1D.Dense>? Output = null;
public MemoryBuffer1D<int, Stride1D.Dense>? HeuristicFlag = null!;
public int BufferCapacity;
private HeuristicGroupABGpuTables _sharedTables;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisors => _sharedTables.HeuristicGroupADivisors;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupADivisorSquares => _sharedTables.HeuristicGroupADivisorSquares;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding1 => _sharedTables.HeuristicGroupBDivisorsEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding1 => _sharedTables.HeuristicGroupBDivisorSquaresEnding1;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding7 => _sharedTables.HeuristicGroupBDivisorsEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding7 => _sharedTables.HeuristicGroupBDivisorSquaresEnding7;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorsEnding9 => _sharedTables.HeuristicGroupBDivisorsEnding9;
public MemoryBuffer1D<ulong, Stride1D.Dense> HeuristicGroupBDivisorSquaresEnding9 => _sharedTables.HeuristicGroupBDivisorSquaresEnding9;
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
internal readonly int AcceleratorIndex;
internal HeuristicGroupABPrimeTesterAccelerator(int minBufferCapacity)
{
var acceleratorIndex = AcceleratorPool.Shared.Rent();
AcceleratorIndex = acceleratorIndex;
Accelerator accelerator = _accelerators[acceleratorIndex];
Context = accelerator.Context;
Accelerator = accelerator;
EnsureCapacity(minBufferCapacity);
HeuristicTrialDivisionKernel = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<int>, ulong, ulong, HeuristicGpuDivisorTableKind, HeuristicGroupABGpuViews>(PrimeTesterKernels.HeuristicTrialGroupABDivisionKernel));
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
_sharedTables = HeuristicGroupABGpuTables.EnsureStaticTables(acceleratorIndex, stream);
DivisorTables = _sharedTables.CreateViews();
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
}
public void EnsureCapacity(int minCapacity)
{
if (minCapacity <= BufferCapacity)
{
return;
}
Input?.Dispose();
Output?.Dispose();
HeuristicFlag?.Dispose();
Input = Accelerator.Allocate1D<ulong>(minCapacity);
Output = Accelerator.Allocate1D<byte>(minCapacity);
HeuristicFlag = Accelerator.Allocate1D<int>(minCapacity);
BufferCapacity = minCapacity;
}
public void Dispose()
{
Return(this);
}
internal void DisposeResources()
{
Input?.Dispose();
Output?.Dispose();
HeuristicFlag?.Dispose();
// ReleaseSharedTables(_sharedTables);
// These resources are shared between GPU leases
// Accelerator.Dispose();
// AcceleratorContext.Dispose();
}
}
@@ -1,58 +0,0 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu.Accelerators;
internal readonly struct LastDigitGpuTables
{
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private static readonly LastDigitGpuTables[] _sharedTables = new LastDigitGpuTables[PerfectNumberConstants.RollingAccelerators];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static LastDigitGpuTables GetStaticTables(int acceleratorIndex) => _sharedTables[acceleratorIndex];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void WarmUp(int acceleratorIndex, AcceleratorStream stream) => _sharedTables[acceleratorIndex] = new LastDigitGpuTables(_accelerators[acceleratorIndex], stream);
internal LastDigitGpuTables(Accelerator accelerator, AcceleratorStream stream)
{
var primesLastOne = DivisorGenerator.SmallPrimesLastOne;
var primesLastSeven = DivisorGenerator.SmallPrimesLastSeven;
var primesLastThree = DivisorGenerator.SmallPrimesLastThree;
var primesLastNine = DivisorGenerator.SmallPrimesLastNine;
var primesPow2LastOne = DivisorGenerator.SmallPrimesPow2LastOne;
var primesPow2LastSeven = DivisorGenerator.SmallPrimesPow2LastSeven;
var primesPow2LastThree = DivisorGenerator.SmallPrimesPow2LastThree;
var primesPow2LastNine = DivisorGenerator.SmallPrimesPow2LastNine;
DevicePrimesLastOne = accelerator.Allocate1D(stream, primesLastOne);
DevicePrimesLastSeven = accelerator.Allocate1D(stream, primesLastSeven);
DevicePrimesLastThree = accelerator.Allocate1D(stream, primesLastThree);
DevicePrimesLastNine = accelerator.Allocate1D(stream, primesLastNine);
DevicePrimesPow2LastOne = accelerator.Allocate1D(stream, primesPow2LastOne);
DevicePrimesPow2LastSeven = accelerator.Allocate1D(stream, primesPow2LastSeven);
DevicePrimesPow2LastThree = accelerator.Allocate1D(stream, primesPow2LastThree);
DevicePrimesPow2LastNine = accelerator.Allocate1D(stream, primesPow2LastNine);
}
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastOne;
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastSeven;
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastThree;
internal readonly MemoryBuffer1D<uint, Stride1D.Dense> DevicePrimesLastNine;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree;
internal readonly MemoryBuffer1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine;
internal void Dispose()
{
DevicePrimesLastOne.Dispose();
DevicePrimesLastSeven.Dispose();
DevicePrimesLastThree.Dispose();
DevicePrimesLastNine.Dispose();
DevicePrimesPow2LastOne.Dispose();
DevicePrimesPow2LastSeven.Dispose();
DevicePrimesPow2LastThree.Dispose();
DevicePrimesPow2LastNine.Dispose();
}
}
@@ -1,300 +0,0 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Kernels;
namespace PerfectNumbers.Core.Gpu.Accelerators;
public sealed class PrimeOrderCalculatorAccelerator
{
#region Pool
[ThreadStatic]
private static Queue<PrimeOrderCalculatorAccelerator>? _pool;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static PrimeOrderCalculatorAccelerator Rent(int primeTesterCapacity)
{
var pool = _pool ??= [];
if (!pool.TryDequeue(out var gpu))
{
int acceleratorIndex = AcceleratorPool.Shared.Rent();
gpu = new(acceleratorIndex, PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity, PerfectNumberConstants.DefaultSmallPrimeFactorSlotCount, PerfectNumberConstants.DefaultSpecialMaxFactorCapacity);
}
else
{
gpu.EnsureCapacity(PerfectNumberConstants.DefaultFactorsBuffer, primeTesterCapacity);
}
return gpu;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Return(PrimeOrderCalculatorAccelerator gpu) => _pool!.Enqueue(gpu);
internal static void Clear()
{
Queue<PrimeOrderCalculatorAccelerator>? pool = _pool;
if (pool != null)
{
while (pool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
}
}
internal static void DisposeAll()
{
Queue<PrimeOrderCalculatorAccelerator>? pool = _pool;
if (pool != null)
{
while (pool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
}
}
internal static void WarmUp()
{
Accelerator[] accelerators = AcceleratorPool.Shared.Accelerators;
int acceleratorCount = accelerators.Length;
for (var i = 0; i < acceleratorCount; i++)
{
Console.WriteLine($"Preparing accelerator {i}...");
var accelerator = accelerators[i];
AcceleratorStreamPool.WarmUp(i);
// Don't take this from the pool as quick uploads of data to the accelerator consumes much of GPU's memory and throws.
AcceleratorStream stream = accelerator.CreateStream();
// TODO: Review which tables are needed when final execution path is defined.
LastDigitGpuTables.WarmUp(i, stream);
HeuristicCombinedGpuTables.WarmUp(i, stream);
// SmallPrimeFactorGpuTables.WarmUp(i, stream);
// SharedHeuristicGpuTables.EnsureStaticTables(accelerator, stream);
// _ = GpuKernelPool.GetOrAddKernels(accelerator, stream, KernelType.None);
// KernelContainer kernels = GpuKernelPool.GetOrAddKernels(accelerator, stream);
// GpuStaticTableInitializer.EnsureStaticTables(accelerator, kernels, stream);
stream.Synchronize();
stream.Dispose();
accelerator.Synchronize();
}
}
#endregion
#region Static Tables
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastOne;
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastSeven;
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastThree;
public readonly ArrayView1D<uint, Stride1D.Dense> DevicePrimesLastNine;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastOne;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastSeven;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastThree;
public readonly ArrayView1D<ulong, Stride1D.Dense> DevicePrimesPow2LastNine;
public readonly HeuristicCombinedGpuViews DivisorTables;
public readonly ArrayView1D<uint, Stride1D.Dense> SmallPrimeFactorPrimes;
public readonly ArrayView1D<ulong, Stride1D.Dense> SmallPrimeFactorSquares;
#endregion
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public readonly Accelerator Accelerator;
public readonly int AcceleratorIndex;
public MemoryBuffer1D<ulong, Stride1D.Dense> Input;
public MemoryBuffer1D<ulong, Stride1D.Dense> OutputUlong;
public MemoryBuffer1D<byte, Stride1D.Dense> OutputByte;
public MemoryBuffer1D<int, Stride1D.Dense> OutputInt;
public MemoryBuffer1D<KeyValuePair<ulong, int>, Stride1D.Dense> Pow2ModEntriesToTestOnDevice;
public readonly MemoryBuffer1D<int, Stride1D.Dense> SmallPrimeFactorCountSlot;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> SpecialMaxResult;
public Dictionary<ulong, int> Pow2ModEntriesToTestOnHost = [];
public readonly Action<AcceleratorStream, int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> CheckFactorsKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> ConvertToStandardKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews> HeuristicCombinedTrialDivisionKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>> KeepMontgomeryKernelLauncher;
public readonly Action<AcceleratorStream, ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> PollardRhoKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>> Pow2ModWideKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<byte>> SharesFactorKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>> SmallPrimeSieveKernelLauncher;
public readonly Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SpecialMaxKernelLauncher;
public void EnsureCapacity(int factorsCount, int primeTesterCapacity)
{
Accelerator accelerator = Accelerator;
MemoryBuffer1D<KeyValuePair<ulong, int>, Stride1D.Dense> pow2ModEntriesToTestOnDevice = Pow2ModEntriesToTestOnDevice;
if (pow2ModEntriesToTestOnDevice.Length < factorsCount)
{
lock (accelerator)
{
pow2ModEntriesToTestOnDevice.Dispose();
Pow2ModEntriesToTestOnDevice = accelerator.Allocate1D<KeyValuePair<ulong, int>>(factorsCount);
}
}
MemoryBuffer1D<ulong, Stride1D.Dense> input = Input;
if (input.Length < primeTesterCapacity)
{
lock (accelerator)
{
input.Dispose();
Input = accelerator.Allocate1D<ulong>(primeTesterCapacity);
}
}
MemoryBuffer1D<byte, Stride1D.Dense> outputByte = OutputByte;
if (outputByte.Length < primeTesterCapacity)
{
lock (accelerator)
{
outputByte.Dispose();
OutputByte = accelerator.Allocate1D<byte>(primeTesterCapacity);
}
}
MemoryBuffer1D<int, Stride1D.Dense> outputInt = OutputInt;
if (outputInt.Length < primeTesterCapacity)
{
lock (accelerator)
{
outputInt.Dispose();
OutputInt = accelerator.Allocate1D<int>(primeTesterCapacity);
}
}
}
public void EnsureSmallPrimeFactorSlotsCapacity(int newSize)
{
// Console.WriteLine($"Resizing GPU scratch buffer from pool ({buffer.SmallPrimeFactorPrimeSlots.Length} / {smallPrimeFactorSlotCount}), ({buffer.SpecialMaxFactors.Length}/{specialMaxFactorCapacity})");
var accelerator = Accelerator;
MemoryBuffer1D<ulong, Stride1D.Dense> outputUlong = OutputUlong;
if (outputUlong.Length < newSize)
{
lock (accelerator)
{
outputUlong.Dispose();
OutputUlong = accelerator.Allocate1D<ulong>(newSize);
}
}
MemoryBuffer1D<int, Stride1D.Dense> outputInt = OutputInt;
if (outputInt.Length < newSize)
{
lock (accelerator)
{
outputInt.Dispose();
OutputInt = accelerator.Allocate1D<int>(newSize);
}
}
}
public void EnsureSpecialMaxFactorsCapacity(int newSize)
{
var accelerator = Accelerator;
MemoryBuffer1D<ulong, Stride1D.Dense> input = Input;
if (input.Length < newSize)
{
lock (accelerator)
{
input.Dispose();
Input = accelerator.Allocate1D<ulong>(newSize);
}
}
MemoryBuffer1D<ulong, Stride1D.Dense> outputUlong = OutputUlong;
if (outputUlong.Length < newSize)
{
lock (accelerator)
{
outputUlong.Dispose();
OutputUlong = accelerator.Allocate1D<ulong>(newSize);
}
}
}
public PrimeOrderCalculatorAccelerator(int acceleratorIndex, int factorsCount, int primeTesterCapacity, int smallPrimeFactorSlotCount, int specialMaxFactorCapacity)
{
var accelerator = _accelerators[acceleratorIndex];
Accelerator = accelerator;
AcceleratorIndex = acceleratorIndex;
lock (accelerator)
{
Input = accelerator.Allocate1D<ulong>(Math.Max(specialMaxFactorCapacity, primeTesterCapacity));
OutputByte = Accelerator.Allocate1D<byte>(primeTesterCapacity);
OutputInt = Accelerator.Allocate1D<int>(Math.Max(primeTesterCapacity, smallPrimeFactorSlotCount));
OutputUlong = accelerator.Allocate1D<ulong>(Math.Max(specialMaxFactorCapacity, smallPrimeFactorSlotCount));
Pow2ModEntriesToTestOnDevice = accelerator.Allocate1D<KeyValuePair<ulong, int>>(factorsCount);
SmallPrimeFactorCountSlot = accelerator.Allocate1D<int>(1);
SpecialMaxResult = accelerator.Allocate1D<ulong>(1);
LastDigitGpuTables lastDigitSharedTables = LastDigitGpuTables.GetStaticTables(acceleratorIndex);
DevicePrimesLastOne = lastDigitSharedTables.DevicePrimesLastOne.View;
DevicePrimesLastSeven = lastDigitSharedTables.DevicePrimesLastSeven.View;
DevicePrimesLastThree = lastDigitSharedTables.DevicePrimesLastThree.View;
DevicePrimesLastNine = lastDigitSharedTables.DevicePrimesLastNine.View;
DevicePrimesPow2LastOne = lastDigitSharedTables.DevicePrimesPow2LastOne.View;
DevicePrimesPow2LastSeven = lastDigitSharedTables.DevicePrimesPow2LastSeven.View;
DevicePrimesPow2LastThree = lastDigitSharedTables.DevicePrimesPow2LastThree.View;
DevicePrimesPow2LastNine = lastDigitSharedTables.DevicePrimesPow2LastNine.View;
DivisorTables = HeuristicCombinedGpuTables
.GetStaticTables(acceleratorIndex)
.CreateViews();
var smallPrimeFactorTables = SmallPrimeFactorGpuTables.GetStaticTables(acceleratorIndex);
if (smallPrimeFactorTables.HasValue)
{
SmallPrimeFactorGpuTables tables = smallPrimeFactorTables.Value;
SmallPrimeFactorPrimes = tables.Primes.View;
SmallPrimeFactorSquares = tables.Squares.View;
}
CheckFactorsKernelLauncher = KernelUtil.GetKernel(accelerator.LoadStreamKernel<int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.CheckFactorsKernel)).CreateLauncherDelegate<Action<AcceleratorStream, int, ulong, ArrayView1D<KeyValuePair<ulong, int>, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
ConvertToStandardKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.Pow2MontgomeryKernelConvertToStandard)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
HeuristicCombinedTrialDivisionKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews>(PrimeTesterKernels.HeuristicTrialCombinedDivisionKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, byte, ArrayView<int>, ulong, ulong, HeuristicCombinedGpuViews>>();
KeepMontgomeryKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.Pow2MontgomeryKernelKeepMontgomery)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<ulong, Stride1D.Dense>, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>>>();
PollardRhoKernelLauncher = KernelUtil.GetKernel(accelerator.LoadStreamKernel<ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2MontgomeryKernels.TryPollardRhoKernel)).CreateLauncherDelegate<Action<AcceleratorStream, ulong, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<byte, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
Pow2ModWideKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>(PrimeOrderGpuHeuristics.Pow2ModKernelWide)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView1D<GpuUInt128, Stride1D.Dense>, GpuUInt128, ArrayView1D<GpuUInt128, Stride1D.Dense>>>();
SharesFactorKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SharesFactorKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<byte>>>();
SmallPrimeFactorKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
SmallPrimeSieveKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>(PrimeTesterKernels.SmallPrimeSieveKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<uint>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<ulong>, ArrayView<byte>>>();
SpecialMaxKernelLauncher = KernelUtil.GetKernel(accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(PrimeOrderGpuHeuristics.EvaluateSpecialMaxCandidatesKernel)).CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
}
}
public void Dispose()
{
Input.Dispose();
OutputByte.Dispose();
OutputInt.Dispose();
OutputUlong.Dispose();
Pow2ModEntriesToTestOnDevice.Dispose();
SmallPrimeFactorCountSlot.Dispose();
SpecialMaxResult.Dispose();
// These resources are shared between GPU leases
// Stream.Dispose();
// Accelerator.Dispose();
// Context.Dispose();
}
}
@@ -1,75 +0,0 @@
using System.Runtime.CompilerServices;
using ILGPU;
namespace PerfectNumbers.Core.Gpu;
public static class ThreadStaticDeterministicRandomGpu
{
[ThreadStatic]
private static DeterministicRandomGpu? _exclusive;
internal static DeterministicRandomGpu Exclusive => _exclusive ?? new DeterministicRandomGpu();
}
internal struct DeterministicRandomGpu
{
private static long s_seedCounter;
private ulong _state;
public readonly ulong State
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _state;
}
}
public DeterministicRandomGpu()
{
_state = CreateThreadState();
}
public DeterministicRandomGpu(ulong initialState)
{
_state = initialState;
}
public ulong NextUInt64()
{
ulong state = _state;
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
_state = state;
return state * 2685821657736338717UL;
}
public GpuUInt128 NextUInt128()
{
GpuUInt128 high = (GpuUInt128)NextUInt64();
ReadOnlyGpuUInt128 low = (ReadOnlyGpuUInt128)NextUInt64();
high.ShiftLeft(64);
high.Add(low);
return high;
}
public void SetState(ulong state)
{
_state = state;
}
private static ulong CreateThreadState()
{
ulong seed = (ulong)Atomic.Add(ref s_seedCounter, 1);
seed += 0x9E3779B97F4A7C15UL;
seed = (seed ^ (seed >> 30)) * 0xBF58476D1CE4E5B9UL;
seed = (seed ^ (seed >> 27)) * 0x94D049BB133111EBUL;
seed ^= seed >> 31;
if (seed == 0UL)
{
seed = 0x9E3779B97F4A7C15UL;
}
return seed;
}
}
@@ -0,0 +1,142 @@
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal static class DivisorGeneratorGpuCache
{
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
public static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y)
{
return ReferenceEquals(x, y);
}
public int GetHashCode(Accelerator obj)
{
return RuntimeHelpers.GetHashCode(obj);
}
}
private sealed class DivisorGeneratorGpuTables : IDisposable
{
internal DivisorGeneratorGpuTables(Accelerator accelerator)
{
SmallPrimes = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimes);
SmallPrimesPow2 = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2);
SmallPrimesLastOne = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastOne);
SmallPrimesPow2LastOne = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastOne);
SmallPrimesLastSeven = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastSeven);
SmallPrimesPow2LastSeven = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastSeven);
SmallPrimesLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastThree);
SmallPrimesPow2LastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastThree);
SmallPrimesLastNine = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastNine);
SmallPrimesPow2LastNine = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastNine);
SmallPrimesLastOneWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastOneWithoutLastThree);
SmallPrimesPow2LastOneWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastOneWithoutLastThree);
SmallPrimesLastSevenWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastSevenWithoutLastThree);
SmallPrimesPow2LastSevenWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastSevenWithoutLastThree);
SmallPrimesLastNineWithoutLastThree = AllocateAndCopyUInt(accelerator, DivisorGenerator.SmallPrimesLastNineWithoutLastThree);
SmallPrimesPow2LastNineWithoutLastThree = AllocateAndCopyULong(accelerator, DivisorGenerator.SmallPrimesPow2LastNineWithoutLastThree);
}
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimes { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2 { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastOne { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastOne { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastSeven { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastSeven { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastThree { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastNine { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastNine { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastOneWithoutLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastOneWithoutLastThree { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastSevenWithoutLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastSevenWithoutLastThree { get; }
internal MemoryBuffer1D<uint, Stride1D.Dense> SmallPrimesLastNineWithoutLastThree { get; }
internal MemoryBuffer1D<ulong, Stride1D.Dense> SmallPrimesPow2LastNineWithoutLastThree { get; }
public void Dispose()
{
SmallPrimes.Dispose();
SmallPrimesPow2.Dispose();
SmallPrimesLastOne.Dispose();
SmallPrimesPow2LastOne.Dispose();
SmallPrimesLastSeven.Dispose();
SmallPrimesPow2LastSeven.Dispose();
SmallPrimesLastThree.Dispose();
SmallPrimesPow2LastThree.Dispose();
SmallPrimesLastNine.Dispose();
SmallPrimesPow2LastNine.Dispose();
SmallPrimesLastOneWithoutLastThree.Dispose();
SmallPrimesPow2LastOneWithoutLastThree.Dispose();
SmallPrimesLastSevenWithoutLastThree.Dispose();
SmallPrimesPow2LastSevenWithoutLastThree.Dispose();
SmallPrimesLastNineWithoutLastThree.Dispose();
SmallPrimesPow2LastNineWithoutLastThree.Dispose();
}
private static MemoryBuffer1D<uint, Stride1D.Dense> AllocateAndCopyUInt(Accelerator accelerator, uint[] source)
{
var buffer = accelerator.Allocate1D<uint>(source.Length);
if (source.Length > 0)
{
buffer.View.CopyFromCPU(source);
}
return buffer;
}
private static MemoryBuffer1D<ulong, Stride1D.Dense> AllocateAndCopyULong(Accelerator accelerator, ulong[] source)
{
var buffer = accelerator.Allocate1D<ulong>(source.Length);
if (source.Length > 0)
{
buffer.View.CopyFromCPU(source);
}
return buffer;
}
}
private static readonly ConcurrentDictionary<Accelerator, DivisorGeneratorGpuTables> Cache = new(AcceleratorReferenceComparer.Instance);
public static void WarmUpByDivisorTables(Accelerator accelerator)
{
if (accelerator is null || accelerator.AcceleratorType == AcceleratorType.CPU)
{
return;
}
Cache.GetOrAdd(accelerator, acc => new DivisorGeneratorGpuTables(acc));
}
public static void Clear(Accelerator accelerator)
{
if (accelerator is null)
{
return;
}
if (Cache.TryRemove(accelerator, out var tables))
{
tables.Dispose();
}
}
public static void DisposeAll()
{
foreach (var entry in Cache)
{
entry.Value.Dispose();
}
Cache.Clear();
}
}
@@ -1,4 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -23,14 +24,17 @@ internal struct ExponentRemainderStepperGpu
_currentResidue = GpuUInt128.One; _currentResidue = GpuUInt128.One;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool MatchesDivisor(in GpuDivisorPartialData divisor) => _modulus == divisor.Modulus; public bool MatchesDivisor(in GpuDivisorPartialData divisor)
{
return _modulus == divisor.Modulus;
}
/// <summary> /// <summary>
/// Clears the cached residue so the stepper can be reused for another divisor. /// Clears the cached residue so the stepper can be reused for another divisor.
/// Callers must reinitialize the stepper before consuming any residues after a reset. /// Callers must reinitialize the stepper before consuming any residues after a reset.
/// </summary> /// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Reset() public void Reset()
{ {
PreviousExponent = 0UL; PreviousExponent = 0UL;
+117
View File
@@ -0,0 +1,117 @@
using System;
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
public static class GpuContextPool
{
// The pool intentionally skips pre-loading ProcessEightBitWindows kernels so accelerator initialization
// stays stable; GpuKernelPool JIT-compiles them on first use alongside the small-cycle uploads.
// Default device preference for generic GPU kernels (prime scans, NTT, etc.)
internal sealed class PooledContext
{
public Context Context { get; }
public Accelerator Accelerator { get; }
public PooledContext()
{
Context = Context.CreateDefault();
Accelerator = Context.GetPreferredDevice(false).CreateAccelerator(Context);
DivisorGeneratorGpuCache.WarmUpByDivisorTables(Accelerator);
// NOTE: Avoid loading/compiling any kernel here to prevent implicit
// CL stream/queue creation during accelerator construction.
// Some OpenCL drivers are fragile when a queue is created immediately
// after device init. Real kernels will be JIT-loaded at first use.
}
public void Dispose()
{
// Ensure all cached GPU buffers for this accelerator are released.
NttGpuMath.ClearCaches(Accelerator);
// Release PrimeTester GPU state for this accelerator (kernel/device primes)
// Clear any per-accelerator cached resources to avoid releasing
// after the accelerator is destroyed.
DivisorGeneratorGpuCache.Clear(Accelerator);
PrimeTester.ClearGpuCaches(Accelerator);
Accelerator.Dispose();
Context.Dispose();
}
}
private static readonly ConcurrentQueue<PooledContext> GpuPool = new();
private static int WarmedGpuContextCount;
// Allows callers to choose CPU/GPU per use-case, decoupled from ForceCpu.
public static GpuContextLease Rent()
{
if (GpuPool.TryDequeue(out var gpu))
{
return new GpuContextLease(gpu);
}
// Create a new accelerator when the pool does not have one available.
return new GpuContextLease(new PooledContext());
}
public static void WarmUpPool(int threadCount)
{
if (threadCount <= WarmedGpuContextCount)
{
return;
}
int toCreate = threadCount - WarmedGpuContextCount;
var contexts = new PooledContext[toCreate];
for (int i = 0; i < toCreate; i++)
{
contexts[i] = new PooledContext();
}
for (int i = 0; i < toCreate; i++)
{
GpuPool.Enqueue(contexts[i]);
}
WarmedGpuContextCount = threadCount;
}
public static void DisposeAll()
{
while (GpuPool.TryDequeue(out var gpu))
{
gpu.Dispose();
}
DivisorGeneratorGpuCache.DisposeAll();
PrimeTester.DisposeGpuContexts();
WarmedGpuContextCount = 0;
}
private static void Return(PooledContext ctx)
{
ctx.Accelerator.Synchronize();
GpuPool.Enqueue(ctx);
}
public readonly struct GpuContextLease
{
private readonly PooledContext _ctx;
internal GpuContextLease(PooledContext ctx)
{
_ctx = ctx;
}
public readonly Context Context => _ctx.Context;
public readonly Accelerator Accelerator => _ctx.Accelerator;
public readonly void Dispose()
{
Return(_ctx);
}
}
}
+153
View File
@@ -0,0 +1,153 @@
using System;
using System.Collections.Concurrent;
using ILGPU;
using ILGPU.Runtime;
using static PerfectNumbers.Core.Gpu.GpuContextPool;
namespace PerfectNumbers.Core.Gpu;
public sealed class GpuKernelLease
{
private static readonly ConcurrentQueue<GpuKernelLease> Pool = new();
private Action? _releaseLimiter;
private GpuContextLease? _gpu;
private KernelContainer? _kernels;
private AcceleratorStream? _stream;
private GpuKernelLease()
{
}
internal static GpuKernelLease Rent(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels)
{
if (!Pool.TryDequeue(out var lease))
{
lease = new GpuKernelLease();
}
lease._releaseLimiter = limiter.Dispose;
lease._gpu = gpu;
lease._kernels = kernels;
lease._stream = null;
return lease;
}
public Accelerator Accelerator => _gpu?.Accelerator ?? throw new NullReferenceException("GPU context is already released");
public AcceleratorStream Stream
{
get
{
if (_stream is { } stream)
{
return stream;
}
// It feels that we can combine these into 1 return statement, but that increases the max stack frame size to 3 from 2
stream = Accelerator.CreateStream();
_stream = stream;
return stream;
}
}
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>> OrderKernel
{
get
{
var accel = Accelerator; // avoid capturing 'this' in lambda
return KernelContainer.InitOnce(ref _kernels!.Order, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>();
});
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModKernel
{
get
{
var accel = Accelerator;
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>>(Pow2ModKernels.Pow2ModKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalKernel
{
get
{
var accel = Accelerator;
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>>(IncrementalKernels.IncrementalKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> IncrementalOrderKernel
{
get
{
var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.IncrementalOrder, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>> Pow2ModOrderKernel
{
get
{
var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.Pow2ModOrder, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>> SmallPrimeFactorKernel
{
get
{
var accel = Accelerator;
return KernelContainer.InitOnce(ref _kernels!.SmallPrimeFactor, () =>
{
var loaded = accel.LoadAutoGroupedStreamKernel<Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>(SmallPrimeFactorKernels.SmallPrimeFactorKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
public void Dispose()
{
_stream?.Dispose();
_stream = null;
_gpu?.Dispose();
_gpu = null;
_releaseLimiter?.Invoke();
_releaseLimiter = null;
_kernels = null;
Pool.Enqueue(this);
}
}
+271 -200
View File
@@ -1,232 +1,303 @@
using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators; using static PerfectNumbers.Core.Gpu.GpuContextPool;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public readonly struct ResidueAutomatonArgs
{
public readonly ulong Q0M10;
public readonly ulong Step10;
public readonly ulong Q0M8;
public readonly ulong Step8;
public readonly ulong Q0M3;
public readonly ulong Step3;
public readonly ulong Q0M5;
public readonly ulong Step5;
public ResidueAutomatonArgs(ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5)
{
Q0M10 = q0m10; Step10 = step10; Q0M8 = q0m8; Step8 = step8; Q0M3 = q0m3; Step3 = step3; Q0M5 = q0m5; Step5 = step5;
}
}
public sealed class KernelContainer
{
// Serializes first-time initialization of kernels/buffers per accelerator.
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
public Action<AcceleratorStream, Index1D, ulong, uint, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, int, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<int, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SmallPrimeFactor;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorPrimeSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorExponentSlots;
public MemoryBuffer1D<int, Stride1D.Dense>? SmallPrimeFactorCountSlot;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorRemainingSlot;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
public static T InitOnce<T>(ref T? slot, Func<T> factory) where T : class
{
var current = Volatile.Read(ref slot);
if (current is not null)
{
return current;
}
current = Volatile.Read(ref slot);
if (current is null)
{
current = factory();
Volatile.Write(ref slot, current);
}
return current;
}
}
public readonly struct ResiduePrimeViews(
ArrayView1D<uint, Stride1D.Dense> lastOne,
ArrayView1D<uint, Stride1D.Dense> lastSeven,
ArrayView1D<ulong, Stride1D.Dense> lastOnePow2,
ArrayView1D<ulong, Stride1D.Dense> lastSevenPow2)
{
public readonly ArrayView1D<uint, Stride1D.Dense> LastOne = lastOne;
public readonly ArrayView1D<uint, Stride1D.Dense> LastSeven = lastSeven;
public readonly ArrayView1D<ulong, Stride1D.Dense> LastOnePow2 = lastOnePow2;
public readonly ArrayView1D<ulong, Stride1D.Dense> LastSevenPow2 = lastSevenPow2;
}
public readonly struct SmallPrimeFactorTables
{
public readonly MemoryBuffer1D<uint, Stride1D.Dense> Primes;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> Squares;
public readonly int Count;
public SmallPrimeFactorTables(
MemoryBuffer1D<uint, Stride1D.Dense> primes,
MemoryBuffer1D<ulong, Stride1D.Dense> squares,
int count)
{
Primes = primes;
Squares = squares;
Count = count;
}
public ArrayView1D<uint, Stride1D.Dense> PrimesView => Primes.View;
public ArrayView1D<ulong, Stride1D.Dense> SquaresView => Squares.View;
}
public readonly struct SmallPrimeFactorScratch
{
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> PrimeSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> ExponentSlots;
public readonly MemoryBuffer1D<int, Stride1D.Dense> CountSlot;
public readonly MemoryBuffer1D<ulong, Stride1D.Dense> RemainingSlot;
public SmallPrimeFactorScratch(
MemoryBuffer1D<ulong, Stride1D.Dense> primeSlots,
MemoryBuffer1D<int, Stride1D.Dense> exponentSlots,
MemoryBuffer1D<int, Stride1D.Dense> countSlot,
MemoryBuffer1D<ulong, Stride1D.Dense> remainingSlot)
{
PrimeSlots = primeSlots;
ExponentSlots = exponentSlots;
CountSlot = countSlot;
RemainingSlot = remainingSlot;
}
public void Clear()
{
PrimeSlots.MemSetToZero();
ExponentSlots.MemSetToZero();
CountSlot.MemSetToZero();
RemainingSlot.MemSetToZero();
}
}
public class GpuKernelPool public class GpuKernelPool
{ {
[ThreadStatic] 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 KernelContainer[]? _pool;
public static T InitOnce<T>(ref T? target, Func<T> valueFactory) where T : class private static KernelContainer GetKernels(Accelerator accelerator)
{ {
if (target is { } value) return KernelCache.GetOrAdd(accelerator, _ => new KernelContainer());
{ }
return value;
}
var newValue = valueFactory(); // Ensures the small cycles table is uploaded to the device for the given accelerator.
return Interlocked.CompareExchange(ref target, newValue, null) ?? newValue; // Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
} public static ArrayView1D<ulong, Stride1D.Dense> EnsureSmallCyclesOnDevice(Accelerator accelerator)
{
var kernels = GetKernels(accelerator);
if (kernels.SmallCycles is { } buffer)
{
return buffer.View;
}
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; // 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.
{
if (kernels.SmallCycles is { } existing)
{
return existing.View;
}
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot(); // TODO: Preload this device buffer during startup and keep it immutable so we can delete the lock above in favor of the preloaded snapshot.
var device = accelerator.Allocate1D<ulong>(host.Length);
device.View.CopyFromCPU(host);
kernels.SmallCycles = device;
return device.View;
}
}
internal static KernelContainer GetOrAddKernels(int acceleratorIndex, AcceleratorStream stream, KernelType kernelType) public static ResiduePrimeViews EnsureSmallPrimesOnDevice(Accelerator accelerator)
{ {
var pool = _pool ??= []; var kernels = GetKernels(accelerator);
if (pool[acceleratorIndex] is not { } kernels) if (kernels.SmallPrimesLastOne is { } lastOne &&
{ kernels.SmallPrimesLastSeven is { } lastSeven &&
kernels = new(); kernels.SmallPrimesPow2LastOne is { } lastOnePow2 &&
} kernels.SmallPrimesPow2LastSeven is { } lastSevenPow2)
{
return new ResiduePrimeViews(lastOne.View, lastSeven.View, lastOnePow2.View, lastSevenPow2.View);
}
Accelerator accelerator = _accelerators[acceleratorIndex]; 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 (kernelType.HasFlag(KernelType.OrderKernelScan)) {
{ if (kernels.SmallPrimesLastOne is { } existingLastOne &&
InitOrderKernelScan(accelerator, kernels); kernels.SmallPrimesLastSeven is { } existingLastSeven &&
} kernels.SmallPrimesPow2LastOne is { } existingLastOnePow2 &&
kernels.SmallPrimesPow2LastSeven is { } existingLastSevenPow2)
{
return new ResiduePrimeViews(existingLastOne.View, existingLastSeven.View, existingLastOnePow2.View, existingLastSevenPow2.View);
}
if (kernelType.HasFlag(KernelType.Pow2ModKernelScan)) var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
{ var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven;
InitPow2ModKernelScan(accelerator, kernels); var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
} var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
if (kernelType.HasFlag(KernelType.IncrementalKernelScan)) var deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length);
{ deviceLastOne.View.CopyFromCPU(hostLastOne);
InitIncrementalKernelScan(accelerator, kernels); var deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length);
} deviceLastSeven.View.CopyFromCPU(hostLastSeven);
var deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
deviceLastOnePow2.View.CopyFromCPU(hostLastOnePow2);
var deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
deviceLastSevenPow2.View.CopyFromCPU(hostLastSevenPow2);
if (kernelType.HasFlag(KernelType.IncrementalOrderKernelScan)) kernels.SmallPrimesLastOne = deviceLastOne;
{ kernels.SmallPrimesLastSeven = deviceLastSeven;
IncrementalOrderKernelScan(accelerator, kernels); kernels.SmallPrimesPow2LastOne = deviceLastOnePow2;
} kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
if (kernelType.HasFlag(KernelType.Pow2ModOrderKernelScan)) return new ResiduePrimeViews(deviceLastOne.View, deviceLastSeven.View, deviceLastOnePow2.View, deviceLastSevenPow2.View);
{ }
InitPow2ModOrderKernelScan(accelerator, kernels); }
}
PreloadStaticTables(accelerator, kernels, stream, kernelType); public static SmallPrimeFactorTables EnsureSmallPrimeFactorTables(Accelerator accelerator)
{
var kernels = GetKernels(accelerator);
if (kernels.SmallPrimeFactorsPrimes is { } primeBuffer && kernels.SmallPrimeFactorsSquares is { } squareBuffer)
{
return new SmallPrimeFactorTables(primeBuffer, squareBuffer, (int)primeBuffer.Length);
}
pool[acceleratorIndex] = kernels; lock (kernels)
return kernels; {
if (kernels.SmallPrimeFactorsPrimes is { } existingPrimes && kernels.SmallPrimeFactorsSquares is { } existingSquares)
{
return new SmallPrimeFactorTables(existingPrimes, existingSquares, (int)existingPrimes.Length);
}
static void InitOrderKernelScan(Accelerator accelerator, KernelContainer kernels) var hostPrimes = PrimesGenerator.SmallPrimes;
{ var hostSquares = PrimesGenerator.SmallPrimesPow2;
InitOnce(ref kernels.Order, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>(OrderKernels.OrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded); var devicePrimes = accelerator.Allocate1D<uint>(hostPrimes.Length);
devicePrimes.View.CopyFromCPU(hostPrimes);
var deviceSquares = accelerator.Allocate1D<ulong>(hostSquares.Length);
deviceSquares.View.CopyFromCPU(hostSquares);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>>(); kernels.SmallPrimeFactorsPrimes = devicePrimes;
}); kernels.SmallPrimeFactorsSquares = deviceSquares;
}
static void InitPow2ModKernelScan(Accelerator accelerator, KernelContainer kernels) return new SmallPrimeFactorTables(devicePrimes, deviceSquares, hostPrimes.Length);
{ }
InitOnce(ref kernels.Pow2Mod, () => }
{
var loaded = accelerator.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>>(Pow2ModKernels.Pow2ModKernelScan);
var kernel = KernelUtil.GetKernel(loaded); public static SmallPrimeFactorScratch EnsureSmallPrimeFactorScratch(Accelerator accelerator, int slotCount)
{
var kernels = GetKernels(accelerator);
lock (kernels)
{
MemoryBuffer1D<ulong, Stride1D.Dense>? primeSlots = kernels.SmallPrimeFactorPrimeSlots;
if (primeSlots is null || primeSlots.Length < slotCount)
{
primeSlots?.Dispose();
primeSlots = accelerator.Allocate1D<ulong>(slotCount);
kernels.SmallPrimeFactorPrimeSlots = primeSlots;
}
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>>>(); MemoryBuffer1D<int, Stride1D.Dense>? exponentSlots = kernels.SmallPrimeFactorExponentSlots;
}); if (exponentSlots is null || exponentSlots.Length < slotCount)
} {
exponentSlots?.Dispose();
exponentSlots = accelerator.Allocate1D<int>(slotCount);
kernels.SmallPrimeFactorExponentSlots = exponentSlots;
}
static void InitIncrementalKernelScan(Accelerator accelerator, KernelContainer kernels) MemoryBuffer1D<int, Stride1D.Dense>? countSlot = kernels.SmallPrimeFactorCountSlot;
{ if (countSlot is null)
InitOnce(ref kernels.Incremental, () => {
{ countSlot = accelerator.Allocate1D<int>(1);
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalKernelScan); kernels.SmallPrimeFactorCountSlot = countSlot;
}
var kernel = KernelUtil.GetKernel(loaded); MemoryBuffer1D<ulong, Stride1D.Dense>? remainingSlot = kernels.SmallPrimeFactorRemainingSlot;
if (remainingSlot is null)
{
remainingSlot = accelerator.Allocate1D<ulong>(1);
kernels.SmallPrimeFactorRemainingSlot = remainingSlot;
}
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>>(); return new SmallPrimeFactorScratch(primeSlots!, exponentSlots!, countSlot!, remainingSlot!);
}); }
} }
static void IncrementalOrderKernelScan(Accelerator accelerator, KernelContainer kernels) public static GpuKernelLease GetKernel(bool useGpuOrder)
{ {
InitOnce(ref kernels.IncrementalOrder, () => var limiter = GpuPrimeWorkLimiter.Acquire();
{ var gpu = Rent();
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(IncrementalKernels.IncrementalOrderKernelScan); var accelerator = gpu.Accelerator;
var kernels = GetKernels(accelerator);
return GpuKernelLease.Rent(limiter, gpu, kernels);
}
var kernel = KernelUtil.GetKernel(loaded); /// <summary>
/// Runs a GPU action with an acquired accelerator and stream.
/// </summary>
/// <param name="action">Action to run with (Accelerator, Stream).</param>
public static void Run(Action<Accelerator, AcceleratorStream> action)
{
var lease = GetKernel(useGpuOrder: true);
var accelerator = lease.Accelerator;
var stream = accelerator.CreateStream();
action(accelerator, stream);
stream.Dispose();
lease.Dispose();
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, }
ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
static void InitPow2ModOrderKernelScan(Accelerator accelerator, KernelContainer kernels)
{
InitOnce(ref kernels.Pow2ModOrder, () =>
{
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>(Pow2ModKernels.Pow2ModOrderKernelScan);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>>();
});
}
}
internal static void PreloadStaticTables(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream, KernelType kernelType)
{
if (kernelType.HasFlag(KernelType.SmallCycles))
{
EnsureSmallCyclesOnDevice(accelerator, kernels, stream);
}
if (kernelType.HasFlag(KernelType.SmallPrimes))
{
EnsureSmallPrimesOnDevice(accelerator, kernels, stream);
}
}
public static ArrayView1D<ulong, Stride1D.Dense> GetSmallCyclesOnDevice(KernelContainer kernels) => kernels.SmallCycles!;
// Ensures the small cycles table is uploaded to the device for the given accelerator.
// Returns the ArrayView to pass into kernels (when kernels are extended to accept it).
private static void EnsureSmallCyclesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream)
{
if (kernels.SmallCycles is { })
{
return;
}
var host = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
MemoryBuffer1D<ulong, Stride1D.Dense>? device;
lock (accelerator)
{
device = accelerator.Allocate1D<ulong>(host.Length);
}
device.View.CopyFromCPU(stream, host);
kernels.SmallCycles = device;
}
public static ResiduePrimeViews GetSmallPrimesOnDevice(KernelContainer kernels) => new(
kernels.SmallPrimesLastOne!,
kernels.SmallPrimesLastSeven!,
kernels.SmallPrimesPow2LastOne!,
kernels.SmallPrimesPow2LastSeven!
);
private static void EnsureSmallPrimesOnDevice(Accelerator accelerator, KernelContainer kernels, AcceleratorStream stream)
{
if (kernels.SmallPrimesLastOne is { } &&
kernels.SmallPrimesLastSeven is { } &&
kernels.SmallPrimesPow2LastOne is { } &&
kernels.SmallPrimesPow2LastSeven is { })
{
return;
}
if (kernels.SmallPrimesLastOne is { } &&
kernels.SmallPrimesLastSeven is { } &&
kernels.SmallPrimesPow2LastOne is { } &&
kernels.SmallPrimesPow2LastSeven is { })
{
return;
}
var hostLastOne = PrimesGenerator.SmallPrimesLastOne;
var hostLastSeven = PrimesGenerator.SmallPrimesLastSeven;
var hostLastOnePow2 = PrimesGenerator.SmallPrimesPow2LastOne;
var hostLastSevenPow2 = PrimesGenerator.SmallPrimesPow2LastSeven;
MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastOne;
MemoryBuffer1D<uint, Stride1D.Dense>? deviceLastSeven;
MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastOnePow2;
MemoryBuffer1D<ulong, Stride1D.Dense>? deviceLastSevenPow2;
lock (accelerator)
{
deviceLastOne = accelerator.Allocate1D<uint>(hostLastOne.Length);
deviceLastSeven = accelerator.Allocate1D<uint>(hostLastSeven.Length);
deviceLastOnePow2 = accelerator.Allocate1D<ulong>(hostLastOnePow2.Length);
deviceLastSevenPow2 = accelerator.Allocate1D<ulong>(hostLastSevenPow2.Length);
}
deviceLastOne.View.CopyFromCPU(stream, hostLastOne);
deviceLastSeven.View.CopyFromCPU(stream, hostLastSeven);
deviceLastOnePow2.View.CopyFromCPU(stream, hostLastOnePow2);
deviceLastSevenPow2.View.CopyFromCPU(stream, hostLastSevenPow2);
kernels.SmallPrimesLastOne = deviceLastOne;
kernels.SmallPrimesLastSeven = deviceLastSeven;
kernels.SmallPrimesPow2LastOne = deviceLastOnePow2;
kernels.SmallPrimesPow2LastSeven = deviceLastSevenPow2;
}
/// <summary>
/// Runs a GPU action with an acquired accelerator and stream.
/// </summary>
/// <param name="action">Action to run with (Accelerator, Stream).</param>
public static void Run(Action<Accelerator, AcceleratorStream> action)
{
// GpuPrimeWorkLimiter.Acquire();
var acceleratorIndex = AcceleratorPool.Shared.Rent();
var accelerator = _accelerators[acceleratorIndex];
var stream = AcceleratorStreamPool.Rent(acceleratorIndex);
action(accelerator, stream);
stream.Synchronize();
stream.Dispose();
accelerator.Dispose();
// GpuPrimeWorkLimiter.Release();
}
} }
+32 -17
View File
@@ -2,29 +2,44 @@ namespace PerfectNumbers.Core.Gpu;
public static class GpuPrimeWorkLimiter public static class GpuPrimeWorkLimiter
{ {
private static SemaphoreSlim _semaphore = new(1, int.MaxValue); private static SemaphoreSlim _semaphore = new(1, int.MaxValue);
private static int _currentLimit = 1; private static int _currentLimit = 1;
public static void Acquire() { } // => _semaphore.Wait(); public static Lease Acquire()
public static void Release() { }// => _semaphore.Release();
public static void SetLimit(int value)
{ {
if (value <= 0) // TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve
// GPU jobs stop allocating Lease instances on every acquisition.
var sem = _semaphore;
// sem.Wait();
return new Lease(sem);
}
public static void SetLimit(int value)
{
if (value == _currentLimit)
{
return;
}
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
// the same semaphore pool without reallocating per adjustment.
_semaphore = new SemaphoreSlim(value, value);
_currentLimit = value;
}
public sealed class Lease
{
private readonly SemaphoreSlim _sem;
internal Lease(SemaphoreSlim sem)
{ {
value = 1; _sem = sem;
} }
if (value == _currentLimit) public void Dispose()
{ {
return; // _sem.Release();
} }
// TODO: Switch to a shared limiter implementation with GpuWorkLimiter so we can coordinate CPU/GPU prime work using
// the same semaphore pool without reallocating per adjustment.
var previous = _semaphore;
_semaphore = new SemaphoreSlim(value, value);
_currentLimit = value;
previous.Dispose();
} }
} }
+17 -47
View File
@@ -261,13 +261,13 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
public override readonly int GetHashCode() => HashCode.Combine(High, Low); public override readonly int GetHashCode() => HashCode.Combine(High, Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(in ReadOnlyGpuUInt128 other) => Add(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) => Add(other.High, other.Low); public void Add(GpuUInt128 other) => AddInternal(other.High, other.Low);
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(ulong otherHigh, ulong otherLow) private void AddInternal(ulong otherHigh, ulong otherLow)
{ {
ulong originalLow = Low; ulong originalLow = Low;
// We're reusing originalLow for carry to avoid an extra local. // We're reusing originalLow for carry to avoid an extra local.
@@ -316,7 +316,7 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
return; return;
} }
Add(valueHigh, valueLow); AddInternal(valueHigh, valueLow);
if (CompareTo(in modulus) >= 0) if (CompareTo(in modulus) >= 0)
{ {
Sub(in modulus); Sub(in modulus);
@@ -589,6 +589,9 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static GpuUInt128 BinaryGcd(GpuUInt128 u, GpuUInt128 v) public static GpuUInt128 BinaryGcd(GpuUInt128 u, GpuUInt128 v)
{ {
// TODO: Replace this scalar binary GCD with the branchless reduction from
// GpuUInt128BinaryGcdBenchmarks so CPU fallbacks stay aligned with the GPU kernel
// performance when resolving large divisor residues.
if (u.IsZero) if (u.IsZero)
{ {
return v; return v;
@@ -599,50 +602,26 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
return u; return u;
} }
ulong combinedLow = u.Low | v.Low; int shift = TrailingZeroCount(new GpuUInt128(u.High | v.High, u.Low | v.Low));
int shift;
if (combinedLow != 0UL)
{
shift = BitOperations.TrailingZeroCount(combinedLow);
}
else
{
ulong combinedHigh = u.High | v.High;
shift = 64 + BitOperations.TrailingZeroCount(combinedHigh);
}
int zu = TrailingZeroCount(u); int zu = TrailingZeroCount(u);
u >>= zu; u >>= zu;
GpuUInt128 currentV = v; do
while (true)
{ {
int zv = TrailingZeroCount(currentV); int zv = TrailingZeroCount(v);
currentV >>= zv; v >>= zv;
if (u > v)
bool swap = u > currentV;
GpuUInt128 min = ConditionalSelect(u, currentV, swap);
GpuUInt128 max = ConditionalSelect(currentV, u, swap);
max.Sub(min);
if (max.IsZero)
{ {
return min << shift; (u, v) = (v, u);
} }
u = min; v -= u;
currentV = max;
} }
while (!v.IsZero);
return u << shift;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static GpuUInt128 ConditionalSelect(in GpuUInt128 left, in GpuUInt128 right, bool useRight)
{
ulong mask = useRight ? ulong.MaxValue : 0UL;
return new GpuUInt128(
left.High ^ ((left.High ^ right.High) & mask),
left.Low ^ ((left.Low ^ right.Low) & mask));
}
public void Sub(ulong value) public void Sub(ulong value)
{ {
@@ -1550,15 +1529,6 @@ public struct GpuUInt128 : IComparable<GpuUInt128>, IEquatable<GpuUInt128>
high = hi; high = hi;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Pow2()
{
ulong operand = Low;
Low = operand * operand;
ulong highProduct = operand * High;
High = highProduct + MulHigh(operand, operand);
}
} }
// TODO: Check if the TODO below is still relevant. // TODO: Check if the TODO below is still relevant.
@@ -0,0 +1,97 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal readonly struct HeuristicGpuDivisorTables
{
public static readonly HeuristicGpuDivisorTables Shared = default;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupADivisors;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> GroupBDivisorsEnding9;
public HeuristicGpuDivisorTables(
ArrayView1D<ulong, Stride1D.Dense> groupADivisors,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> groupBDivisorsEnding9)
{
GroupADivisors = groupADivisors;
GroupBDivisorsEnding1 = groupBDivisorsEnding1;
GroupBDivisorsEnding7 = groupBDivisorsEnding7;
GroupBDivisorsEnding9 = groupBDivisorsEnding9;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectGroupB(byte ending)
{
return ending switch
{
1 => GroupBDivisorsEnding1,
7 => GroupBDivisorsEnding7,
9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectDivisors(HeuristicGpuDivisorTableKind kind, byte ending)
{
return kind switch
{
HeuristicGpuDivisorTableKind.GroupA => GroupADivisors,
HeuristicGpuDivisorTableKind.GroupBEnding1 => GroupBDivisorsEnding1,
HeuristicGpuDivisorTableKind.GroupBEnding7 => GroupBDivisorsEnding7,
HeuristicGpuDivisorTableKind.GroupBEnding9 => GroupBDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
internal static void InitializeShared(in HeuristicGpuDivisorTables tables)
{
Unsafe.AsRef(in Shared) = tables;
}
}
internal readonly struct HeuristicGpuCombinedDivisorTables
{
public static readonly HeuristicGpuCombinedDivisorTables Shared = default;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding1;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding3;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding7;
public readonly ArrayView1D<ulong, Stride1D.Dense> CombinedDivisorsEnding9;
public HeuristicGpuCombinedDivisorTables(
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding1,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding3,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding7,
ArrayView1D<ulong, Stride1D.Dense> combinedDivisorsEnding9)
{
CombinedDivisorsEnding1 = combinedDivisorsEnding1;
CombinedDivisorsEnding3 = combinedDivisorsEnding3;
CombinedDivisorsEnding7 = combinedDivisorsEnding7;
CombinedDivisorsEnding9 = combinedDivisorsEnding9;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ArrayView1D<ulong, Stride1D.Dense> SelectCombined(byte ending)
{
return ending switch
{
1 => CombinedDivisorsEnding1,
3 => CombinedDivisorsEnding3,
7 => CombinedDivisorsEnding7,
9 => CombinedDivisorsEnding9,
_ => ArrayView1D<ulong, Stride1D.Dense>.Empty,
};
}
internal static void InitializeShared(in HeuristicGpuCombinedDivisorTables tables)
{
Unsafe.AsRef(in Shared) = tables;
}
}
@@ -1,58 +0,0 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
public sealed class KernelContainer
{
// Serializes first-time initialization of kernels/buffers per accelerator.
public Action<AcceleratorStream, Index1D, ulong, ulong, ArrayView<GpuUInt128>, ArrayView<ulong>>? Order;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ulong, ulong, ulong, ulong, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>>? Incremental;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>,
ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>,
ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2Mod;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? IncrementalOrder;
public Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong,
ResidueAutomatonArgs, ArrayView<int>, ArrayView1D<ulong, Stride1D.Dense>>? Pow2ModOrder;
public Kernel? SmallPrimeFactor;
public Action<AcceleratorStream, Index1D, ulong, ArrayView1D<ulong, Stride1D.Dense>, int, MontgomeryDivisorData, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>? SpecialMax;
// Optional device buffer with small divisor cycles (<= 4M). Index = divisor, value = cycle length.
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallCycles;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastOne;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimesLastSeven;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastOne;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimesPow2LastSeven;
public MemoryBuffer1D<uint, Stride1D.Dense>? SmallPrimeFactorsPrimes;
public MemoryBuffer1D<ulong, Stride1D.Dense>? SmallPrimeFactorsSquares;
public void Dispose()
{
// Order = null;
// Incremental = null;
// Pow2Mod = null;
// IncrementalOrder = null;
// Pow2ModOrder = null;
// SmallPrimeFactor = null;
// SpecialMax = null;
// SmallCycles?.Dispose();
// SmallPrimesLastOne?.Dispose();
// SmallPrimesLastSeven?.Dispose();
// SmallPrimesPow2LastOne?.Dispose();
// SmallPrimesPow2LastSeven?.Dispose();
// SmallPrimeFactorsPrimes?.Dispose();
// SmallPrimeFactorsSquares?.Dispose();
}
}
-14
View File
@@ -1,14 +0,0 @@
namespace PerfectNumbers.Core.Gpu;
[Flags]
public enum KernelType
{
None,
SmallCycles,
SmallPrimes,
Pow2ModKernelScan,
OrderKernelScan,
IncrementalKernelScan,
IncrementalOrderKernelScan,
Pow2ModOrderKernelScan
}
@@ -1,4 +1,7 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,5 +1,6 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,4 +1,5 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,5 +1,7 @@
using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
using UInt128 = System.UInt128; using UInt128 = System.UInt128;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -303,7 +305,7 @@ internal static class LucasLehmerKernels
states[idx] = s; states[idx] = s;
} }
public static void Kernel(Index1D _, ulong exponent, GpuUInt128 modulus, ArrayView<GpuUInt128> state) public static void Kernel(Index1D index, ulong exponent, GpuUInt128 modulus, ArrayView<GpuUInt128> state)
{ {
// Lucas–Lehmer iteration in the field GF(2^p-1). // Lucas–Lehmer iteration in the field GF(2^p-1).
// For p < 128, use a Mersenne-specific squaring + reduction to avoid // For p < 128, use a Mersenne-specific squaring + reduction to avoid
@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -33,7 +32,7 @@ internal static class NttBarrettKernels
} }
// Barrett-based squaring: v = v^2 mod n for 128-bit n // Barrett-based squaring: v = v^2 mod n for 128-bit n
public static void SquareBarrett128Kernel(Index1D index, AcceleratorStream stream, ArrayView<GpuUInt128> data, ulong modHigh, ulong modLow, ulong muHigh, ulong muLow) public static void SquareBarrett128Kernel(Index1D index, ArrayView<GpuUInt128> data, ulong modHigh, ulong modLow, ulong muHigh, ulong muLow)
{ {
var v = data[index]; var v = data[index];
Mul128Full(v.High, v.Low, v.High, v.Low, out var z3, out var z2, out var z1, out var z0); Mul128Full(v.High, v.Low, v.High, v.Low, out var z3, out var z2, out var z1, out var z0);
@@ -1,6 +1,5 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -44,7 +43,7 @@ internal static class NttMontgomeryKernels
data[index] = new GpuUInt128(0UL, v); data[index] = new GpuUInt128(0UL, v);
} }
public static void SquareMont64Kernel(Index1D index, AcceleratorStream stream, ArrayView<GpuUInt128> data, ulong modulus, ulong nPrime) public static void SquareMont64Kernel(Index1D index, ArrayView<GpuUInt128> data, ulong modulus, ulong nPrime)
{ {
ulong aR = data[index].Low; ulong aR = data[index].Low;
ulong v = MontMul64(aR, aR, modulus, nPrime); ulong v = MontMul64(aR, aR, modulus, nPrime);
@@ -1,4 +1,5 @@
using ILGPU; using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,4 +1,5 @@
using ILGPU; using ILGPU;
using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
@@ -1,80 +1,26 @@
using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Gpu.Kernels; namespace PerfectNumbers.Core.Gpu;
internal static class Pow2MontgomeryKernels internal static class Pow2MontgomeryKernels
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static void Pow2MontgomeryKernelKeepMontgomery(
private static GpuUInt128 AdvancePolynomialGpu(GpuUInt128 x, ulong c, ulong modulus) Index1D index,
{ ArrayView1D<ulong, Stride1D.Dense> exponents,
x.Pow2(); MontgomeryDivisorData divisor,
x.AddMod(c, modulus); ArrayView1D<ulong, Stride1D.Dense> results)
return x; {
} results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[index]);
}
public static void Pow2MontgomeryKernelKeepMontgomery( public static void Pow2MontgomeryKernelConvertToStandard(
Index1D index, Index1D index,
ArrayView1D<ulong, Stride1D.Dense> exponents, ArrayView1D<ulong, Stride1D.Dense> exponents,
MontgomeryDivisorData divisor, MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> results) ArrayView1D<ulong, Stride1D.Dense> results)
{ {
results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuKeepMontgomery(divisor, exponents[0]); results[index] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[index]);
} }
public static void Pow2MontgomeryKernelConvertToStandard(
Index1D index,
ArrayView1D<ulong, Stride1D.Dense> exponents,
MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> results)
{
results[0] = ULongExtensions.Pow2MontgomeryModWindowedGpuConvertToStandard(divisor, exponents[0]);
}
public static void TryPollardRhoKernel(ulong n, int limit, ArrayView1D<ulong, Stride1D.Dense> randomState, ArrayView1D<byte, Stride1D.Dense> factored, ArrayView1D<ulong, Stride1D.Dense> factor)
{
factored[0] = 0;
if ((n & 1UL) == 0UL)
{
factor[0] = 2UL;
factored[0] = 1;
return;
}
int attempt = 0;
GpuUInt128 nGpuUInt128 = (GpuUInt128)n;
DeterministicRandomGpu random = new(randomState[0]);
while (attempt < limit)
{
ulong c = (random.NextUInt64() % (n - 1UL)) + 1UL;
GpuUInt128 x = (random.NextUInt64() % (n - 2UL)) + 2UL;
GpuUInt128 y = x;
GpuUInt128 d = GpuUInt128.One;
while (d.CompareTo(GpuUInt128.One) == 0)
{
x = AdvancePolynomialGpu(x, c, modulus: n);
y = AdvancePolynomialGpu(y, c, n);
y = AdvancePolynomialGpu(y, c, n);
GpuUInt128 diff = x.CompareTo(y) > 0 ? x - y : y - x;
d = GpuUInt128.BinaryGcd(diff, nGpuUInt128);
}
if (d != n)
{
if (d.High == 0)
{
factor[0] = d.Low;
factored[0] = 1;
return;
}
}
attempt++;
}
randomState[0] = random.State;
}
} }
File diff suppressed because it is too large. Load diff
@@ -1,79 +0,0 @@
using ILGPU;
using ILGPU.Runtime;
namespace PerfectNumbers.Core.Gpu;
internal static partial class PrimeOrderGpuHeuristics
{
internal static void EvaluateSpecialMaxCandidatesKernel(
Index1D index,
ulong phi,
ArrayView1D<ulong, Stride1D.Dense> factors,
int factorCount,
MontgomeryDivisorData divisor,
ArrayView1D<ulong, Stride1D.Dense> candidates,
ArrayView1D<ulong, Stride1D.Dense> resultOut)
{
if (index != 0)
{
return;
}
int actual = 0;
for (int i = 0; i < factorCount; i++)
{
ulong factor = factors[i];
if (factor <= 1UL)
{
continue;
}
ulong reduced = phi / factor;
if (reduced == 0UL)
{
continue;
}
candidates[actual] = reduced;
actual++;
}
if (actual == 0)
{
resultOut[0] = 1;
return;
}
for (int i = 1; i < actual; i++)
{
ulong current = candidates[i];
int insert = i - 1;
while (insert >= 0 && candidates[insert] > current)
{
candidates[insert + 1] = candidates[insert];
insert--;
}
candidates[insert + 1] = current;
}
var divisorPartial = new GpuDivisorPartialData(divisor.Modulus);
var stepper = new ExponentRemainderStepperGpu(divisorPartial);
if (stepper.InitializeIsUnityGpu(candidates[0]))
{
Atomic.Exchange(ref resultOut[0], 0);
return;
}
for (int i = 1; i < actual; i++)
{
if (stepper.ComputeNextIsUnityGpu(candidates[i]))
{
Atomic.Exchange(ref resultOut[0], 0);
return;
}
}
Atomic.Exchange(ref resultOut[0], 1);
}
}
@@ -2,39 +2,41 @@ using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Algorithms; using ILGPU.Algorithms;
using ILGPU.Runtime; using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu; namespace PerfectNumbers.Core.Gpu;
public enum HeuristicGpuDivisorTableKind : byte internal enum HeuristicGpuDivisorTableKind : byte
{ {
GroupA = 0, GroupA = 0,
GroupBEnding1 = 1, GroupBEnding1 = 1,
GroupBEnding7 = 7, GroupBEnding7 = 7,
GroupBEnding9 = 9, GroupBEnding9 = 9,
Combined = 255,
} }
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( public static void SmallPrimeSieveKernel(
Index1D index, Index1D index,
ArrayView<ulong> numbers, ArrayView<ulong> numbers,
ArrayView<uint> smallPrimesLastOne, ArrayView<uint> smallPrimes,
ArrayView<uint> smallPrimesLastSeven, ArrayView<uint> smallPrimesLastOne,
ArrayView<uint> smallPrimesLastThree, ArrayView<uint> smallPrimesLastSeven,
ArrayView<uint> smallPrimesLastNine, ArrayView<uint> smallPrimesLastThree,
ArrayView<ulong> smallPrimesPow2LastOne, ArrayView<uint> smallPrimesLastNine,
ArrayView<ulong> smallPrimesPow2LastSeven, ArrayView<ulong> smallPrimesPow2,
ArrayView<ulong> smallPrimesPow2LastThree, ArrayView<ulong> smallPrimesPow2LastOne,
ArrayView<ulong> smallPrimesPow2LastNine, ArrayView<ulong> smallPrimesPow2LastSeven,
ArrayView<byte> results) ArrayView<ulong> smallPrimesPow2LastThree,
ArrayView<ulong> smallPrimesPow2LastNine,
ArrayView<byte> results)
{ {
ulong n = numbers[index]; ulong n = numbers[index];
byte result = 1; byte result = 1;
ArrayView<uint> primes; ArrayView<uint> primes = smallPrimes;
ArrayView<ulong> primeSquares; ArrayView<ulong> primeSquares = smallPrimesPow2;
ulong lastDigit = n % 10UL; ulong lastDigit = n % 10UL;
switch (lastDigit) switch (lastDigit)
{ {
@@ -53,9 +55,7 @@ internal static class PrimeTesterKernels
case 9UL: case 9UL:
primes = smallPrimesLastNine; primes = smallPrimesLastNine;
primeSquares = smallPrimesPow2LastNine; primeSquares = smallPrimesPow2LastNine;
break; break;
default:
return;
} }
int length = (int)primes.Length; int length = (int)primes.Length;
@@ -78,66 +78,29 @@ internal static class PrimeTesterKernels
results[index] = result; results[index] = result;
} }
public static void HeuristicTrialCombinedDivisionKernel( public static void HeuristicTrialDivisionKernel(
Index1D index,
byte nMod10,
ArrayView<int> resultFlag,
ulong n,
ulong maxDivisorSquare,
HeuristicCombinedGpuViews tables)
{
// byte nMod10 = (byte)(n % 10UL);
ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(nMod10);
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(nMod10);
int divisorLength = (int)divisorSquares.Length;
int threadIndex = index;
if (threadIndex >= divisorLength)
{
return;
}
ulong divisorSquare = divisorSquares[threadIndex];
if (divisorSquare > maxDivisorSquare)
{
return;
}
ulong divisor = divisors[threadIndex];
divisor = n % divisor;
if (divisor == 0UL)
{
resultFlag[0] = 1;
}
}
public static void HeuristicTrialGroupABDivisionKernel(
Index1D index, Index1D index,
ArrayView<int> resultFlag, ArrayView<int> resultFlag,
ulong n, ulong n,
ulong maxDivisorSquare, ulong maxDivisor,
HeuristicGpuDivisorTableKind tableKind, HeuristicGpuDivisorTableKind tableKind,
HeuristicGroupABGpuViews tables) HeuristicGpuDivisorTables tables,
HeuristicGpuCombinedDivisorTables combinedTables)
{ {
ArrayView1D<ulong, Stride1D.Dense> divisors = tables.SelectDivisors(tableKind); byte nMod10 = (byte)(n % 10UL);
ArrayView1D<ulong, Stride1D.Dense> divisorSquares = tables.SelectDivisorSquares(tableKind); ArrayView1D<ulong, Stride1D.Dense> divisors = tableKind == HeuristicGpuDivisorTableKind.Combined
? combinedTables.SelectCombined(nMod10)
: tables.SelectDivisors(tableKind, nMod10);
int divisorLength = (int)divisorSquares.Length; int divisorLength = (int)divisors.Length;
int threadIndex = index; int threadIndex = index;
if (threadIndex >= divisorLength) bool skipThread = threadIndex >= divisorLength;
{ ulong divisor = skipThread ? 1UL : divisors[threadIndex];
return; skipThread = skipThread | (divisor > maxDivisor);
}
ulong divisorSquare = divisorSquares[threadIndex]; ulong remainder = skipThread ? 1UL : n % divisor;
if (divisorSquare > maxDivisorSquare) bool hasFactor = !skipThread & (remainder == 0UL);
{ if (hasFactor)
return;
}
ulong divisor = divisors[threadIndex];
divisor = n % divisor;
if (divisor == 0UL)
{ {
resultFlag[0] = 1; resultFlag[0] = 1;
} }
@@ -153,37 +116,35 @@ internal static class PrimeTesterKernels
private static ulong BinaryGcdGpu(ulong u, ulong v) private static ulong BinaryGcdGpu(ulong u, ulong v)
{ {
// TODO: Replace this inline GPU binary GCD with the kernel extracted from
// GpuUInt128BinaryGcdBenchmarks via GpuKernelPool so device callers reuse the
// fully unrolled ladder instead of this branchy fallback.
bool uIsZero = u == 0UL; bool uIsZero = u == 0UL;
bool vIsZero = v == 0UL; bool vIsZero = v == 0UL;
bool eitherZero = uIsZero | vIsZero; bool eitherZero = uIsZero | vIsZero;
ulong zeroResult = uIsZero ? v : u; ulong zeroResult = uIsZero ? v : u;
ulong combined = (u | v) | 1UL; ulong combined = u | v | 1UL;
int shift = XMath.TrailingZeroCount(combined); int shift = XMath.TrailingZeroCount(combined);
ulong currentU = u >> XMath.TrailingZeroCount(u | 1UL);
ulong currentV = v >> XMath.TrailingZeroCount(v | 1UL);
while (true) ulong normalizedU = u >> XMath.TrailingZeroCount(u | 1UL);
ulong normalizedV = v >> XMath.TrailingZeroCount(v | 1UL);
ulong currentU = normalizedU;
ulong currentV = normalizedV;
bool loopCondition = currentV != 0UL;
while (loopCondition)
{ {
currentV >>= XMath.TrailingZeroCount(currentV); currentV >>= XMath.TrailingZeroCount(currentV);
bool swap = currentU > currentV; ulong minValue = XMath.Min(currentU, currentV);
ulong minValue = ConditionalSelect(currentU, currentV, swap); ulong maxValue = XMath.Max(currentU, currentV);
ulong maxValue = ConditionalSelect(currentV, currentU, swap);
currentV = maxValue - minValue;
currentU = minValue; currentU = minValue;
if (currentV == 0UL) currentV = maxValue - minValue;
{ loopCondition = currentV != 0UL;
ulong gcd = currentU << shift;
return eitherZero ? zeroResult : gcd;
}
} }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] ulong gcd = currentU << shift;
private static ulong ConditionalSelect(ulong left, ulong right, bool useRight) return eitherZero ? zeroResult : gcd;
{
ulong mask = useRight ? ulong.MaxValue : 0UL;
return left ^ ((left ^ right) & mask);
} }
} }
File diff suppressed because it is too large. Load diff
@@ -1,3 +1,4 @@
using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -8,157 +9,127 @@ namespace PerfectNumbers.Core.Gpu;
/// </summary> /// </summary>
public sealed class MersenneNumberDivisorGpuTester public sealed class MersenneNumberDivisorGpuTester
{ {
[ThreadStatic] private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>> _kernelCache = new();
private static Dictionary<Accelerator, Queue<MemoryBuffer1D<byte, Stride1D.Dense>>>? _resultBuffers; private readonly ConcurrentDictionary<Accelerator, MemoryBuffer1D<byte, Stride1D.Dense>> _resultBuffers = new();
private static Queue<MemoryBuffer1D<byte, Stride1D.Dense>> GetQueue(Accelerator accelerator) private Action<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetKernel(Accelerator accelerator) =>
{ _kernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel));
var pool = _resultBuffers ??= [];
if (!pool.TryGetValue(accelerator, out var bufferQueue))
{
bufferQueue = [];
pool[accelerator] = bufferQueue;
}
return bufferQueue; public static void BuildDivisorCandidates()
} {
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
ulong cycle;
int count = 0, i, snapshotLength = snapshot.Length;
for (i = 3; i < snapshotLength; i += 2)
{
cycle = snapshot[i];
if (cycle == 0U)
{
continue;
}
private static Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>> GetDivisorKernel(Accelerator accelerator) list[count++] = ((ulong)i, (uint)cycle);
{ }
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>(DivisorKernels.Kernel);
var kernel = KernelUtil.GetKernel(loaded); _divisorCandidates = count == 0 ? [] : list[..count];
}
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ReadOnlyGpuUInt128, ArrayView<byte>>>();
}
public static void BuildDivisorCandidates()
{
ulong[] snapshot = MersenneDivisorCycles.Shared.ExportSmallCyclesSnapshot();
(ulong divisor, uint cycle)[] list = new (ulong divisor, uint cycle)[snapshot.Length / 2];
ulong cycle;
int count = 0, i, snapshotLength = snapshot.Length;
for (i = 3; i < snapshotLength; i += 2)
{
cycle = snapshot[i];
if (cycle == 0U)
{
continue;
}
list[count++] = ((ulong)i, (uint)cycle);
}
_divisorCandidates = count == 0 ? [] : list[..count];
}
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
{
// var accelerator = SharedGpuContext.CreateAccelerator();
var acceleratorIndex = AcceleratorPool.Shared.Rent();
var accelerator = _accelerators[acceleratorIndex];
var stream = accelerator.CreateStream();
Queue<MemoryBuffer1D<byte, Stride1D.Dense>> resultBuffers = GetQueue(accelerator);
if (!resultBuffers.TryDequeue(out var resultBuffer))
{
// lock(accelerator)
{
resultBuffer = accelerator.Allocate1D<byte>(1);
}
}
public bool IsDivisible(ulong exponent, in ReadOnlyGpuUInt128 divisor)
{
var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator;
var kernel = GetKernel(accelerator);
var resultBuffer = _resultBuffers.GetOrAdd(accelerator, acc => acc.Allocate1D<byte>(1));
// There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0] // There is no point in clearing this buffer. We always override item [0] and never use it beyond item [0]
// resultBuffer.MemSetToZero(stream); // resultBuffer.MemSetToZero();
var divisorKernel = GetDivisorKernel(accelerator); kernel(1, exponent, divisor, resultBuffer.View);
divisorKernel(stream, 1, exponent, divisor, resultBuffer.View); accelerator.Synchronize();
byte result = 0; Span<byte> result = stackalloc byte[1];
resultBuffer.View.CopyToCPU(stream, ref result, 1); resultBuffer.View.CopyToCPU(ref result[0], 1);
stream.Synchronize(); bool divisible = result[0] != 0;
bool divisible = result != 0; result[0] = 0;
resultBuffers.Enqueue(resultBuffer); resultBuffer.View.CopyFromCPU(ref result[0], 1);
stream.Dispose(); accelerator.Synchronize();
// AcceleratorPool.Shared.Return(accelerator); gpu.Dispose();
// accelerator.Dispose(); return divisible;
return divisible; }
}
private static (ulong divisor, uint cycle)[]? _divisorCandidates = []; private static (ulong divisor, uint cycle)[]? _divisorCandidates = Array.Empty<(ulong divisor, uint cycle)>();
public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted) public bool IsPrime(ulong p, UInt128 d, ulong divisorCyclesSearchLimit, out bool divisorsExhausted)
{ {
ReadOnlyGpuUInt128 readOnlyDivisor; ReadOnlyGpuUInt128 readOnlyDivisor;
if (d != UInt128.Zero) if (d != UInt128.Zero)
{ {
readOnlyDivisor = new ReadOnlyGpuUInt128(d); readOnlyDivisor = new ReadOnlyGpuUInt128(d);
if (IsDivisible(p, in readOnlyDivisor)) if (IsDivisible(p, in readOnlyDivisor))
{ {
divisorsExhausted = true; divisorsExhausted = true;
return false; return false;
} }
divisorsExhausted = false; divisorsExhausted = false;
return true; return true;
} }
if (_divisorCandidates is { Length: > 0 } candidates) if (_divisorCandidates is { Length: > 0 } candidates)
{ {
int candidateCount = candidates.Length; int candidateCount = candidates.Length;
for (int index = 0; index < candidateCount; index++) for (int index = 0; index < candidateCount; index++)
{ {
(ulong candidateDivisor, uint cycle) = candidates[index]; (ulong candidateDivisor, uint cycle) = candidates[index];
if (p % cycle != 0UL) if (p % cycle != 0UL)
{ {
continue; continue;
} }
readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors. readOnlyDivisor = new ReadOnlyGpuUInt128(candidateDivisor); // Reusing readOnlyDivisor for candidate divisors.
if (IsDivisible(p, in readOnlyDivisor)) if (IsDivisible(p, in readOnlyDivisor))
{ {
divisorsExhausted = true; divisorsExhausted = true;
return false; return false;
} }
} }
} }
UInt128 kMul2; UInt128 kMul2;
var divisorCycles = MersenneDivisorCycles.Shared; var divisorCycles = MersenneDivisorCycles.Shared;
UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1); UInt128 maxK2 = UInt128.MaxValue / ((UInt128)p << 1);
ulong limit = divisorCyclesSearchLimit; ulong limit = divisorCyclesSearchLimit;
if ((UInt128)limit > maxK2) if ((UInt128)limit > maxK2)
{ {
limit = (ulong)maxK2; limit = (ulong)maxK2;
} }
for (ulong k2 = 1UL; k2 <= limit; k2++) for (ulong k2 = 1UL; k2 <= limit; k2++)
{ {
kMul2 = (UInt128)k2 << 1; kMul2 = (UInt128)k2 << 1;
UInt128 candidate = checked(kMul2 * p); UInt128 candidate = checked(kMul2 * p);
d = checked(candidate + UInt128.One); d = checked(candidate + UInt128.One);
if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One) if (p < 64UL && d == ((UInt128)1 << (int)p) - UInt128.One)
{ {
continue; continue;
} }
UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d); UInt128 cycle128 = MersenneDivisorCycles.GetCycle(d);
if ((UInt128)p % cycle128 != UInt128.Zero) if ((UInt128)p % cycle128 != UInt128.Zero)
{ {
continue; continue;
} }
readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors. readOnlyDivisor = new ReadOnlyGpuUInt128(d); // Reusing readOnlyDivisor for generated divisors.
if (IsDivisible(p, in readOnlyDivisor)) if (IsDivisible(p, in readOnlyDivisor))
{ {
divisorsExhausted = true; divisorsExhausted = true;
return false; return false;
} }
} }
divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2; divisorsExhausted = (UInt128)divisorCyclesSearchLimit >= maxK2;
return true; return true;
} }
} }
@@ -12,41 +12,36 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder; private readonly bool _useGpuOrder = useGpuOrder;
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{ {
throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder})."); throw new NotImplementedException($"GPU incremental scanning requires the device cycle heuristics implementation (kernel: {_kernelType}, GPU order: {_useGpuOrder}).");
// GpuPrimeWorkLimiter.Acquire(); var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = gpuLease.Accelerator;
var accelerator = _accelerators[acceleratorIndex]; var stream = gpuLease.Stream;
var stream = accelerator.CreateStream();
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy
UInt128 kStart = 1UL; UInt128 kStart = 1UL;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL; ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModKernelScan | KernelType.IncrementalKernelScan); var pow2Kernel = gpuLease.Pow2ModKernel;
var pow2Kernel = kernels.Pow2Mod!; var incKernel = gpuLease.IncrementalKernel;
var incKernel = kernels.Incremental!;
ulong step10 = (exponent.Mod10() << 1).Mod10(); ulong step10 = (exponent.Mod10() << 1).Mod10();
ulong step8 = ((exponent & 7UL) << 1) & 7UL; ulong step8 = ((exponent & 7UL) << 1) & 7UL;
ulong step3 = ((exponent % 3UL) << 1) % 3UL; ulong step3 = ((exponent % 3UL) << 1) % 3UL;
ulong step5 = ((exponent % 5UL) << 1) % 5UL; ulong step5 = ((exponent % 5UL) << 1) % 5UL;
GpuUInt128 twoPGpu = (GpuUInt128)twoP; GpuUInt128 twoPGpu = (GpuUInt128)twoP;
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
ResiduePrimeViews primeViews = default; ResiduePrimeViews primeViews = default;
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
primeViews = GpuKernelPool.GetSmallPrimesOnDevice(kernels); primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator);
} }
var orderBuffer = accelerator.Allocate1D<ulong>(batchSize); var orderBuffer = accelerator.Allocate1D<ulong>(batchSize);
// Avoid giant stack allocations that can trigger StackOverflow when batchSize is large. // Avoid giant stack allocations that can trigger StackOverflow when batchSize is large.
// Rent a reusable array from the shared pool instead. // Rent a reusable array from the shared pool instead.
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; ulong[] orderArray = ArrayPool<ulong>.Shared.Rent(batchSize);
ulong[] orderArray = ulongPool.Rent(batchSize);
UInt128 remaining; UInt128 remaining;
int currentSize; int currentSize;
int i; int i;
@@ -97,8 +92,8 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
smallCyclesView); smallCyclesView);
} }
orderBuffer.View.CopyToCPU(stream, ref MemoryMarshal.GetReference(orders), currentSize);
stream.Synchronize(); stream.Synchronize();
orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize);
if (_kernelType == GpuKernelType.Pow2Mod) if (_kernelType == GpuKernelType.Pow2Mod)
{ {
for (i = 0; i < currentSize; i++) for (i = 0; i < currentSize; i++)
@@ -125,9 +120,8 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u
kStart += (UInt128)currentSize; kStart += (UInt128)currentSize;
} }
stream.Dispose(); ArrayPool<ulong>.Shared.Return(orderArray);
ulongPool.Return(orderArray); orderBuffer.Dispose();
orderBuffer.Dispose(); gpuLease.Dispose();
// GpuPrimeWorkLimiter.Release();
} }
} }
@@ -8,125 +8,32 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberLucasLehmerGpuTester public class MersenneNumberLucasLehmerGpuTester
{ {
[ThreadStatic] private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>> KernelCache = new();
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>? _lucasLehmerKernel; private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> AddSmallKernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> SubSmallKernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ulong, ArrayView<GpuUInt128>>> ReduceKernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>> IsZeroKernelCache = new();
private readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>> BatchKernelCache = new();
[ThreadStatic] private Action<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetKernel(Accelerator accelerator)
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _addSmallKernel; {
return KernelCache.GetOrAdd(accelerator, accel => accel.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel));
}
[ThreadStatic] private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator) =>
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _subSmallKernel; AddSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel));
[ThreadStatic] private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator) =>
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>? _reduceKernel; SubSmallKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.SubtractSmallKernel));
[ThreadStatic] private Action<Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator) =>
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>>? _isZeroKernel; ReduceKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.ReduceModMersenneKernel));
[ThreadStatic] private Action<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator) =>
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>>? _lucasLehmerBatchKernel; IsZeroKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(LucasLehmerKernels.IsZeroKernel));
private static Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>> GetLucasLehmerKernel(Accelerator accelerator) private Action<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>> GetBatchKernel(Accelerator accelerator) =>
{ BatchKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>(LucasLehmerKernels.KernelBatch));
var pool = _lucasLehmerKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>(LucasLehmerKernels.Kernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, GpuUInt128, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetAddSmallKernel(Accelerator accelerator)
{
var pool = _addSmallKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.AddSmallKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetSubSmallKernel(Accelerator accelerator)
{
var pool = _subSmallKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.SubtractSmallKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>> GetReduceKernel(Accelerator accelerator)
{
var pool = _reduceKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ulong, ArrayView<GpuUInt128>>(LucasLehmerKernels.ReduceModMersenneKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ulong, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>> GetIsZeroKernel(Accelerator accelerator)
{
var pool = _isZeroKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>(LucasLehmerKernels.IsZeroKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<byte>>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>> GetLucasLehmerBatchKernel(Accelerator accelerator)
{
var pool = _lucasLehmerBatchKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>(LucasLehmerKernels.KernelBatch);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<ulong>, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>>>();
pool[accelerator] = cached;
return cached;
}
// Configurable LL slice size to keep kernels short. Default 32. // Configurable LL slice size to keep kernels short. Default 32.
public int SliceSize = 32; public int SliceSize = 32;
@@ -147,11 +54,13 @@ public class MersenneNumberLucasLehmerGpuTester
return false; return false;
} }
var limiter = GpuPrimeWorkLimiter.Acquire();
bool result; bool result;
if (exponent >= 128UL) if (exponent >= 128UL)
{ {
if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot)) if (!TryGetNttParameters(exponent, out var nttMod, out var primitiveRoot))
{ {
limiter.Dispose();
throw new NotSupportedException("NTT parameters for the given exponent are not available."); throw new NotSupportedException("NTT parameters for the given exponent are not available.");
} }
@@ -159,22 +68,19 @@ public class MersenneNumberLucasLehmerGpuTester
} }
else else
{ {
// GpuPrimeWorkLimiter.Acquire(); var gpu = GpuContextPool.Rent();
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var accelerator = gpu.Accelerator;
var accelerator = _accelerators[acceleratorIndex]; var kernel = GetKernel(accelerator);
var stream = accelerator.CreateStream();
var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch. var modulus = new GpuUInt128(((UInt128)1 << (int)exponent) - 1UL); // TODO: Cache these Mersenne moduli per exponent so LL GPU runs skip rebuilding them every launch.
var buffer = accelerator.Allocate1D<GpuUInt128>(1); var buffer = accelerator.Allocate1D<GpuUInt128>(1);
var lucasLehmerKernel = GetLucasLehmerKernel(accelerator); kernel(1, exponent, modulus, buffer.View);
lucasLehmerKernel(stream, 1, exponent, modulus, buffer.View); accelerator.Synchronize();
stream.Synchronize();
// TODO: Replace this with .CopyToCPU with pre-allocated buffer using stream. Synchronize on the stream after copy.
result = buffer.GetAsArray1D()[0].IsZero; result = buffer.GetAsArray1D()[0].IsZero;
buffer.Dispose(); buffer.Dispose();
stream.Dispose(); gpu.Dispose();
} }
// GpuPrimeWorkLimiter.Release(); limiter.Dispose();
return result; return result;
} }
@@ -194,18 +100,16 @@ public class MersenneNumberLucasLehmerGpuTester
} }
} }
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var kernel = GetBatchKernel(accelerator);
var expBuffer = accelerator.Allocate1D<ulong>(count); var expBuffer = accelerator.Allocate1D<ulong>(count);
ArrayPool<ulong> ulongPool = ThreadStaticPools.UlongPool; ulong[] expArray = ArrayPool<ulong>.Shared.Rent(count);
ulong[] expArray = ulongPool.Rent(count);
exponents.CopyTo(expArray); exponents.CopyTo(expArray);
expBuffer.View.CopyFromCPU(stream, ref expArray[0], count); expBuffer.View.CopyFromCPU(ref expArray[0], count);
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool; GpuUInt128[] modulusArray = ArrayPool<GpuUInt128>.Shared.Rent(count);
GpuUInt128[] modulusArray = gpuUInt128Pool.Rent(count);
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
// TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128) // TODO: Replace this per-exponent shift with a small shared table (one entry per supported exponent < 128)
@@ -214,20 +118,19 @@ public class MersenneNumberLucasLehmerGpuTester
} }
var modBuffer = accelerator.Allocate1D<GpuUInt128>(count); var modBuffer = accelerator.Allocate1D<GpuUInt128>(count);
modBuffer.View.CopyFromCPU(stream, ref modulusArray[0], count); modBuffer.View.CopyFromCPU(ref modulusArray[0], count);
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(count); var stateBuffer = accelerator.Allocate1D<GpuUInt128>(count);
var lucasLehmerBatchKernel = GetLucasLehmerBatchKernel(accelerator); kernel(count, expBuffer.View, modBuffer.View, stateBuffer.View);
lucasLehmerBatchKernel(stream, count, expBuffer.View, modBuffer.View, stateBuffer.View); accelerator.Synchronize();
stateBuffer.View.CopyToCPU(stream, ref residues[0], count); stateBuffer.View.CopyToCPU(ref residues[0], count);
stream.Synchronize();
stateBuffer.Dispose(); stateBuffer.Dispose();
modBuffer.Dispose(); modBuffer.Dispose();
expBuffer.Dispose(); expBuffer.Dispose();
gpuUInt128Pool.Return(modulusArray); ArrayPool<GpuUInt128>.Shared.Return(modulusArray);
ulongPool.Return(expArray); ArrayPool<ulong>.Shared.Return(expArray);
stream.Dispose(); gpu.Dispose();
} }
public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results) public void IsMersennePrimeBatch(ReadOnlySpan<ulong> exponents, Span<bool> results)
@@ -238,27 +141,23 @@ public class MersenneNumberLucasLehmerGpuTester
throw new ArgumentException("Result span too small", nameof(results)); throw new ArgumentException("Result span too small", nameof(results));
} }
ArrayPool<GpuUInt128> gpuUInt128Pool = ThreadStaticPools.GpuUInt128Pool; GpuUInt128[] buffer = ArrayPool<GpuUInt128>.Shared.Rent(count);
GpuUInt128[] buffer = gpuUInt128Pool.Rent(count);
ComputeResidues(exponents, buffer.AsSpan(0, count)); ComputeResidues(exponents, buffer.AsSpan(0, count));
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
results[i] = buffer[i].IsZero; results[i] = buffer[i].IsZero;
} }
gpuUInt128Pool.Return(buffer); ArrayPool<GpuUInt128>.Shared.Return(buffer);
} }
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu) private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu)
{ {
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var addKernel = GetAddSmallKernel(accelerator);
var subKernel = GetSubSmallKernel(accelerator); var subKernel = GetSubSmallKernel(accelerator);
var reduceKernel = GetReduceKernel(accelerator); var reduceKernel = GetReduceKernel(accelerator);
var isZeroKernel = GetIsZeroKernel(accelerator); var zeroKernel = GetIsZeroKernel(accelerator);
int limbCount = (int)((exponent + 127UL) / 128UL); int limbCount = (int)((exponent + 127UL) / 128UL);
int target = limbCount * 2; int target = limbCount * 2;
@@ -268,9 +167,8 @@ public class MersenneNumberLucasLehmerGpuTester
transformLength <<= 1; transformLength <<= 1;
} }
var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength); var stateBuffer = accelerator.Allocate1D<GpuUInt128>(transformLength);
stateBuffer.MemSetToZero(stream); stateBuffer.MemSetToZero();
var addSmallKernel = GetAddSmallKernel(accelerator); addKernel(1, 4UL, stateBuffer.View);
addSmallKernel(stream, 1, 4UL, stateBuffer.View);
// TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to // TODO(LL-SLICE): Slice Lucas–Lehmer iterations into short batches to
// avoid long-running kernels and TDR. Example: process 8–64 iterations // avoid long-running kernels and TDR. Example: process 8–64 iterations
@@ -285,26 +183,27 @@ public class MersenneNumberLucasLehmerGpuTester
for (int s = 0; s < iter; s++) for (int s = 0; s < iter; s++)
{ {
// Square in NTT domain with staged kernels (short runtime per kernel). // Square in NTT domain with staged kernels (short runtime per kernel).
NttGpuMath.SquareDevice(accelerator, stream, stateBuffer.View, nttMod, primitiveRoot); NttGpuMath.SquareDevice(accelerator, stateBuffer.View, nttMod, primitiveRoot);
subKernel(stream, 1, 2UL, stateBuffer.View); subKernel(1, 2UL, stateBuffer.View);
reduceKernel(stream, 1, exponent, stateBuffer.View); reduceKernel(1, exponent, stateBuffer.View);
} }
// Synchronize between slices to yield to scheduler and avoid TDR. // Synchronize between slices to yield to scheduler and avoid TDR.
accelerator.Synchronize();
i += (ulong)iter; i += (ulong)iter;
} }
reduceKernel(stream, 1, exponent, stateBuffer.View); reduceKernel(1, exponent, stateBuffer.View);
var resultBuffer = accelerator.Allocate1D<byte>(1);
isZeroKernel(stream, 1, stateBuffer.View, resultBuffer.View);
Span<byte> result = stackalloc byte[1];
resultBuffer.View.CopyToCPU(stream, result);
stream.Synchronize();
var resultBuffer = accelerator.Allocate1D<byte>(1);
zeroKernel(1, stateBuffer.View, resultBuffer.View);
accelerator.Synchronize();
byte[] result = new byte[1];
resultBuffer.View.CopyToCPU(result);
bool isPrime = result[0] != 0; bool isPrime = result[0] != 0;
resultBuffer.Dispose(); resultBuffer.Dispose();
stateBuffer.Dispose(); stateBuffer.Dispose();
stream.Dispose(); gpu.Dispose();
return isPrime; return isPrime;
} }
@@ -573,4 +472,6 @@ public class MersenneNumberLucasLehmerGpuTester
return result; return result;
} }
} }
@@ -4,76 +4,62 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder) public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder)
{ {
private readonly GpuKernelType _kernelType = kernelType; private readonly GpuKernelType _kernelType = kernelType;
private readonly bool _useGpuOrder = useGpuOrder; private readonly bool _useGpuOrder = useGpuOrder;
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder);
var accelerator = gpuLease.Accelerator;
var stream = gpuLease.Stream;
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) var kernel = _kernelType switch
{ {
// GpuPrimeWorkLimiter.Acquire(); GpuKernelType.Pow2Mod => gpuLease.Pow2ModOrderKernel,
// var accelerator = SharedGpuContext.Accelerator; _ => gpuLease.IncrementalOrderKernel,
var acceleratorIndex = AcceleratorPool.Shared.Rent(); };
var accelerator = _accelerators[acceleratorIndex];
var stream = accelerator.CreateStream();
int batchSize = GpuConstants.ScanBatchSize; // large batch improves GPU occupancy and avoids TDR
UInt128 kStart = UInt128.One;
ulong divMul = (ulong)((((UInt128)1 << 64) - UInt128.One) / exponent) + 1UL;
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0; // ILGPU kernels do not support bool parameters
KernelContainer kernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.Pow2ModOrderKernelScan | KernelType.IncrementalOrderKernelScan); var foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run.
var kernel = _kernelType switch exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
{
GpuKernelType.Pow2Mod => kernels.Pow2ModOrder!,
_ => kernels.IncrementalOrder!,
};
MemoryBuffer1D<int, ILGPU.Stride1D.Dense>? foundBuffer; UInt128 remaining;
// lock (accelerator) int currentSize;
{ int found = 0;
foundBuffer = accelerator.Allocate1D<int>(1); // TODO: Replace this allocation with the pooled device buffer from GpuOrderKernelBenchmarks so repeated scans reuse the pinned staging memory instead of allocating per run. while (kStart <= maxK && Volatile.Read(ref isPrime))
{
remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
foundBuffer.MemSetToZero();
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
} // Ensure device has small cycles table for early rejections in order kernels
exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul,
kernelArgs, foundBuffer.View, smallCyclesView);
UInt128 remaining; stream.Synchronize();
int currentSize; foundBuffer.View.CopyToCPU(ref found, 1);
int found = 0; if (found != 0)
{
Volatile.Write(ref isPrime, false);
break;
}
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(kernels); kStart += (UInt128)currentSize;
while (kStart <= maxK && Volatile.Read(ref isPrime)) }
{
remaining = maxK - kStart + 1UL;
currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining;
// This clear is required because the result may not always be set. Remove it after the kernels are modified to always set the result.
foundBuffer.MemSetToZero(stream);
// Precompute residue automaton bases for this batch
UInt128 q0 = twoP * kStart + 1UL;
// TODO: Replace these modulo recomputations with the divisor-cycle stepping deltas captured in
// MersenneDivisorCycleLengthGpuBenchmarks so each batch advances via cached residues from the
// single snapshot block instead of recalculating mod 10/8/5/3 in every iteration.
q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
// Ensure device has small cycles table for early rejections in order kernels foundBuffer.Dispose();
kernel( gpuLease.Dispose();
stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, }
last, divMul, kernelArgs, foundBuffer.View, smallCyclesView
);
foundBuffer.View.CopyToCPU(stream, ref found, 1);
stream.Synchronize();
if (found != 0)
{
Volatile.Write(ref isPrime, false);
break;
}
kStart += (UInt128)currentSize;
}
stream.Dispose();
foundBuffer.Dispose();
// GpuPrimeWorkLimiter.Release();
}
} }
@@ -1,4 +1,9 @@
using System;
using System.Buffers;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -7,211 +12,185 @@ namespace PerfectNumbers.Core.Gpu;
public class MersenneNumberResidueGpuTester(bool useGpuOrder) public class MersenneNumberResidueGpuTester(bool useGpuOrder)
{ {
private readonly bool _useGpuOrder = useGpuOrder; private readonly bool _useGpuOrder = useGpuOrder;
private readonly ConcurrentBag<GpuContextPool.GpuContextLease> _acceleratorPool = new();
private readonly ConcurrentDictionary<Accelerator, 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>>> _pow2ModKernelCache = new(AcceleratorReferenceComparer.Instance);
private readonly ConcurrentDictionary<Accelerator, ConcurrentBag<ResidueResources>> _resourcePools = new(AcceleratorReferenceComparer.Instance);
[ThreadStatic] // TODO: Integrate MersenneDivisorCycles.Shared to consult cycle lengths for small q (<= 4M) and fast-reject
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ulong, GpuUInt128, GpuUInt128, byte, ulong, ResidueAutomatonArgs, ArrayView<ulong>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<uint, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>, ArrayView1D<ulong, Stride1D.Dense>>>? _pow2ModKernel; // candidates without launching heavy kernels. Consider a device-visible constant buffer per-accelerator
// via GpuKernelPool to avoid host round-trips.
// TODO: When cycles are missing for larger q values, compute only the single required cycle on the device
// requested by the caller, skip queuing additional block generation, and keep the snapshot cache untouched.
[ThreadStatic] // GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1.
private static Queue<ResidueResources>? _resourcePools; public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime)
{
var limiter = GpuPrimeWorkLimiter.Acquire();
var gpuLease = RentAccelerator();
var accelerator = gpuLease.Accelerator;
// TODO: Integrate MersenneDivisorCycles.Shared to consult cycle lengths for small q (<= 4M) and fast-reject // Monitor.Enter(gpuLease.ExecutionLock);
// candidates without launching heavy kernels. Consider a device-visible constant buffer per-accelerator
// via GpuKernelPool to avoid host round-trips.
// TODO: When cycles are missing for larger q values, compute only the single required cycle on the device
// requested by the caller, skip queuing additional block generation, and keep the snapshot cache untouched.
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators; var stream = accelerator.CreateStream();
var kernel = GetPow2ModKernel(accelerator);
var resources = RentResources(accelerator, GpuConstants.ScanBatchSize);
// GPU residue variant: check 2^p % q == 1 for q = 2*p*k + 1. var orderBuffer = resources.OrderBuffer;
public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) ulong[] orderArray = resources.OrderArray;
{ int batchSize = resources.Capacity;
// GpuPrimeWorkLimiter.Acquire();
var acceleratorIndex = AcceleratorPool.Shared.Rent();
var accelerator = _accelerators[acceleratorIndex];
// Monitor.Enter(gpuLease.ExecutionLock); // Ensure device has small cycles and primes tables for in-kernel lookup
var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator);
ResiduePrimeViews primeViews = GpuKernelPool.EnsureSmallPrimesOnDevice(accelerator);
var stream = accelerator.CreateStream(); UInt128 kStart = 1UL;
var kernel = GetPow2ModKernel(accelerator); UInt128 limit = maxK + UInt128.One;
var resources = RentResources(accelerator, GpuConstants.ScanBatchSize); byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0;
var gpuKernels = GpuKernelPool.GetOrAddKernels(acceleratorIndex, stream, KernelType.SmallCycles | KernelType.SmallPrimes); twoP.Mod10_8_5_3(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
step10 = step10.Mod10();
var orderBuffer = resources.OrderBuffer; GpuUInt128 twoPGpu = (GpuUInt128)twoP;
ulong[] orderArray = resources.OrderArray; UInt128 batchSize128 = (UInt128)batchSize;
int batchSize = resources.Capacity; UInt128 fullBatchStep = twoP * batchSize128;
UInt128 q = twoP * kStart + UInt128.One;
// Ensure device has small cycles and primes tables for in-kernel lookup while (kStart < limit && Volatile.Read(ref isPrime))
// TODO: Pass-in the stream down the path {
var smallCyclesView = GpuKernelPool.GetSmallCyclesOnDevice(gpuKernels); UInt128 remaining = limit - kStart;
ResiduePrimeViews primeViews = GpuKernelPool.GetSmallPrimesOnDevice(gpuKernels); int currentSize = remaining > batchSize128 ? batchSize : (int)remaining;
Span<ulong> orders = orderArray.AsSpan(0, currentSize);
ref ulong ordersRef = ref MemoryMarshal.GetReference(orders);
UInt128 kStart = 1UL; q.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3);
UInt128 limit = maxK + UInt128.One; var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5);
byte last = lastDigit == LastDigit.Seven ? (byte)1 : (byte)0;
twoP.Mod10_8_5_3(out ulong step10, out ulong step8, out ulong step5, out ulong step3);
step10 = step10.Mod10();
GpuUInt128 twoPGpu = (GpuUInt128)twoP; kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, 0UL,
UInt128 batchSize128 = (UInt128)batchSize; kernelArgs, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven, primeViews.LastOnePow2, primeViews.LastSevenPow2);
UInt128 fullBatchStep = twoP * batchSize128;
UInt128 q = twoP * kStart + UInt128.One;
while (kStart < limit && Volatile.Read(ref isPrime)) stream.Synchronize();
{ orderBuffer.View.CopyToCPU(ref ordersRef, currentSize);
UInt128 remaining = limit - kStart; if (!Volatile.Read(ref isPrime))
int currentSize = remaining > batchSize128 ? batchSize : (int)remaining; {
Span<ulong> orders = orderArray.AsSpan(0, currentSize); break;
ref ulong ordersRef = ref MemoryMarshal.GetReference(orders); }
q.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); bool compositeFound = false;
var kernelArgs = new ResidueAutomatonArgs(q0m10, step10, q0m8, step8, q0m3, step3, q0m5, step5); for (int i = 0; i < currentSize; i++)
{
if (orders[i] != 0UL)
{
Volatile.Write(ref isPrime, false);
compositeFound = true;
break;
}
}
kernel(stream, currentSize, exponent, twoPGpu, (GpuUInt128)kStart, last, 0UL, if (compositeFound)
kernelArgs, orderBuffer.View, smallCyclesView, primeViews.LastOne, primeViews.LastSeven, primeViews.LastOnePow2, primeViews.LastSevenPow2); {
break;
}
orderBuffer.View.CopyToCPU(stream, ref ordersRef, currentSize); UInt128 processed = (UInt128)currentSize;
stream.Synchronize(); kStart += processed;
if (!Volatile.Read(ref isPrime)) if (kStart < limit)
{
if (processed == batchSize128)
{
q += fullBatchStep;
}
else
{
q += twoP * processed; // TODO: Swap this multiplication for the shared cycle stepping helper once residue cycles are mandatory so we can reuse the cached remainder ladder instead of performing UInt128 multiply operations.
}
}
}
ReturnResources(accelerator, resources);
stream.Dispose();
// Monitor.Exit(gpuLease.ExecutionLock);
ReturnAccelerator(gpuLease);
limiter.Dispose();
}
private 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>> GetPow2ModKernel(Accelerator accelerator) =>
_pow2ModKernelCache.GetOrAdd(
accelerator,
static acc =>
{
var loaded = acc.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>>(Pow2ModKernels.Pow2ModKernelScan);
return (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>>)Delegate.CreateDelegate(
typeof(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>>),
loaded.Target,
loaded.Method);
});
private GpuContextPool.GpuContextLease RentAccelerator()
{
if (_acceleratorPool.TryTake(out var lease))
{
return lease;
}
return GpuContextPool.Rent();
}
private void ReturnAccelerator(GpuContextPool.GpuContextLease lease) => _acceleratorPool.Add(lease);
private ResidueResources RentResources(Accelerator accelerator, int capacity)
{
var bag = _resourcePools.GetOrAdd(accelerator, static _ => new ConcurrentBag<ResidueResources>());
while (bag.TryTake(out var resources))
{
if (resources.Capacity >= capacity)
{ {
break; // The caller always overwrites the required elements. We don't need to worry about clearing the output buffer.
} // resources.OrderBuffer.MemSetToZero();
return resources;
}
bool compositeFound = false; resources.Dispose();
for (int i = 0; i < currentSize; i++) }
{
if (orders[i] != 0UL)
{
Volatile.Write(ref isPrime, false);
compositeFound = true;
break;
}
}
if (compositeFound) var created = new ResidueResources(accelerator, capacity);
{ // The caller always overwrites the required elements. We don't need to worry about clearing the output buffer.
break; // created.OrderBuffer.MemSetToZero();
} return created;
}
UInt128 processed = (UInt128)currentSize; private void ReturnResources(Accelerator accelerator, ResidueResources resources) =>
kStart += processed; _resourcePools.GetOrAdd(accelerator, static _ => new ConcurrentBag<ResidueResources>()).Add(resources);
if (kStart < limit)
{
if (processed == batchSize128)
{
q += fullBatchStep;
}
else
{
q += twoP * processed; // TODO: Swap this multiplication for the shared cycle stepping helper once residue cycles are mandatory so we can reuse the cached remainder ladder instead of performing UInt128 multiply operations.
}
}
}
ReturnResources(resources); private sealed class ResidueResources
stream.Dispose(); {
// Monitor.Exit(gpuLease.ExecutionLock); internal ResidueResources(Accelerator accelerator, int capacity)
gpuKernels.Dispose(); {
// GpuPrimeWorkLimiter.Release(); Capacity = Math.Max(1, capacity);
} OrderBuffer = accelerator.Allocate1D<ulong>(Capacity);
OrderArray = ArrayPool<ulong>.Shared.Rent(Capacity);
}
private static 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>> GetPow2ModKernel(Accelerator accelerator) internal MemoryBuffer1D<ulong, Stride1D.Dense> OrderBuffer { get; }
{
var pool = _pow2ModKernel ?? []; internal ulong[] OrderArray { get; }
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
cached = CreatePow2ModKernel(accelerator); internal int Capacity { get; }
pool[accelerator] = cached;
return cached;
}
private static 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>> CreatePow2ModKernel(Accelerator accelerator) public void Dispose()
{ {
var loaded = accelerator.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>>(Pow2ModKernels.Pow2ModKernelScan); OrderBuffer.Dispose();
ArrayPool<ulong>.Shared.Return(OrderArray, clearArray: false);
}
}
var kernel = (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>>)Delegate.CreateDelegate( private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
typeof(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>>), {
loaded.Target, public static AcceleratorReferenceComparer Instance { get; } = new();
loaded.Method);
return kernel;
}
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
private static ResidueResources RentResources(Accelerator accelerator, int capacity) public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
{ }
var resourcePools = _resourcePools ??= new();
if (resourcePools.TryDequeue(out var bag))
{
bag.EnsureCapacity(accelerator, capacity);
return bag;
}
return new ResidueResources(accelerator, capacity);
}
private static void ReturnResources(ResidueResources resources)
{
var resourcePools = _resourcePools ??= new();
resourcePools.Enqueue(resources);
}
private sealed class ResidueResources
{
internal ResidueResources(Accelerator accelerator, int capacity)
{
Capacity = Math.Max(1, capacity);
// lock (accelerator)
{
OrderBuffer = accelerator.Allocate1D<ulong>(Capacity);
}
OrderArray = ThreadStaticPools.UlongPool.Rent(Capacity);
}
internal void EnsureCapacity(Accelerator accelerator, int capacity)
{
if (OrderBuffer.Length >= capacity)
{
return;
}
OrderBuffer.Dispose();
ThreadStaticPools.UlongPool.Return(OrderArray);
// lock (accelerator)
{
OrderBuffer = accelerator.Allocate1D<ulong>(capacity);
}
OrderArray = ThreadStaticPools.UlongPool.Rent(capacity);
Capacity = capacity;
}
internal MemoryBuffer1D<ulong, Stride1D.Dense> OrderBuffer { get; private set; }
internal ulong[] OrderArray { get; private set; }
internal int Capacity { get; private set; }
public void Dispose()
{
OrderBuffer.Dispose();
ThreadStaticPools.UlongPool.Return(OrderArray, clearArray: false);
}
}
private sealed class AcceleratorReferenceComparer : IEqualityComparer<Accelerator>
{
public static AcceleratorReferenceComparer Instance { get; } = new();
public bool Equals(Accelerator? x, Accelerator? y) => ReferenceEquals(x, y);
public int GetHashCode(Accelerator obj) => RuntimeHelpers.GetHashCode(obj);
}
} }
+239 -363
View File
@@ -1,3 +1,4 @@
using System.Buffers;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using ILGPU; using ILGPU;
using ILGPU.Runtime; using ILGPU.Runtime;
@@ -26,64 +27,27 @@ public static class NttGpuMath
// Global backend setting for GPU transforms. Used by ForwardGpu/InverseGpu. // Global backend setting for GPU transforms. Used by ForwardGpu/InverseGpu.
// TODO(NTT-OPT): Consider passing backend explicitly to avoid global state. // TODO(NTT-OPT): Consider passing backend explicitly to avoid global state.
public static NttBackend GpuTransformBackend { get; set; } = NttBackend.Reference; public static NttBackend GpuTransformBackend { get; set; } = NttBackend.Reference;
// Controls modular reduction strategy inside staged NTT paths. // Controls modular reduction strategy inside staged NTT paths.
public static ModReductionMode ReductionMode { get; set; } = ModReductionMode.Auto; public static ModReductionMode ReductionMode { get; set; } = ModReductionMode.Auto;
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128>> MulKernelCache = new(); // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>> StageKernelCache = new(); // TODO: Same as above – materialize staged kernels during startup so we can drop ConcurrentDictionary usage entirely per the benchmark guidance.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128>> ScaleKernelCache = new(); // TODO: Promote to the static kernel table initialized alongside the GpuModularArithmeticBenchmarks fast path.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong>> StageMontKernelCache = new(); // TODO: Inline the Montgomery kernels into the startup table instead of using a concurrent cache now that we no longer mutate state at runtime.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>> StageBarrett128KernelCache = new(); // TODO: Same plan – reuse the precomputed kernels measured fastest in MontgomeryMultiplyBenchmarks rather than looking them up dynamically.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong>> ScaleBarrett128KernelCache = new(); // TODO: Collapse into the startup kernel array once Barrett128 constants are preloaded.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>> SquareBarrett128KernelCache = new(); // TODO: Fold these kernels into the same startup table to avoid concurrent access overhead highlighted in GpuModularArithmeticBenchmarks.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>> ToMont64KernelCache = new(); // TODO: Prebind and reuse the Montgomery conversions from MontgomeryMultiplyBenchmarks instead of storing them in a concurrent cache.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong>> FromMont64KernelCache = new(); // TODO: As above – drop ConcurrentDictionary once startup prewarms every kernel.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong>> SquareMont64KernelCache = new(); // TODO: Inline into the static kernel table established during initialization.
private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>> ScaleMont64KernelCache = new(); // TODO: Move to the startup kernel table so Montgomery-scaled launches skip concurrent lookups.
[ThreadStatic] private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128>> ForwardKernelCache = new(); // TODO: Warm these forward kernels during initialization and store them in a plain array so the LucasLehmerGpuBenchmarks launch costs disappear.
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128>>? _mulKernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic] private static readonly ConcurrentDictionary<Accelerator, Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128>> InverseKernelCache = new(); // TODO: Same as above – replace with startup-prepared tables per LucasLehmerGpuBenchmarks guidance.
private static Dictionary<Accelerator, Action<AcceleratorStream,Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>>? _stageKernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic] 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 Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128>>? _scaleKernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic] private sealed class SquareCacheEntry
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong>>? _stageMontKernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>>? _stageBarrett128Kernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>>? _toMont64Kernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong>>? _fromMont64Kernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong>>? _scaleBarrett128Kernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static readonly Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>>? _squareMont64Kernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>>? _scaleMont64Kernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128>>? _forwardKernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128>>? _inverseKernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
[ThreadStatic]
private static Dictionary<Accelerator, Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int>>? _bitReverseKernel; // TODO: Replace this concurrent cache with the prewarmed accelerator-indexed tables from GpuModularArithmeticBenchmarks so kernel launches avoid dictionary lookups once the kernels are baked during startup.
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128> GetStageKernel(Accelerator accelerator)
{
var pool = _stageKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>(NttButterflyKernels.StageKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>>();
pool[accelerator] = cached;
return cached;
}
private sealed class SquareCacheEntry
{ {
public MemoryBuffer1D<GpuUInt128, Stride1D.Dense> Buffer { get; } public MemoryBuffer1D<GpuUInt128, Stride1D.Dense> Buffer { get; }
public GpuUInt128 Root { get; } public GpuUInt128 Root { get; }
@@ -108,7 +72,7 @@ public static class NttGpuMath
public ulong BarrettMuHigh { get; } public ulong BarrettMuHigh { get; }
public ulong BarrettMuLow { get; } public ulong BarrettMuLow { get; }
public SquareCacheEntry(Accelerator accelerator, AcceleratorStream stream, int length, GpuUInt128 modulus, GpuUInt128 primitiveRoot) public SquareCacheEntry(Accelerator accelerator, int length, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
Buffer = accelerator.Allocate1D<GpuUInt128>(length); Buffer = accelerator.Allocate1D<GpuUInt128>(length);
Root = new GpuUInt128(primitiveRoot); Root = new GpuUInt128(primitiveRoot);
@@ -135,7 +99,7 @@ public static class NttGpuMath
// Precompute stage twiddle factors (forward and inverse) and upload to GPU. // Precompute stage twiddle factors (forward and inverse) and upload to GPU.
Bits = (int)Math.Log2(length); Bits = (int)Math.Log2(length);
int twiddleCount = length - 1; int twiddleCount = length - 1;
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ArrayPool<GpuUInt128>.Shared;
var forward = pool.Rent(twiddleCount); var forward = pool.Rent(twiddleCount);
var inverse = pool.Rent(twiddleCount); var inverse = pool.Rent(twiddleCount);
StageOffsets = new int[Bits]; StageOffsets = new int[Bits];
@@ -172,9 +136,9 @@ public static class NttGpuMath
} }
Twiddles = accelerator.Allocate1D<GpuUInt128>(twiddleCount); Twiddles = accelerator.Allocate1D<GpuUInt128>(twiddleCount);
Twiddles.View.CopyFromCPU(stream, ref forward[0], twiddleCount); Twiddles.View.CopyFromCPU(ref forward[0], twiddleCount);
TwiddlesInv = accelerator.Allocate1D<GpuUInt128>(twiddleCount); TwiddlesInv = accelerator.Allocate1D<GpuUInt128>(twiddleCount);
TwiddlesInv.View.CopyFromCPU(stream, ref inverse[0], twiddleCount); TwiddlesInv.View.CopyFromCPU(ref inverse[0], twiddleCount);
// Prepare Montgomery constants for 64-bit moduli and twiddles in Montgomery domain. // Prepare Montgomery constants for 64-bit moduli and twiddles in Montgomery domain.
if (modulus.High == 0UL && (ReductionMode == ModReductionMode.Auto || ReductionMode == ModReductionMode.Mont64)) if (modulus.High == 0UL && (ReductionMode == ModReductionMode.Auto || ReductionMode == ModReductionMode.Mont64))
@@ -202,9 +166,9 @@ public static class NttGpuMath
} }
TwiddlesMont = accelerator.Allocate1D<GpuUInt128>(twiddleCount); TwiddlesMont = accelerator.Allocate1D<GpuUInt128>(twiddleCount);
TwiddlesMont.View.CopyFromCPU(stream, ref forwardMont[0], twiddleCount); TwiddlesMont.View.CopyFromCPU(ref forwardMont[0], twiddleCount);
TwiddlesInvMont = accelerator.Allocate1D<GpuUInt128>(twiddleCount); TwiddlesInvMont = accelerator.Allocate1D<GpuUInt128>(twiddleCount);
TwiddlesInvMont.View.CopyFromCPU(stream, ref inverseMont[0], twiddleCount); TwiddlesInvMont.View.CopyFromCPU(ref inverseMont[0], twiddleCount);
pool.Return(forwardMont, clearArray: true); pool.Return(forwardMont, clearArray: true);
pool.Return(inverseMont, clearArray: true); pool.Return(inverseMont, clearArray: true);
@@ -286,7 +250,7 @@ public static class NttGpuMath
private readonly record struct SquareCacheKey(int Length, GpuUInt128 Modulus, GpuUInt128 PrimitiveRoot); private readonly record struct SquareCacheKey(int Length, GpuUInt128 Modulus, GpuUInt128 PrimitiveRoot);
private static readonly ConcurrentDictionary<SquareCacheKey, SquareCacheEntry> SquareCache = []; private static readonly ConcurrentDictionary<Accelerator, ConcurrentDictionary<SquareCacheKey, SquareCacheEntry>> SquareCache = new();
// TODO(NTT-OPT): Add a TwiddleCache for stage-wise Cooley–Tukey NTT. // TODO(NTT-OPT): Add a TwiddleCache for stage-wise Cooley–Tukey NTT.
// Design: // Design:
@@ -294,10 +258,11 @@ public static class NttGpuMath
// - Store one GPU buffer with all stage twiddles, or per-stage slices. // - Store one GPU buffer with all stage twiddles, or per-stage slices.
// - Expose accessors to retrieve per-stage views used by butterfly kernels. // - Expose accessors to retrieve per-stage views used by butterfly kernels.
private static SquareCacheEntry GetSquareCache(Accelerator accelerator, AcceleratorStream stream, int length, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static SquareCacheEntry GetSquareCache(Accelerator accelerator, int length, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
var perAccel = SquareCache.GetOrAdd(accelerator, _ => new ConcurrentDictionary<SquareCacheKey, SquareCacheEntry>());
var key = new SquareCacheKey(length, modulus, primitiveRoot); var key = new SquareCacheKey(length, modulus, primitiveRoot);
return SquareCache.GetOrAdd(key, _ => new SquareCacheEntry(accelerator, stream, length, modulus, primitiveRoot)); return perAccel.GetOrAdd(key, _ => new SquareCacheEntry(accelerator, length, modulus, primitiveRoot));
} }
private static readonly ulong[] SmallPrimes = GenerateSmallPrimes(1000); private static readonly ulong[] SmallPrimes = GenerateSmallPrimes(1000);
@@ -365,197 +330,105 @@ public static class NttGpuMath
{ {
if (accelerator is null) if (accelerator is null)
{ {
foreach (var entry in SquareCache.Values) foreach (var perAccel in SquareCache.Values)
{ {
entry.Dispose(); foreach (var entry in perAccel.Values)
{
entry.Dispose();
}
} }
SquareCache.Clear(); SquareCache.Clear();
MulKernelCache.Clear();
StageKernelCache.Clear();
ScaleKernelCache.Clear();
StageMontKernelCache.Clear();
StageBarrett128KernelCache.Clear();
ToMont64KernelCache.Clear();
FromMont64KernelCache.Clear();
SquareMont64KernelCache.Clear();
ScaleMont64KernelCache.Clear();
ForwardKernelCache.Clear();
InverseKernelCache.Clear();
// TODO(NTT-OPT): Clear TwiddleCache once introduced. // TODO(NTT-OPT): Clear TwiddleCache once introduced.
return; return;
} }
var squareCache = SquareCache; if (SquareCache.TryRemove(accelerator, out var cache))
foreach (var entry in squareCache.Values) {
{ foreach (var entry in cache.Values)
entry.Dispose(); {
} entry.Dispose();
}
}
MulKernelCache.TryRemove(accelerator, out _);
StageKernelCache.TryRemove(accelerator, out _);
ScaleKernelCache.TryRemove(accelerator, out _);
StageMontKernelCache.TryRemove(accelerator, out _);
StageBarrett128KernelCache.TryRemove(accelerator, out _);
ToMont64KernelCache.TryRemove(accelerator, out _);
FromMont64KernelCache.TryRemove(accelerator, out _);
SquareMont64KernelCache.TryRemove(accelerator, out _);
ScaleMont64KernelCache.TryRemove(accelerator, out _);
ForwardKernelCache.TryRemove(accelerator, out _);
InverseKernelCache.TryRemove(accelerator, out _);
// TODO(NTT-OPT): Remove per-accelerator twiddle buffers once added. // TODO(NTT-OPT): Remove per-accelerator twiddle buffers once added.
} }
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int> GetBitReverseKernel(Accelerator accelerator) private static Action<Index1D, ArrayView<GpuUInt128>, int> GetBitReverseKernel(Accelerator accelerator) =>
{ BitReverseKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int>(NttTransformKernels.BitReverseKernel));
var pool = _bitReverseKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int>(NttTransformKernels.BitReverseKernel); private static Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128> GetMulKernel(Accelerator accelerator) =>
MulKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128>(NttPointwiseKernels.MulKernel));
var kernel = KernelUtil.GetKernel(loaded); private static Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128> GetStageKernel(Accelerator accelerator) =>
StageKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, GpuUInt128>(NttButterflyKernels.StageKernel));
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int>>(); private static Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128> GetScaleKernel(Accelerator accelerator) =>
pool[accelerator] = cached; ScaleKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128>(NttPointwiseKernels.ScaleKernel));
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128> GetForwardKernel(Accelerator accelerator) private static Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong> GetStageMontKernel(Accelerator accelerator) =>
{ StageMontKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong>(NttMontgomeryKernels.StageMontKernel));
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128>(NttTransformKernels.ForwardKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128>>();
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong> GetFromMont64Kernel(Accelerator accelerator) private static Action<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong> GetStageBarrett128Kernel(Accelerator accelerator) =>
{ StageBarrett128KernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>(NttBarrettKernels.StageBarrett128Kernel));
var pool = _fromMont64Kernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong>(NttMontgomeryKernels.FromMont64Kernel); private static Action<Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong> GetScaleBarrett128Kernel(Accelerator accelerator) =>
ScaleBarrett128KernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong>(NttBarrettKernels.ScaleBarrett128Kernel));
var kernel = KernelUtil.GetKernel(loaded); private static Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong> GetSquareBarrett128Kernel(Accelerator accelerator) =>
SquareBarrett128KernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>(NttBarrettKernels.SquareBarrett128Kernel));
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong>>(); private static Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong> GetToMont64Kernel(Accelerator accelerator) =>
pool[accelerator] = cached; ToMont64KernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>(NttMontgomeryKernels.ToMont64Kernel));
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128> GetInverseKernel(Accelerator accelerator) private static Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong> GetFromMont64Kernel(Accelerator accelerator) =>
{ FromMont64KernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong>(NttMontgomeryKernels.FromMont64Kernel));
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128>(NttTransformKernels.InverseKernel);
var kernel = KernelUtil.GetKernel(loaded);
return kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128>>();
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128> GetMulKernel(Accelerator accelerator) private static Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong> GetSquareMont64Kernel(Accelerator accelerator) =>
{ SquareMont64KernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong>(NttMontgomeryKernels.SquareMont64Kernel));
var pool = _mulKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128>(NttPointwiseKernels.MulKernel); private static Action<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong> GetScaleMont64Kernel(Accelerator accelerator) =>
ScaleMont64KernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>(NttMontgomeryKernels.ScaleMont64Kernel));
var kernel = KernelUtil.GetKernel(loaded); private static Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128> GetForwardKernel(Accelerator accelerator) =>
ForwardKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128>(NttTransformKernels.ForwardKernel));
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, GpuUInt128>>(); // TODO(NTT-OPT): Introduce stage-wise Cooley–Tukey kernels and a
pool[accelerator] = cached; // per-(accelerator,length,modulus,root) twiddle cache. The plan:
return cached; // - Precompute twiddle factors for all stages once and cache on GPU.
} // - Replace the current O(n^2) ForwardKernel with O(n log n) butterflies.
// - Provide accessors like GetStageKernel(...) and GetTwiddleBuffer(...).
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128> GetScaleKernel(Accelerator accelerator) private static Action<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128> GetInverseKernel(Accelerator accelerator) =>
{ InverseKernelCache.GetOrAdd(accelerator, acc => acc.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ArrayView<GpuUInt128>, int, GpuUInt128, GpuUInt128, GpuUInt128>(NttTransformKernels.InverseKernel));
var pool = _scaleKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128>(NttPointwiseKernels.ScaleKernel); // TODO(NTT-OPT): Mirror the forward stage-wise design for the inverse
// transform. Precompute inverse twiddles and a normalization factor
// (n^-1 mod m). Launch one short kernel per stage to avoid TDR.
var kernel = KernelUtil.GetKernel(loaded); public static void BitReverse(Span<GpuUInt128> values)
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, GpuUInt128, GpuUInt128>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong> GetScaleBarrett128Kernel(Accelerator accelerator)
{
var pool = _scaleBarrett128Kernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong>(NttBarrettKernels.ScaleBarrett128Kernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, GpuUInt128, ulong, ulong, ulong, ulong>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong> GetScaleMont64Kernel(Accelerator accelerator)
{
var pool = _scaleMont64Kernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>(NttMontgomeryKernels.ScaleMont64Kernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong> GetStageBarrett128Kernel(Accelerator accelerator)
{
var pool = _stageBarrett128Kernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>(NttBarrettKernels.StageBarrett128Kernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong, ulong, ulong>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong> GetStageMontKernel(Accelerator accelerator)
{
var pool = _stageMontKernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong>(NttMontgomeryKernels.StageMontKernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, int, int, int, ArrayView<GpuUInt128>, ulong, ulong>>();
pool[accelerator] = cached;
return cached;
}
private static Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong> GetToMont64Kernel(Accelerator accelerator)
{
var pool = _toMont64Kernel ??= [];
if (pool.TryGetValue(accelerator, out var cached))
{
return cached;
}
var loaded = accelerator.LoadAutoGroupedStreamKernel<Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>(NttMontgomeryKernels.ToMont64Kernel);
var kernel = KernelUtil.GetKernel(loaded);
cached = kernel.CreateLauncherDelegate<Action<AcceleratorStream, Index1D, ArrayView<GpuUInt128>, ulong, ulong, ulong>>();
pool[accelerator] = cached;
return cached;
}
// TODO(NTT-OPT): Mirror the forward stage-wise design for the inverse
// transform. Precompute inverse twiddles and a normalization factor
// (n^-1 mod m). Launch one short kernel per stage to avoid TDR.
public static void BitReverse(Span<GpuUInt128> values)
{ {
int n = values.Length; int n = values.Length;
int bits = (int)Math.Log2(n); int bits = (int)Math.Log2(n);
@@ -573,22 +446,21 @@ public static class NttGpuMath
{ {
int n = values.Length; int n = values.Length;
int bits = (int)Math.Log2(n); int bits = (int)Math.Log2(n);
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var pool = ThreadStaticPools.GpuUInt128Pool; var kernel = GetBitReverseKernel(accelerator);
var pool = ArrayPool<GpuUInt128>.Shared;
var array = pool.Rent(n); var array = pool.Rent(n);
values.CopyTo(array); values.CopyTo(array);
var buffer = accelerator.Allocate1D<GpuUInt128>(n); var buffer = accelerator.Allocate1D<GpuUInt128>(n);
var stream = accelerator.CreateStream(); buffer.View.CopyFromCPU(ref array[0], n);
buffer.View.CopyFromCPU(stream, ref array[0], n); kernel(n, buffer.View, bits);
var bitReverseKernel = GetBitReverseKernel(accelerator); accelerator.Synchronize();
bitReverseKernel(stream, n, buffer.View, bits); buffer.View.CopyToCPU(ref array[0], n);
buffer.View.CopyToCPU(stream, ref array[0], n);
stream.Synchronize();
array.AsSpan(0, n).CopyTo(values); array.AsSpan(0, n).CopyTo(values);
pool.Return(array, clearArray: true); pool.Return(array, clearArray: true);
buffer.Dispose(); buffer.Dispose();
stream.Dispose(); gpu.Dispose();
} }
@@ -636,30 +508,30 @@ public static class NttGpuMath
private static void ForwardGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void ForwardGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = (int)values.Length; int n = (int)values.Length;
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var kernel = GetForwardKernel(accelerator);
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ArrayPool<GpuUInt128>.Shared;
var inputArray = pool.Rent(n); var inputArray = pool.Rent(n);
var outputArray = pool.Rent(n); var outputArray = pool.Rent(n);
values.CopyTo(inputArray); values.CopyTo(inputArray);
var inputBuffer = accelerator.Allocate1D<GpuUInt128>(n); var inputBuffer = accelerator.Allocate1D<GpuUInt128>(n);
var outputBuffer = accelerator.Allocate1D<GpuUInt128>(n); var outputBuffer = accelerator.Allocate1D<GpuUInt128>(n);
inputBuffer.View.CopyFromCPU(stream, ref inputArray[0], n); inputBuffer.View.CopyFromCPU(ref inputArray[0], n);
UInt128 modValue = modulus; UInt128 modValue = modulus;
UInt128 expBase = (modValue - 1UL) / (ulong)n; UInt128 expBase = (modValue - 1UL) / (ulong)n;
var root = new GpuUInt128(primitiveRoot); var root = new GpuUInt128(primitiveRoot);
root.ModPow((ulong)expBase, modulus); root.ModPow((ulong)expBase, modulus);
var forwardKernel = GetForwardKernel(accelerator); kernel(n, inputBuffer.View, outputBuffer.View, n, modulus, root);
forwardKernel(stream, n, inputBuffer.View, outputBuffer.View, n, modulus, root); accelerator.Synchronize();
outputBuffer.View.CopyToCPU(stream, ref outputArray[0], n); outputBuffer.View.CopyToCPU(ref outputArray[0], n);
stream.Synchronize();
outputArray.AsSpan(0, n).CopyTo(values); outputArray.AsSpan(0, n).CopyTo(values);
pool.Return(inputArray, clearArray: true); pool.Return(inputArray, clearArray: true);
pool.Return(outputArray, clearArray: true); pool.Return(outputArray, clearArray: true);
outputBuffer.Dispose(); outputBuffer.Dispose();
inputBuffer.Dispose(); inputBuffer.Dispose();
stream.Dispose(); gpu.Dispose();
} }
public static void Inverse(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) public static void Inverse(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
@@ -733,16 +605,16 @@ public static class NttGpuMath
private static void InverseGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void InverseGpuReference(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = values.Length; int n = values.Length;
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var kernel = GetInverseKernel(accelerator);
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ArrayPool<GpuUInt128>.Shared;
var inputArray = pool.Rent(n); var inputArray = pool.Rent(n);
var outputArray = pool.Rent(n); var outputArray = pool.Rent(n);
values.CopyTo(inputArray); values.CopyTo(inputArray);
var inputBuffer = accelerator.Allocate1D<GpuUInt128>(n); var inputBuffer = accelerator.Allocate1D<GpuUInt128>(n);
var outputBuffer = accelerator.Allocate1D<GpuUInt128>(n); var outputBuffer = accelerator.Allocate1D<GpuUInt128>(n);
inputBuffer.View.CopyFromCPU(stream, ref inputArray[0], n); inputBuffer.View.CopyFromCPU(ref inputArray[0], n);
UInt128 modValue = modulus; UInt128 modValue = modulus;
UInt128 expBase = (modValue - 1UL) / (ulong)n; UInt128 expBase = (modValue - 1UL) / (ulong)n;
var root = new GpuUInt128(primitiveRoot); var root = new GpuUInt128(primitiveRoot);
@@ -758,78 +630,78 @@ public static class NttGpuMath
} }
var nInv = new GpuUInt128((UInt128)n); var nInv = new GpuUInt128((UInt128)n);
if (modulus.High == 0UL) if (modulus.High == 0UL)
{ {
nInv.ModInv(modulus.Low); nInv.ModInv(modulus.Low);
} }
else else
{ {
nInv.ModInv(modulus); nInv.ModInv(modulus);
} }
kernel(n, inputBuffer.View, outputBuffer.View, n, modulus, rootInv, nInv);
var inverseKernel = GetInverseKernel(accelerator); accelerator.Synchronize();
inverseKernel(stream, n, inputBuffer.View, outputBuffer.View, n, modulus, rootInv, nInv); outputBuffer.View.CopyToCPU(ref outputArray[0], n);
outputBuffer.View.CopyToCPU(stream, ref outputArray[0], n);
stream.Synchronize();
outputArray.AsSpan(0, n).CopyTo(values); outputArray.AsSpan(0, n).CopyTo(values);
pool.Return(inputArray, clearArray: true); pool.Return(inputArray, clearArray: true);
pool.Return(outputArray, clearArray: true); pool.Return(outputArray, clearArray: true);
outputBuffer.Dispose(); outputBuffer.Dispose();
inputBuffer.Dispose(); inputBuffer.Dispose();
stream.Dispose(); gpu.Dispose();
} }
private static void ForwardGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void ForwardGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = values.Length; int n = values.Length;
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream();
var buffer = accelerator.Allocate1D<GpuUInt128>(n); var buffer = accelerator.Allocate1D<GpuUInt128>(n);
buffer.View.CopyFromCPU(stream, ref values[0], n); buffer.View.CopyFromCPU(ref values[0], n);
var cache = GetSquareCache(accelerator, stream, n, modulus, primitiveRoot); var cache = GetSquareCache(accelerator, n, modulus, primitiveRoot);
ForwardDevice(accelerator, stream, buffer.View, n, modulus, cache); ForwardDevice(accelerator, buffer.View, n, modulus, cache);
buffer.View.CopyToCPU(stream, ref values[0], n); buffer.View.CopyToCPU(ref values[0], n);
stream.Synchronize();
stream.Dispose();
buffer.Dispose(); buffer.Dispose();
gpu.Dispose();
} }
private static void InverseGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) private static void InverseGpuStaged(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
int n = values.Length; int n = values.Length;
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream();
var buffer = accelerator.Allocate1D<GpuUInt128>(n); var buffer = accelerator.Allocate1D<GpuUInt128>(n);
buffer.View.CopyFromCPU(stream, ref values[0], n); buffer.View.CopyFromCPU(ref values[0], n);
var cache = GetSquareCache(accelerator, stream, n, modulus, primitiveRoot); var cache = GetSquareCache(accelerator, n, modulus, primitiveRoot);
InverseDevice(accelerator, stream, buffer.View, n, modulus, cache); InverseDevice(accelerator, buffer.View, n, modulus, cache);
buffer.View.CopyToCPU(stream, ref values[0], n); buffer.View.CopyToCPU(ref values[0], n);
stream.Synchronize();
stream.Dispose();
buffer.Dispose(); buffer.Dispose();
gpu.Dispose();
} }
private static void ForwardDevice(Accelerator accelerator, AcceleratorStream stream, ArrayView<GpuUInt128> data, int length, GpuUInt128 modulus, SquareCacheEntry cache) private static void ForwardDevice(Accelerator accelerator, ArrayView<GpuUInt128> data, int length, GpuUInt128 modulus, SquareCacheEntry cache)
{ {
var bitReverseKernel = GetBitReverseKernel(accelerator); var bitKernel = GetBitReverseKernel(accelerator);
bitReverseKernel(stream, length, data, cache.Bits); bitKernel(length, data, cache.Bits);
accelerator.Synchronize();
if (cache.UseMontgomery64) if (cache.UseMontgomery64)
{ {
var toMont64Kernel = GetToMont64Kernel(accelerator); var toMont = GetToMont64Kernel(accelerator);
toMont64Kernel(stream, length, data, cache.ModulusLow, cache.MontNPrime64, cache.MontR2Mod64); toMont(length, data, cache.ModulusLow, cache.MontNPrime64, cache.MontR2Mod64);
accelerator.Synchronize();
var stageKernel = GetStageMontKernel(accelerator); var stageKernel = GetStageMontKernel(accelerator);
int butterflies = length >> 1; int butterflies = length >> 1;
for (int s = 0; s < cache.Bits; s++) for (int s = 0; s < cache.Bits; s++)
{ {
int len = 1 << (s + 1); int len = 1 << (s + 1);
int half = len >> 1; int half = len >> 1;
stageKernel(stream, butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesMont!.View, cache.ModulusLow, cache.MontNPrime64); stageKernel(butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesMont!.View, cache.ModulusLow, cache.MontNPrime64);
} accelerator.Synchronize();
var fromMont64Kernel = GetFromMont64Kernel(accelerator); }
fromMont64Kernel(stream, length, data, cache.ModulusLow, cache.MontNPrime64); var fromMont = GetFromMont64Kernel(accelerator);
fromMont(length, data, cache.ModulusLow, cache.MontNPrime64);
} }
else if (cache.UseBarrett128) else if (cache.UseBarrett128)
{ {
@@ -839,7 +711,8 @@ public static class NttGpuMath
{ {
int len = 1 << (s + 1); int len = 1 << (s + 1);
int half = len >> 1; int half = len >> 1;
stageKernel(stream, butterflies, data, len, half, cache.StageOffsets[s], cache.Twiddles.View, cache.ModulusHigh, cache.ModulusLow, cache.BarrettMuHigh, cache.BarrettMuLow); stageKernel(butterflies, data, len, half, cache.StageOffsets[s], cache.Twiddles.View, cache.ModulusHigh, cache.ModulusLow, cache.BarrettMuHigh, cache.BarrettMuLow);
accelerator.Synchronize();
} }
} }
else else
@@ -850,67 +723,72 @@ public static class NttGpuMath
{ {
int len = 1 << (s + 1); int len = 1 << (s + 1);
int half = len >> 1; int half = len >> 1;
stageKernel(stream, butterflies, data, len, half, cache.StageOffsets[s], cache.Twiddles.View, modulus); stageKernel(butterflies, data, len, half, cache.StageOffsets[s], cache.Twiddles.View, modulus);
accelerator.Synchronize();
} }
} }
stream.Synchronize(); accelerator.Synchronize();
} }
private static void InverseDevice(Accelerator accelerator, AcceleratorStream stream, ArrayView<GpuUInt128> data, int length, GpuUInt128 modulus, SquareCacheEntry cache) private static void InverseDevice(Accelerator accelerator, ArrayView<GpuUInt128> data, int length, GpuUInt128 modulus, SquareCacheEntry cache)
{ {
var bitReverseKernel = GetBitReverseKernel(accelerator); var bitKernel = GetBitReverseKernel(accelerator);
bitReverseKernel(stream, length, data, cache.Bits); bitKernel(length, data, cache.Bits);
accelerator.Synchronize();
if (cache.UseMontgomery64) if (cache.UseMontgomery64)
{ {
var toMont64Kernel = GetToMont64Kernel(accelerator); var toMont = GetToMont64Kernel(accelerator);
toMont64Kernel(stream, length, data, cache.ModulusLow, cache.MontNPrime64, cache.MontR2Mod64); toMont(length, data, cache.ModulusLow, cache.MontNPrime64, cache.MontR2Mod64);
accelerator.Synchronize();
var stageKernel = GetStageMontKernel(accelerator); var stageKernel = GetStageMontKernel(accelerator);
int butterflies = length >> 1; int butterflies = length >> 1;
for (int s = 0; s < cache.Bits; s++) for (int s = 0; s < cache.Bits; s++)
{ {
int len = 1 << (s + 1); int len = 1 << (s + 1);
int half = len >> 1; int half = len >> 1;
stageKernel(stream, butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesInvMont!.View, cache.ModulusLow, cache.MontNPrime64); stageKernel(butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesInvMont!.View, cache.ModulusLow, cache.MontNPrime64);
} accelerator.Synchronize();
}
var scaleMont64Kernel = GetScaleMont64Kernel(accelerator); var scaleKernel = GetScaleMont64Kernel(accelerator);
scaleMont64Kernel(stream, length, data, cache.ModulusLow, cache.MontNPrime64, cache.MontNInvR64); scaleKernel(length, data, cache.ModulusLow, cache.MontNPrime64, cache.MontNInvR64);
accelerator.Synchronize();
var fromMont64Kernel = GetFromMont64Kernel(accelerator); var fromMont = GetFromMont64Kernel(accelerator);
fromMont64Kernel(stream, length, data, cache.ModulusLow, cache.MontNPrime64); fromMont(length, data, cache.ModulusLow, cache.MontNPrime64);
} }
else if (cache.UseBarrett128) else if (cache.UseBarrett128)
{ {
var stageKernel = GetStageBarrett128Kernel(accelerator); var stageKernel = GetStageBarrett128Kernel(accelerator);
int butterflies = length >> 1; int butterflies = length >> 1;
for (int s = 0; s < cache.Bits; s++) for (int s = 0; s < cache.Bits; s++)
{ {
int len = 1 << (s + 1); int len = 1 << (s + 1);
int half = len >> 1; int half = len >> 1;
stageKernel(stream, butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesInv.View, cache.ModulusHigh, cache.ModulusLow, cache.BarrettMuHigh, cache.BarrettMuLow); stageKernel(butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesInv.View, cache.ModulusHigh, cache.ModulusLow, cache.BarrettMuHigh, cache.BarrettMuLow);
} accelerator.Synchronize();
}
var scaleBarrett128Kernel = GetScaleBarrett128Kernel(accelerator); var scaleKernel = GetScaleBarrett128Kernel(accelerator);
scaleBarrett128Kernel(stream, length, data, cache.NInv, cache.ModulusHigh, cache.ModulusLow, cache.BarrettMuHigh, cache.BarrettMuLow); scaleKernel(length, data, cache.NInv, cache.ModulusHigh, cache.ModulusLow, cache.BarrettMuHigh, cache.BarrettMuLow);
accelerator.Synchronize();
} }
else else
{ {
var stageKernel = GetStageKernel(accelerator); var stageKernel = GetStageKernel(accelerator);
int butterflies = length >> 1; int butterflies = length >> 1;
for (int s = 0; s < cache.Bits; s++) for (int s = 0; s < cache.Bits; s++)
{ {
int len = 1 << (s + 1); int len = 1 << (s + 1);
int half = len >> 1; int half = len >> 1;
stageKernel(stream, butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesInv.View, modulus); stageKernel(butterflies, data, len, half, cache.StageOffsets[s], cache.TwiddlesInv.View, modulus);
} accelerator.Synchronize();
}
var scaleKernel = GetScaleKernel(accelerator); var scaleKernel = GetScaleKernel(accelerator);
scaleKernel(stream, length, data, cache.NInv, modulus); scaleKernel(length, data, cache.NInv, modulus);
accelerator.Synchronize();
} }
stream.Synchronize(); accelerator.Synchronize();
} }
public static void Convolve(Span<GpuUInt128> left, Span<GpuUInt128> right, GpuUInt128 modulus, GpuUInt128 primitiveRoot) public static void Convolve(Span<GpuUInt128> left, Span<GpuUInt128> right, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
@@ -922,7 +800,7 @@ public static class NttGpuMath
n <<= 1; n <<= 1;
} }
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ArrayPool<GpuUInt128>.Shared;
var aBuffer = pool.Rent(n); var aBuffer = pool.Rent(n);
var bBuffer = pool.Rent(n); var bBuffer = pool.Rent(n);
var a = aBuffer.AsSpan(0, n); var a = aBuffer.AsSpan(0, n);
@@ -959,7 +837,7 @@ public static class NttGpuMath
n <<= 1; n <<= 1;
} }
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ArrayPool<GpuUInt128>.Shared;
var aBuffer = pool.Rent(n); var aBuffer = pool.Rent(n);
var bBuffer = pool.Rent(n); var bBuffer = pool.Rent(n);
var a = aBuffer.AsSpan(0, n); var a = aBuffer.AsSpan(0, n);
@@ -993,43 +871,41 @@ public static class NttGpuMath
public static void SquareGpu(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) => public static void SquareGpu(Span<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) =>
ConvolveGpu(values, values, modulus, primitiveRoot); ConvolveGpu(values, values, modulus, primitiveRoot);
public static void SquareDevice(Accelerator accelerator, AcceleratorStream stream, ArrayView<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot) public static void SquareDevice(Accelerator accelerator, ArrayView<GpuUInt128> values, GpuUInt128 modulus, GpuUInt128 primitiveRoot)
{ {
var cache = GetSquareCache(accelerator, stream, (int)values.Length, modulus, primitiveRoot); var cache = GetSquareCache(accelerator, (int)values.Length, modulus, primitiveRoot);
ForwardDevice(accelerator, stream, values, (int)values.Length, modulus, cache); ForwardDevice(accelerator, values, (int)values.Length, modulus, cache);
var mulKernel = GetMulKernel(accelerator); var mul = GetMulKernel(accelerator);
mulKernel(stream, (int)values.Length, values, values, modulus); mul((int)values.Length, values, values, modulus);
InverseDevice(accelerator, stream, values, (int)values.Length, modulus, cache); InverseDevice(accelerator, values, (int)values.Length, modulus, cache);
} }
private static readonly Accelerator[] _accelerators = AcceleratorPool.Shared.Accelerators;
public static void PointwiseMultiply(Span<GpuUInt128> left, Span<GpuUInt128> right, GpuUInt128 modulus) public static void PointwiseMultiply(Span<GpuUInt128> left, Span<GpuUInt128> right, GpuUInt128 modulus)
{ {
var acceleratorIndex = AcceleratorPool.Shared.Rent(); var gpu = GpuContextPool.Rent();
var accelerator = _accelerators[acceleratorIndex]; var accelerator = gpu.Accelerator;
var stream = accelerator.CreateStream(); var kernel = GetMulKernel(accelerator);
var pool = ThreadStaticPools.GpuUInt128Pool; var pool = ArrayPool<GpuUInt128>.Shared;
var leftArray = pool.Rent(left.Length); var leftArray = pool.Rent(left.Length);
var rightArray = pool.Rent(right.Length); var rightArray = pool.Rent(right.Length);
left.CopyTo(leftArray); left.CopyTo(leftArray);
right.CopyTo(rightArray); right.CopyTo(rightArray);
var leftBuffer = accelerator.Allocate1D<GpuUInt128>(left.Length); var leftBuffer = accelerator.Allocate1D<GpuUInt128>(left.Length);
var rightBuffer = accelerator.Allocate1D<GpuUInt128>(right.Length); var rightBuffer = accelerator.Allocate1D<GpuUInt128>(right.Length);
leftBuffer.View.CopyFromCPU(stream, ref leftArray[0], left.Length); leftBuffer.View.CopyFromCPU(ref leftArray[0], left.Length);
rightBuffer.View.CopyFromCPU(stream, ref rightArray[0], right.Length); rightBuffer.View.CopyFromCPU(ref rightArray[0], right.Length);
var mulKernel = GetMulKernel(accelerator); kernel(left.Length, leftBuffer.View, rightBuffer.View, modulus);
mulKernel(stream, left.Length, leftBuffer.View, rightBuffer.View, modulus); accelerator.Synchronize();
leftBuffer.View.CopyToCPU(stream, ref leftArray[0], left.Length); leftBuffer.View.CopyToCPU(ref leftArray[0], left.Length);
rightBuffer.View.CopyToCPU(stream, ref rightArray[0], right.Length); rightBuffer.View.CopyToCPU(ref rightArray[0], right.Length);
stream.Synchronize();
leftArray.AsSpan(0, left.Length).CopyTo(left); leftArray.AsSpan(0, left.Length).CopyTo(left);
rightArray.AsSpan(0, right.Length).CopyTo(right); rightArray.AsSpan(0, right.Length).CopyTo(right);
pool.Return(leftArray, clearArray: true); pool.Return(leftArray, clearArray: true);
pool.Return(rightArray, clearArray: true); pool.Return(rightArray, clearArray: true);
rightBuffer.Dispose(); rightBuffer.Dispose();
leftBuffer.Dispose(); leftBuffer.Dispose();
stream.Dispose(); gpu.Dispose();
} }
private static ulong[] GenerateSmallPrimes(int limit) private static ulong[] GenerateSmallPrimes(int limit)
@@ -1,94 +0,0 @@
using System.Runtime.CompilerServices;
using ILGPU;
using ILGPU.Runtime;
using PerfectNumbers.Core.Gpu.Accelerators;
namespace PerfectNumbers.Core.Gpu;
public static class Pow2MontgomeryGpuCalculator
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateKeep(ulong exponent, in MontgomeryDivisorData divisor)
{
// GpuPrimeWorkLimiter.Acquire();
ulong result = 0UL;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
int acceleratorIndex = gpu.AcceleratorIndex;
Accelerator? accelerator = gpu.Accelerator;
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer = gpu.Input;
MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = gpu.OutputUlong;
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1);
// We don't need to worry about any left-overs here.
// resultBuffer.MemSetToZero(stream);
var kernelLauncher = gpu.KeepMontgomeryKernelLauncher;
kernelLauncher(stream, 1, exponentBuffer.View, divisor, resultBuffer.View);
resultBuffer.View.CopyToCPU(stream, ref result, 1);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
PrimeOrderCalculatorAccelerator.Return(gpu);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateConvert(ulong exponent, in MontgomeryDivisorData divisor)
{
// GpuPrimeWorkLimiter.Acquire();
ulong result = 0UL;
var gpu = PrimeOrderCalculatorAccelerator.Rent(1);
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer = gpu.Input;
MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = gpu.OutputUlong;
int acceleratorIndex = gpu.AcceleratorIndex;
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1);
// We don't need to worry about any left-overs here.
// resultBuffer.MemSetToZero(stream);
var kernelLauncher = gpu.ConvertToStandardKernelLauncher!;
kernelLauncher(stream, 1, exponentBuffer.View, divisor, resultBuffer.View);
resultBuffer.View.CopyToCPU(stream, ref result, 1);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
// GpuPrimeWorkLimiter.Release();
PrimeOrderCalculatorAccelerator.Return(gpu);
return result;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ulong CalculateConvert(PrimeOrderCalculatorAccelerator gpu, ulong exponent, in MontgomeryDivisorData divisor)
{
ulong result = 0UL;
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer = gpu.Input;
MemoryBuffer1D<ulong, Stride1D.Dense> resultBuffer = gpu.OutputUlong;
int acceleratorIndex = gpu.AcceleratorIndex;
AcceleratorStream stream = AcceleratorStreamPool.Rent(acceleratorIndex);
exponentBuffer.View.CopyFromCPU(stream, ref exponent, 1);
// We don't need to worry about any left-overs here.
// resultBuffer.MemSetToZero(stream);
var accelerator = gpu.Accelerator;
var kernelLauncher = gpu.ConvertToStandardKernelLauncher;
kernelLauncher(stream, 1, exponentBuffer.View, divisor, resultBuffer.View);
resultBuffer.View.CopyToCPU(stream, ref result, 1);
stream.Synchronize();
AcceleratorStreamPool.Return(acceleratorIndex, stream);
return result;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,15 +0,0 @@
namespace PerfectNumbers.Core.Gpu;
public readonly struct ResidueAutomatonArgs(
ulong q0m10, ulong step10, ulong q0m8, ulong step8, ulong q0m3, ulong step3, ulong q0m5, ulong step5
)
{
public readonly ulong Q0M10 = q0m10;
public readonly ulong Step10 = step10;
public readonly ulong Q0M8 = q0m8;
public readonly ulong Step8 = step8;
public readonly ulong Q0M3 = q0m3;
public readonly ulong Step3 = step3;
public readonly ulong Q0M5 = q0m5;
public readonly ulong Step5 = step5;
}
Loaded 100 of 127 files, more files were not shown because too many files have changed in this diff. Show more