diff --git a/EvenPerfectBitScanner.Tests/ProgramTests.cs b/EvenPerfectBitScanner.Tests/ProgramTests.cs index fecddbf..e4fb578 100644 --- a/EvenPerfectBitScanner.Tests/ProgramTests.cs +++ b/EvenPerfectBitScanner.Tests/ProgramTests.cs @@ -540,7 +540,7 @@ public class ProgramTests public void Main_displays_help_when_requested() { var main = typeof(Program).GetMethod("Main", BindingFlags.NonPublic | BindingFlags.Static)!; - using var writer = new StringWriter(); + var writer = new StringWriter(); TextWriter original = Console.Out; Console.SetOut(writer); @@ -554,6 +554,7 @@ public class ProgramTests } string output = writer.ToString(); + writer.Dispose(); output.Should().Contain("Usage:"); output.Should().Contain("--mersenne-device=cpu|gpu"); output.Should().Contain("--primes-device=cpu|gpu"); diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index b08f418..75aa570 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -720,7 +720,21 @@ internal static class Program if (_useByDivisorMode) { - RunByDivisorMode(byDivisorCandidates, threadCount, _primeTestLimit); + if (_byDivisorTester is null) + { + throw new InvalidOperationException("By-divisor tester has not been initialized."); + } + + MersenneNumberDivisorByDivisorTester.Run( + byDivisorCandidates, + _byDivisorTester, + _byDivisorPreviousResults, + _byDivisorStartPrime, + static () => _lastCompositeP = true, + static () => _lastCompositeP = false, + PrintResult, + threadCount, + _primeTestLimit); FlushBuffer(); StringBuilderPool.Return(_outputBuilder!); return; @@ -861,27 +875,6 @@ internal static class Program return candidates; } - private static void RunByDivisorMode(List candidates, int threadCount, TimeSpan? primeTestLimit) - { - if (_byDivisorTester is null) - { - throw new InvalidOperationException("By-divisor tester has not been initialized."); - } - - // TODO: Inline this helper by invoking MersenneNumberDivisorByDivisorTester.Run directly at call sites so - // the CLI avoids this extra frame during the hot composite-reporting path. - MersenneNumberDivisorByDivisorTester.Run( - candidates, - _byDivisorTester, - _byDivisorPreviousResults, - _byDivisorStartPrime, - static () => _lastCompositeP = true, - static () => _lastCompositeP = false, - PrintResult, - threadCount, - primeTestLimit); - } - private static unsafe void LoadResultsFile(string resultsFileName, Action lineProcessorAction) { using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); diff --git a/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs b/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs index 3b605cc..62d7ee5 100644 --- a/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs @@ -18,13 +18,14 @@ public class GpuPrimeLimiterTests var startedSecond = new TaskCompletionSource(); var sw = new Stopwatch(); - using var first = GpuPrimeWorkLimiter.Acquire(); + var first = GpuPrimeWorkLimiter.Acquire(); sw.Start(); var secondTask = Task.Run(() => { - using var second = GpuPrimeWorkLimiter.Acquire(); + var second = GpuPrimeWorkLimiter.Acquire(); startedSecond.TrySetResult(true); + second.Dispose(); }); // Give secondTask a moment to attempt acquire (it should block). diff --git a/PerfectNumbers.Core.Tests/Gpu/NttMontgomeryTests.cs b/PerfectNumbers.Core.Tests/Gpu/NttMontgomeryTests.cs index 57fe816..90937f1 100644 --- a/PerfectNumbers.Core.Tests/Gpu/NttMontgomeryTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/NttMontgomeryTests.cs @@ -34,23 +34,19 @@ public class NttMontgomeryTests // Device execution GpuContextPool.GpuContextLease context = GpuContextPool.Rent(); - try - { - var accelerator = context.Accelerator; - using var buffer = accelerator.Allocate1D(gpu.Length); - buffer.View.CopyFromCPU(ref gpu[0], gpu.Length); - NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot); - buffer.View.CopyToCPU(ref gpu[0], gpu.Length); + var accelerator = context.Accelerator; + var buffer = accelerator.Allocate1D(gpu.Length); + buffer.View.CopyFromCPU(ref gpu[0], gpu.Length); + NttGpuMath.SquareDevice(accelerator, buffer.View, modulus, primitiveRoot); + buffer.View.CopyToCPU(ref gpu[0], gpu.Length); - for (int i = 0; i < cpu.Length; i++) - { - gpu[i].Should().Be(cpu[i]); - } - } - finally + for (int i = 0; i < cpu.Length; i++) { - context.Dispose(); + gpu[i].Should().Be(cpu[i]); } + + buffer.Dispose(); + context.Dispose(); } [Fact] diff --git a/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs b/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs index 7e51ad4..feefce1 100644 --- a/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs +++ b/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs @@ -17,13 +17,14 @@ public class GpuWorkLimiterTests var startedSecond = new TaskCompletionSource(); var sw = new Stopwatch(); - using var first = GpuWorkLimiter.Acquire(); + var first = GpuWorkLimiter.Acquire(); sw.Start(); var secondTask = Task.Run(() => { - using var second = GpuWorkLimiter.Acquire(); + var second = GpuWorkLimiter.Acquire(); startedSecond.TrySetResult(true); + second.Dispose(); }); await Task.Delay(50); diff --git a/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs b/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs index af63152..ddc21a3 100644 --- a/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneDivisorCyclesGenerateGpuTests.cs @@ -14,8 +14,8 @@ public class MersenneDivisorCyclesGenerateGpuTests { MersenneDivisorCycles.GenerateGpu(path, maxDivisor: 50UL, batchSize: 16, skipCount: 0, nextPosition: 0); var pairs = new List<(ulong divisor, ulong cycle)>(); - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); - using var reader = new BinaryReader(stream); + var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + var reader = new BinaryReader(stream); while (stream.Position < stream.Length) { ulong d = reader.ReadUInt64(); @@ -41,6 +41,9 @@ public class MersenneDivisorCyclesGenerateGpuTests MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(pair.divisor); pair.cycle.Should().Be(MersenneDivisorCycles.CalculateCycleLength(pair.divisor, divisorData)); } + + reader.Dispose(); + stream.Dispose(); } finally { @@ -61,8 +64,8 @@ public class MersenneDivisorCyclesGenerateGpuTests MersenneDivisorCycles.Generate(path, maxDivisor: 50UL, threads: 4); var pairs = new List<(ulong divisor, ulong cycle)>(); - using var stream = new FileStream(path, FileMode.Open, FileAccess.Read); - using var reader = new BinaryReader(stream); + var stream = new FileStream(path, FileMode.Open, FileAccess.Read); + var reader = new BinaryReader(stream); while (stream.Position < stream.Length) { ulong divisor = reader.ReadUInt64(); @@ -95,6 +98,9 @@ public class MersenneDivisorCyclesGenerateGpuTests var (_, completeCount) = MersenneDivisorCycles.FindLast(path); completeCount.Should().Be(expected.Count); + + reader.Dispose(); + stream.Dispose(); } finally { diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs index 4b01a18..609a27d 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs @@ -46,7 +46,7 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL, 333_380UL }; byte[] hits = new byte[exponents.Length]; @@ -74,6 +74,8 @@ public class MersenneNumberDivisorCpuTesterTests } hits.Should().Equal(expected2); + + session.Dispose(); } [Fact] @@ -83,7 +85,7 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(LargeDivisor1); ulong cycle = MersenneDivisorCycles.CalculateCycleLength(LargeDivisor1, divisorData); @@ -98,6 +100,8 @@ public class MersenneNumberDivisorCpuTesterTests } hits.Should().Equal(expected); + + session.Dispose(); } [Fact] @@ -107,7 +111,7 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(LargeDivisor2); ulong cycle = MersenneDivisorCycles.CalculateCycleLength(LargeDivisor2, divisorData); @@ -122,5 +126,7 @@ public class MersenneNumberDivisorCpuTesterTests } hits.Should().Equal(expected); + + session.Dispose(); } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index 3ec73c6..7b32931 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -157,7 +157,7 @@ public class MersenneNumberDivisorGpuTesterTests var tester = new MersenneNumberDivisorByDivisorGpuTester(); tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL, 333_380UL }; byte[] hits = new byte[exponents.Length]; @@ -185,6 +185,8 @@ public class MersenneNumberDivisorGpuTesterTests } hits.Should().Equal(expected2); + + session.Dispose(); } [Fact] @@ -198,7 +200,7 @@ public class MersenneNumberDivisorGpuTesterTests tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL }; byte[] hits = new byte[exponents.Length]; @@ -213,6 +215,8 @@ public class MersenneNumberDivisorGpuTesterTests } hits.Should().Equal(expected); + + session.Dispose(); } [Fact] @@ -225,7 +229,7 @@ public class MersenneNumberDivisorGpuTesterTests }; tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(4_000_169UL); ulong cycle = MersenneDivisorCycles.CalculateCycleLength(4_000_169UL, divisorData); @@ -252,6 +256,8 @@ public class MersenneNumberDivisorGpuTesterTests expected.Should().Equal(direct); hits.Should().Equal(direct); + + session.Dispose(); } [Fact] @@ -264,7 +270,7 @@ public class MersenneNumberDivisorGpuTesterTests }; tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(4_000_561UL); ulong cycle = MersenneDivisorCycles.CalculateCycleLength(4_000_561UL, divisorData); @@ -291,6 +297,8 @@ public class MersenneNumberDivisorGpuTesterTests expected.Should().Equal(direct); hits.Should().Equal(direct); + + session.Dispose(); } [Fact] @@ -300,7 +308,7 @@ public class MersenneNumberDivisorGpuTesterTests var tester = new MersenneNumberDivisorByDivisorGpuTester(); tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); ulong[] exponents = { 59UL, 67UL, 117_652UL }; byte[] hits = new byte[exponents.Length]; @@ -316,6 +324,8 @@ public class MersenneNumberDivisorGpuTesterTests } hits.Should().Equal(expected); + + session.Dispose(); } [Fact] @@ -329,7 +339,7 @@ public class MersenneNumberDivisorGpuTesterTests tester.ConfigureFromMaxPrime(197UL); - using var session = tester.CreateDivisorSession(); + var session = tester.CreateDivisorSession(); ulong[] exponents = { 59UL, 61UL, 67UL, 71UL, 73UL, 79UL, 117_652UL }; byte[] hits = new byte[exponents.Length]; @@ -344,6 +354,8 @@ public class MersenneNumberDivisorGpuTesterTests } hits.Should().Equal(expected); + + session.Dispose(); } } diff --git a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs index f65d521..d921377 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelPool.cs @@ -76,10 +76,10 @@ public readonly struct ResiduePrimeViews( public readonly ArrayView1D LastSevenPow2 = lastSevenPow2; } -public ref struct GpuKernelLease(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels) +public ref struct GpuKernelLease(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels) { private bool disposedValue; - private IDisposable? _limiter = limiter; + private GpuPrimeWorkLimiter.Lease? _limiter = limiter; private GpuContextLease? _gpu = gpu; private KernelContainer _kernels = kernels; private AcceleratorStream? _stream; diff --git a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs index 9602b0c..78ee601 100644 --- a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs +++ b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs @@ -6,13 +6,13 @@ public static class GpuPrimeWorkLimiter private static readonly object _lock = new(); private static int _currentLimit = 1; - public static IDisposable Acquire() + public static Lease Acquire() { // TODO: Inline the pooled limiter guard from the GpuLimiterThroughputBenchmarks so prime-sieve // GPU jobs stop allocating Releaser instances on every acquisition. var sem = _semaphore; sem.Wait(); - return new Releaser(sem); + return new Lease(sem); } public static void SetLimit(int value) @@ -36,12 +36,12 @@ public static class GpuPrimeWorkLimiter } } - private sealed class Releaser : IDisposable + public sealed class Lease { private readonly SemaphoreSlim _sem; private bool _disposed; - public Releaser(SemaphoreSlim sem) + public Lease(SemaphoreSlim sem) { _sem = sem; } diff --git a/PerfectNumbers.Core/Gpu/GpuWorkLimiter.cs b/PerfectNumbers.Core/Gpu/GpuWorkLimiter.cs index a27b11c..557b317 100644 --- a/PerfectNumbers.Core/Gpu/GpuWorkLimiter.cs +++ b/PerfectNumbers.Core/Gpu/GpuWorkLimiter.cs @@ -6,14 +6,14 @@ public static class GpuWorkLimiter private static readonly object _lock = new(); private static int _currentLimit = 1; - public static IDisposable Acquire() + public static Lease Acquire() { // TODO: Replace the per-call Releaser allocation with the pooled struct-based guard from the // GpuLimiterThroughputBenchmarks so limiter acquisition does not allocate when we enter the // GPU scanning hot path. var sem = _semaphore; sem.Wait(); - return new Releaser(sem); + return new Lease(sem); } public static void SetLimit(int value) @@ -40,12 +40,12 @@ public static class GpuWorkLimiter } } - private sealed class Releaser : IDisposable + public sealed class Lease { private readonly SemaphoreSlim _sem; private bool _disposed; - public Releaser(SemaphoreSlim sem) + public Lease(SemaphoreSlim sem) { _sem = sem; } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index f0f7e65..dbef3b1 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -1,6 +1,7 @@ using System; using System.Buffers; using System.Collections.Concurrent; +using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Numerics; @@ -122,6 +123,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv resources.CycleCandidates, resources.CycleLengths, resources.CycleCapacity, + resources, out lastProcessed, out coveredRange, out processedCount @@ -186,6 +188,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ulong[] cycleCandidates, ulong[] cycleLengths, int cycleCapacity, + BatchResources resources, out ulong lastProcessed, out bool coveredRange, out ulong processedCount) @@ -340,10 +343,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv gapView.CopyFromCPU(ref MemoryMarshal.GetReference(gapSpan), chunkCount); - ComputeRemaindersOnGpu(chunkCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel); - ComputeRemaindersOnGpu(chunkCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel); - ComputeRemaindersOnGpu(chunkCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel); - ComputeRemaindersOnGpu(chunkCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel); + ComputeRemaindersOnGpu(chunkCount, remainder10, 10, gapView, deltaView, remainder10View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig); + ComputeRemaindersOnGpu(chunkCount, remainder8, 8, gapView, deltaView, remainder8View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig); + ComputeRemaindersOnGpu(chunkCount, remainder5, 5, gapView, deltaView, remainder5View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig); + ComputeRemaindersOnGpu(chunkCount, remainder3, 3, gapView, deltaView, remainder3View, remainderDeltaKernel, remainderScanKernel, resources.GetRemainderScanConfig); BuildCandidateMaskOnGpu(chunkCount, remainder10View, remainder8View, remainder5View, remainder3View, lastIsSevenFlag, maskView, maskSpan, candidateMaskKernel); @@ -461,7 +464,8 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ArrayView1D deltaView, ArrayView1D remainderView, Action, ArrayView, byte> deltaKernel, - Action, ArrayView, byte, byte> scanKernel) + Action, ArrayView, byte, byte> scanKernel, + Func configProvider) { // GPU batches always contain at least one divisor gap, so the empty-count guard remains commented // to eliminate redundant branching. @@ -471,10 +475,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv // } deltaKernel(count, gapView, deltaView, modulus); - SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic(count); - Index1D gridDim = new Index1D(1); - Index1D groupDim = new Index1D(count); - KernelConfig kernelConfig = new KernelConfig(gridDim, groupDim, sharedMemoryConfig); + KernelConfig kernelConfig = configProvider(count); scanKernel(kernelConfig, new Index1D(count), deltaView, remainderView, baseRemainder, modulus); } @@ -844,7 +845,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private void ReturnBatchResources(Accelerator accelerator, BatchResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => []).Add(resources); - private sealed class BatchResources : IDisposable + private sealed class BatchResources { internal BatchResources(MersenneNumberDivisorByDivisorGpuTester owner, Accelerator accelerator, int capacity) { @@ -906,10 +907,24 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal readonly Action, ArrayView, byte, byte> RemainderScanKernel; internal readonly Action, ArrayView, ArrayView, ArrayView, byte, ArrayView> CandidateMaskKernel; + private readonly Dictionary _remainderScanConfigs = new(); + internal readonly int Capacity; internal readonly int CycleCapacity; + internal KernelConfig GetRemainderScanConfig(int count) + { + if (!_remainderScanConfigs.TryGetValue(count, out KernelConfig config)) + { + SharedMemoryConfig sharedMemoryConfig = SharedMemoryConfig.RequestDynamic(count); + config = new KernelConfig(new Index1D(1), new Index1D(count), sharedMemoryConfig); + _remainderScanConfigs[count] = config; + } + + return config; + } + public void Dispose() { DivisorsBuffer.Dispose(); @@ -948,7 +963,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv return ComputeAllowedMaxDivisor(prime, _divisorLimit); } - public DivisorScanSession CreateDivisorSession() + public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() { // Divisor sessions are handed out only after configuration, so this guard would never trigger on the // production path. @@ -965,8 +980,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv return new DivisorScanSession(this); } - - IMersenneNumberDivisorByDivisorTester.IDivisorScanSession IMersenneNumberDivisorByDivisorTester.CreateDivisorSession() => CreateDivisorSession(); } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs index 4177fc7..b3a4aa3 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorCandidateGpuEvaluator.cs @@ -6,7 +6,7 @@ using ILGPU.Runtime; namespace PerfectNumbers.Core.Gpu; -internal sealed class MersenneNumberDivisorCandidateGpuEvaluator : IDisposable +internal sealed class MersenneNumberDivisorCandidateGpuEvaluator { private const int RemainderCount = 6; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs index 6b8a163..1f71d19 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorMontgomeryGpuBuilder.cs @@ -7,7 +7,7 @@ using ILGPU.Runtime; namespace PerfectNumbers.Core.Gpu; -internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder : IDisposable +internal sealed class MersenneNumberDivisorMontgomeryGpuBuilder { private readonly GpuContextPool.GpuContextLease _lease; private readonly Accelerator _accelerator; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs index 721a519..9a9b26e 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorRemainderGpuStepper.cs @@ -6,7 +6,7 @@ using ILGPU.Runtime; namespace PerfectNumbers.Core.Gpu; -internal sealed class MersenneNumberDivisorRemainderGpuStepper : IDisposable +internal sealed class MersenneNumberDivisorRemainderGpuStepper { private const int RemainderCount = 6; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs index 72c60ad..4af10b6 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorResidueGpuEvaluator.cs @@ -7,7 +7,7 @@ using ILGPU.Runtime; namespace PerfectNumbers.Core.Gpu; -internal sealed class MersenneNumberDivisorResidueGpuEvaluator : IDisposable +internal sealed class MersenneNumberDivisorResidueGpuEvaluator { private readonly GpuContextPool.GpuContextLease _lease; private readonly Accelerator _accelerator; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs index 76a137e..377bbd3 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs @@ -327,8 +327,9 @@ public class MersenneNumberLucasLehmerGpuTester // TODO: Replace StreamWriter with the pooled TextFileWriter pipeline so persisting NTT // parameters reuses the zero-allocation buffered writes highlighted in the scanner I/O // benchmarks instead of allocating a new encoder per append. - using var writer = new StreamWriter(ParameterFilePath, append: true); + var writer = new StreamWriter(ParameterFilePath, append: true); writer.WriteLine($"{length} {modulus.High} {modulus.Low} {primitiveRoot.High} {primitiveRoot.Low}"); + writer.Dispose(); } } @@ -338,7 +339,7 @@ public class MersenneNumberLucasLehmerGpuTester ulong foundRoot = 0UL; bool success = false; var processorCount = Environment.ProcessorCount; - using var cts = new System.Threading.CancellationTokenSource(); + var cts = new System.Threading.CancellationTokenSource(); object sync = new(); // TODO: Move this Parallel.For to the shared low-overhead work scheduler once the NTT parameter @@ -381,11 +382,13 @@ public class MersenneNumberLucasLehmerGpuTester { modulus = new GpuUInt128(0UL, foundCandidate); primitiveRoot = new GpuUInt128(0UL, foundRoot); + cts.Dispose(); return true; } modulus = new GpuUInt128(0UL, 18446744069414584321UL); primitiveRoot = new GpuUInt128(0UL, 7UL); + cts.Dispose(); return false; } diff --git a/PerfectNumbers.Core/Gpu/MontgomeryOddPowerGpu.cs b/PerfectNumbers.Core/Gpu/MontgomeryOddPowerGpu.cs index d1a9efb..d6f016c 100644 --- a/PerfectNumbers.Core/Gpu/MontgomeryOddPowerGpu.cs +++ b/PerfectNumbers.Core/Gpu/MontgomeryOddPowerGpu.cs @@ -64,31 +64,25 @@ internal static class MontgomeryOddPowerGpu var accelerator = lease.Accelerator; var stream = lease.Stream; - using var divisorBuffer = accelerator.Allocate1D(divisorCount); - using var countBuffer = accelerator.Allocate1D(divisorCount); - using var resultBuffer = accelerator.Allocate1D(divisorCount * stride); + var divisorBuffer = accelerator.Allocate1D(divisorCount); + var countBuffer = accelerator.Allocate1D(divisorCount); + var resultBuffer = accelerator.Allocate1D(divisorCount * stride); - try - { - divisorBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(divisors), divisorCount); - countBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(oddPowerCounts), divisorCount); - resultBuffer.MemSetToZero(); + divisorBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(divisors), divisorCount); + countBuffer.View.CopyFromCPU(ref MemoryMarshal.GetReference(oddPowerCounts), divisorCount); + resultBuffer.MemSetToZero(); - var kernel = lease.MontgomeryOddPowerKernel; - kernel(stream, divisorCount, divisorBuffer.View, countBuffer.View, resultBuffer.View, stride); - stream.Synchronize(); + var kernel = lease.MontgomeryOddPowerKernel; + kernel(stream, divisorCount, divisorBuffer.View, countBuffer.View, resultBuffer.View, stride); + stream.Synchronize(); - resultBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), divisorCount * stride); - return true; - } - catch - { - return false; - } - finally - { - execution.Dispose(); - lease.Dispose(); - } + resultBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(results), divisorCount * stride); + + resultBuffer.Dispose(); + countBuffer.Dispose(); + divisorBuffer.Dispose(); + execution.Dispose(); + lease.Dispose(); + return true; } } diff --git a/PerfectNumbers.Core/Gpu/NttGpuMath.cs b/PerfectNumbers.Core/Gpu/NttGpuMath.cs index 821a6b0..fc4b8d2 100644 --- a/PerfectNumbers.Core/Gpu/NttGpuMath.cs +++ b/PerfectNumbers.Core/Gpu/NttGpuMath.cs @@ -47,7 +47,7 @@ public static class NttGpuMath private static readonly ConcurrentDictionary, int>> BitReverseKernelCache = new(); // TODO: Precompute bit-reversal kernels during boot so we can remove the concurrent lookup overhead flagged in the benchmarks. - private sealed class SquareCacheEntry : IDisposable + private sealed class SquareCacheEntry { public MemoryBuffer1D Buffer { get; } public GpuUInt128 Root { get; } diff --git a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs index 2e066c1..084473c 100644 --- a/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs +++ b/PerfectNumbers.Core/Gpu/PrimeOrderGpuHeuristics.cs @@ -1611,7 +1611,7 @@ internal static partial class PrimeOrderGpuHeuristics private static ulong CalculateByDoublingKernel(ulong prime) { ulong order = 1UL; - ulong value = 2UL % prime; + ulong value = 2UL; // Candidate primes exceed two, so the modulo would always return two. while (value != 1UL) { diff --git a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs index d452476..f02cf4f 100644 --- a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs @@ -18,8 +18,10 @@ public interface IMersenneNumberDivisorByDivisorTester void PrepareCandidates(ReadOnlySpan primes, Span allowedMaxValues); - public interface IDivisorScanSession : IDisposable + public interface IDivisorScanSession { void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan primes, Span hits); + + void Dispose(); } } diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 819c8de..cc1aa29 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -196,7 +196,7 @@ public class MersenneDivisorCycles public static IEnumerable<(ulong divisor, ulong cycleLength)> EnumerateStream(Stream compressor) { // Binary pairs: (ulong divisor, ulong cycle) - using var reader = new BinaryReader(compressor, Encoding.UTF8, leaveOpen: true); + var reader = new BinaryReader(compressor, Encoding.UTF8, leaveOpen: true); while (true) { ulong d, c; @@ -207,6 +207,7 @@ public class MersenneDivisorCycles } catch (EndOfStreamException) { + reader.Dispose(); yield break; } @@ -829,7 +830,7 @@ public class MersenneDivisorCycles // Start fresh to avoid mixing formats outputStream.SetLength(0L); outputStream.Position = 0L; - using var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false); + var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false); for (var taskIndex = 0; taskIndex < threads; taskIndex++) { ulong threadStart = start + (ulong)taskIndex * blockSize; @@ -911,6 +912,7 @@ public class MersenneDivisorCycles } Task.WaitAll(tasks); + writer.Dispose(); } public static void GenerateGpu(string path, ulong maxDivisor, int batchSize = 1_000_000, long skipCount = 0L, long nextPosition = 0L) @@ -947,7 +949,7 @@ public class MersenneDivisorCycles outputStream.Position = 0L; } - using var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false); + var writer = new BinaryWriter(outputStream, Encoding.UTF8, leaveOpen: false); try { while (skipCount > 0L && start <= maxDivisor) @@ -1022,6 +1024,7 @@ public class MersenneDivisorCycles pool.Return(outCycles, clearArray: false); } + writer.Dispose(); stream.Dispose(); lease.Dispose(); diff --git a/PerfectNumbers.Core/PrimeFactorPartialResult.cs b/PerfectNumbers.Core/PrimeFactorPartialResult.cs index bdb8d0d..74cc020 100644 --- a/PerfectNumbers.Core/PrimeFactorPartialResult.cs +++ b/PerfectNumbers.Core/PrimeFactorPartialResult.cs @@ -3,7 +3,7 @@ using System.Buffers; namespace PerfectNumbers.Core { - internal sealed class PrimeFactorPartialResult : IDisposable + internal sealed class PrimeFactorPartialResult { [ThreadStatic] private static PrimeFactorPartialResult? s_poolHead; diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index ad53846..c3cf08a 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -176,7 +176,7 @@ public sealed class PrimeTester(bool useInternal = false) DevicePrimes.View.CopyFromCPU(primes); } - internal sealed class ScratchBuffers : IDisposable + internal sealed class ScratchBuffers { public MemoryBuffer1D Input { get; private set; } public MemoryBuffer1D Output { get; private set; } diff --git a/PerfectNumbers.Core/TextFileWriter.cs b/PerfectNumbers.Core/TextFileWriter.cs index 39a8f83..dda50f6 100644 --- a/PerfectNumbers.Core/TextFileWriter.cs +++ b/PerfectNumbers.Core/TextFileWriter.cs @@ -3,7 +3,7 @@ namespace PerfectNumbers.Core; /// /// Provides thread-safe file writing for both text and binary data. /// -public sealed class TextFileWriter : IDisposable +public sealed class TextFileWriter { private readonly FileStream _stream; private StreamWriter? _textWriter; diff --git a/PerfectNumbers.Core/ULongExtensions.cs b/PerfectNumbers.Core/ULongExtensions.cs index cf45857..3c84598 100644 --- a/PerfectNumbers.Core/ULongExtensions.cs +++ b/PerfectNumbers.Core/ULongExtensions.cs @@ -530,7 +530,9 @@ public static class ULongExtensions ulong modulus = divisor.Modulus; if (exponent == 0UL) { - return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus; + return keepMontgomery + ? divisor.MontgomeryOne + : 1UL; // Montgomery moduli stay above one on the scanning path, so the modulo would always yield one. } if (exponent <= Pow2WindowFallbackThreshold) @@ -622,7 +624,9 @@ public static class ULongExtensions ulong modulus = divisor.Modulus; if (exponent == 0UL) { - return keepMontgomery ? divisor.MontgomeryOne : 1UL % modulus; + return keepMontgomery + ? divisor.MontgomeryOne + : 1UL; // Montgomery moduli stay above one on the scanning path, so the modulo would always yield one. } if (exponent <= Pow2WindowFallbackThreshold) @@ -769,7 +773,7 @@ public static class ULongExtensions return; } - ulong baseValue = 2UL % modulus; + ulong baseValue = 2UL; // Moduli arrive as odd primes greater than two, so the modulo would always return two. destination[0] = baseValue; if (oddPowerCount == 1) { diff --git a/apply_patch.cs b/apply_patch.cs index 71b34f6..e22f034 100644 --- a/apply_patch.cs +++ b/apply_patch.cs @@ -335,10 +335,13 @@ if (patchResults.Any(static r => !r.Success)) static List ParsePatchFromFile(string patchFilePath) { - using var stream = File.OpenRead(patchFilePath); - using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 16_384, leaveOpen: false); - using var lineReader = new PatchLineReader(reader); + var stream = File.OpenRead(patchFilePath); + var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true, bufferSize: 16_384, leaveOpen: false); + var lineReader = new PatchLineReader(reader); var files = ParsePatch(lineReader); + lineReader.Dispose(); + reader.Dispose(); + stream.Dispose(); return files; } @@ -500,7 +503,7 @@ static void FinalizePatchFile( files.Add(new PatchFile(originalPath, modifiedPath, hunks ?? new List(), type)); } -private sealed class PatchLineReader : IDisposable +private sealed class PatchLineReader { private const int ReadBufferSize = 4096; private const int InitialLineBufferSize = 256; diff --git a/tools/GpuNumericTypeProbe.csx b/tools/GpuNumericTypeProbe.csx index fe3d284..d2ca4b3 100644 --- a/tools/GpuNumericTypeProbe.csx +++ b/tools/GpuNumericTypeProbe.csx @@ -54,10 +54,11 @@ static ProbeResult ProbeBigInteger(Accelerator accelerator) try { var kernel = accelerator.LoadAutoGroupedStreamKernel>(BigIntegerKernel); - using var buffer = accelerator.Allocate1D(1); + var buffer = accelerator.Allocate1D(1); var extent = new Index1D((int)buffer.Length); kernel(extent, buffer.View); accelerator.Synchronize(); + buffer.Dispose(); return ProbeResult.Success( "System.Numerics.BigInteger", "Addition, subtraction, multiplication, division, and modulo compile on the CPU accelerator."); @@ -73,9 +74,10 @@ static ProbeResult ProbeEInteger(Accelerator accelerator) try { var kernel = accelerator.LoadAutoGroupedStreamKernel>(EIntegerKernel); - using var buffer = accelerator.Allocate1D(1); + var buffer = accelerator.Allocate1D(1); kernel(new Index1D((int)buffer.Length), buffer.View); accelerator.Synchronize(); + buffer.Dispose(); return ProbeResult.Success( "PeterO.Numbers.EInteger", "Addition, subtraction, multiplication, division, and remainder compile on the CPU accelerator."); @@ -93,7 +95,7 @@ static ProbeResult ProbeERational(Accelerator accelerator) try { var kernel = accelerator.LoadAutoGroupedStreamKernel>(ERationalKernel); - using var buffer = accelerator.Allocate1D(1); + var buffer = accelerator.Allocate1D(1); kernel(new Index1D((int)buffer.Length), buffer.View); accelerator.Synchronize(); @@ -101,6 +103,7 @@ static ProbeResult ProbeERational(Accelerator accelerator) details += hasRemainder ? " ERational exposes a remainder API that requires further investigation." : " ERational does not expose a remainder/modulo API."; + buffer.Dispose(); return ProbeResult.Success("PeterO.Numbers.ERational", details); } catch (Exception ex) @@ -120,9 +123,10 @@ static ProbeResult ProbeOpenNumericPrimesIsPrime(Accelerator accelerator) try { var kernel = accelerator.LoadAutoGroupedStreamKernel>(OpenNumericPrimesIsPrimeKernel); - using var buffer = accelerator.Allocate1D(4); + var buffer = accelerator.Allocate1D(4); kernel(new Index1D((int)buffer.Length), buffer.View); accelerator.Synchronize(); + buffer.Dispose(); return ProbeResult.Success( "Open.Numeric.Primes.Prime.Numbers.IsPrime", "IsPrime compiles and runs on the CPU accelerator kernel.");