Merge pull request #43 from mlemanczyk/codex/investigate-evenperfectbitscanner-calculations

Serialize GPU prime sieve per accelerator
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-09-20 19:00:41 +02:00
commit 1714f39f81
3 files changed
+111 -36

No files matched your search

@@ -360,6 +360,48 @@ public class ProgramTests
}
}
[Fact]
[Trait("Category", "Fast")]
public void Residue_mode_accepts_known_primes_in_parallel()
{
var useDivisorField = typeof(Program).GetField("_useDivisor", 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 residueField = typeof(Program).GetField("PResidue", BindingFlags.NonPublic | BindingFlags.Static)!;
var gcdFilterField = typeof(Program).GetField("_useGcdFilter", BindingFlags.NonPublic | BindingFlags.Static)!;
var forceCpuProp = typeof(GpuContextPool).GetProperty("ForceCpu");
useDivisorField.SetValue(null, false);
gcdFilterField.SetValue(null, false);
mersenneField.SetValue(null, new ThreadLocal<MersenneNumberTester>(() => new MersenneNumberTester(
useIncremental: true,
useResidue: true,
maxK: 5_000_000UL,
residueDivisorSets: PerfectNumberConstants.ExtraDivisorCycleSearchLimit), trackAllValues: true));
primeField.SetValue(null, new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true));
residueField.SetValue(null, new ThreadLocal<ModResidueTracker>(() => new ModResidueTracker(ResidueModel.Identity, 2UL, true), trackAllValues: true));
forceCpuProp!.SetValue(null, false);
try
{
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
Parallel.ForEach(primes, prime =>
{
Program.IsEvenPerfectCandidate(prime, 0UL, out bool searched, out bool detailed).Should().BeTrue();
searched.Should().BeTrue();
detailed.Should().BeTrue();
});
}
finally
{
useDivisorField.SetValue(null, false);
mersenneField.SetValue(null, null);
primeField.SetValue(null, null);
residueField.SetValue(null, null);
forceCpuProp!.SetValue(null, false);
}
}
[Fact]
public void Residue_mode_with_default_limits_accepts_large_known_primes()
{
@@ -36,5 +36,33 @@ public class PrimeTesterIsPrimeTests
GpuContextPool.ForceCpu = false;
}
[Fact]
[Trait("Category", "Fast")]
public void IsPrimeGpu_handles_parallel_calls_for_known_primes()
{
GpuContextPool.ForceCpu = false;
GpuPrimeWorkLimiter.SetLimit(64);
PrimeTester.GpuBatchSize = 256_000;
ulong[] primes = [107UL, 127UL, 521UL, 607UL];
var results = new bool[primes.Length];
var testers = new ThreadLocal<PrimeTester>(() => new PrimeTester(), trackAllValues: true);
Parallel.For(0, primes.Length, i =>
{
results[i] = testers.Value!.IsPrimeGpu(primes[i], CancellationToken.None);
});
results.Should().AllBeEquivalentTo(true);
Parallel.For(0, Environment.ProcessorCount * 16, _ =>
{
foreach (ulong prime in primes)
{
testers.Value!.IsPrimeGpu(prime, CancellationToken.None).Should().BeTrue();
}
});
}
}
+41 -36
View File
@@ -83,48 +83,53 @@ public sealed class PrimeTester(bool useInternal = false)
// Limit concurrency and declare variables outside loops for performance and reuse
var limiter = GpuPrimeWorkLimiter.Acquire();
var gpu = GpuContextPool.Rent();
var accelerator = gpu.Accelerator;
// Get per-accelerator cached kernel and device primes buffer.
var state = GpuKernelState.GetOrCreate(accelerator);
MemoryBuffer1D<ulong, Stride1D.Dense> input;
MemoryBuffer1D<byte, Stride1D.Dense> output;
int totalLength, batchSize, pos, remaining, count;
ulong[] temp;
totalLength = values.Length;
batchSize = Math.Max(1, GpuBatchSize);
// Rent per-call scratch buffers sized to current batch (thread-safe)
var scratch = state.RentScratch(batchSize, accelerator);
input = scratch.Input;
output = scratch.Output;
// Reusable host buffer per call
temp = ArrayPool<ulong>.Shared.Rent(batchSize);
pos = 0;
while (pos < totalLength)
try
{
remaining = totalLength - pos;
count = remaining > batchSize ? batchSize : remaining;
var accelerator = gpu.Accelerator;
// Copy chunk to host temp then to device
values.Slice(pos, count).CopyTo(temp);
input.View.CopyFromCPU(ref temp[0], count);
// Get per-accelerator cached kernel and device primes buffer.
var state = GpuKernelState.GetOrCreate(accelerator);
int totalLength = values.Length;
int batchSize = Math.Max(1, GpuBatchSize);
state.Kernel(count, input.View, state.DevicePrimes.View, output.View);
accelerator.Synchronize();
output.View.CopyToCPU(ref results[pos], count);
lock (gpu.ExecutionLock)
{
var scratch = state.RentScratch(batchSize, accelerator);
var input = scratch.Input;
var output = scratch.Output;
ulong[] temp = ArrayPool<ulong>.Shared.Rent(batchSize);
pos += count;
try
{
int pos = 0;
while (pos < totalLength)
{
int remaining = totalLength - pos;
int count = remaining > batchSize ? batchSize : remaining;
values.Slice(pos, count).CopyTo(temp);
input.View.CopyFromCPU(ref temp[0], count);
state.Kernel(count, input.View, state.DevicePrimes.View, output.View);
accelerator.Synchronize();
output.View.CopyToCPU(ref results[pos], count);
pos += count;
}
}
finally
{
ArrayPool<ulong>.Shared.Return(temp);
state.ReturnScratch(scratch);
}
}
}
finally
{
gpu.Dispose();
limiter.Dispose();
}
ArrayPool<ulong>.Shared.Return(temp);
state.ReturnScratch(scratch);
gpu.Dispose();
limiter.Dispose();
}
// Per-accelerator GPU state for prime sieve (kernel + uploaded primes).