From 6dd5cea79fff6298840a391b844773b73e22be0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marek=20Lema=C5=84czyk?= Date: Tue, 28 Oct 2025 02:01:28 +0100 Subject: [PATCH] Remove temporary by-divisor review documents --- EvenPerfectBitScanner/Program.cs | 14 +- .../Gpu/GpuPrimeLimiterTests.cs | 31 ++-- .../GpuWorkLimiterTests.cs | 26 ++- .../MersenneNumberDivisorCpuTesterTests.cs | 69 +++++--- .../MersenneNumberDivisorGpuTesterTests.cs | 124 +++++++++----- .../Cpu/MersenneCpuDivisorScanSession.cs | 9 -- ...MersenneNumberDivisorByDivisorCpuTester.cs | 31 +--- PerfectNumbers.Core/Gpu/GpuContextPool.cs | 13 -- PerfectNumbers.Core/Gpu/GpuKernelLease.cs | 49 ++---- .../Gpu/GpuPrimeWorkLimiter.cs | 17 +- PerfectNumbers.Core/Gpu/GpuWorkLimiter.cs | 19 +-- ...MersenneNumberDivisorByDivisorGpuTester.cs | 54 +------ .../Gpu/MersenneNumberIncrementalGpuTester.cs | 153 +++++++++--------- .../Gpu/MersenneNumberLucasLehmerGpuTester.cs | 14 +- .../Gpu/MersenneNumberOrderGpuTester.cs | 96 ++++++----- .../Gpu/MersenneNumberResidueGpuTester.cs | 2 +- PerfectNumbers.Core/Gpu/NttGpuMath.cs | 2 +- .../IMersenneNumberDivisorByDivisorTester.cs | 4 +- PerfectNumbers.Core/MersenneDivisorCycles.cs | 29 ++-- .../MersenneNumberDivisorByDivisorTester.cs | 12 +- PerfectNumbers.Core/MersenneNumberTester.cs | 87 +++++----- .../MersennePrimeFactorTester.cs | 2 +- .../PrimeOrderCalculator.Gpu.cs | 121 ++++++-------- .../PrimeOrderCalculator.Shared.cs | 72 ++++----- PerfectNumbers.Core/PrimeTester.cs | 19 +-- PerfectNumbers.Core/TaskThreadPool.cs | 27 +--- PerfectNumbers.Core/TextFileWriter.cs | 2 +- 27 files changed, 498 insertions(+), 600 deletions(-) diff --git a/EvenPerfectBitScanner/Program.cs b/EvenPerfectBitScanner/Program.cs index 767bac8..48ff15b 100644 --- a/EvenPerfectBitScanner/Program.cs +++ b/EvenPerfectBitScanner/Program.cs @@ -104,14 +104,7 @@ internal static class Program testPrimeCandidateLimit = Math.Max(1, threadCount) * 3; } - if (useByDivisor) - { - _byDivisorStartPrime = startPrimeProvided ? currentP : 0UL; - } - else - { - _byDivisorStartPrime = 0UL; - } + _byDivisorStartPrime = useByDivisor && startPrimeProvided ? currentP : 0UL; if (useByDivisor && !testMode && string.IsNullOrEmpty(filterFile)) { @@ -177,10 +170,7 @@ internal static class Program Console.WriteLine("Divisor cycles are ready"); - if (useByDivisor) - { - blockSize = 1; - } + blockSize = useByDivisor ? 1 : blockSize; CandidatesCalculator.Initialize(currentP); PrimeTesters = new ThreadLocal(() => new PrimeTester(), trackAllValues: true); diff --git a/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs b/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs index 3b605cc..6eda845 100644 --- a/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs +++ b/PerfectNumbers.Core.Tests/Gpu/GpuPrimeLimiterTests.cs @@ -18,22 +18,33 @@ 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(); - startedSecond.TrySetResult(true); + var second = GpuPrimeWorkLimiter.Acquire(); + try + { + startedSecond.TrySetResult(true); + } + finally + { + second.Dispose(); + } }); - // Give secondTask a moment to attempt acquire (it should block). - await Task.Delay(50); - startedSecond.Task.IsCompleted.Should().BeFalse(); - - // Release first after short delay, allowing second to continue. - // (Dispose at scope end, but we want to end now.) - first.Dispose(); + try + { + // Give secondTask a moment to attempt acquire (it should block). + await Task.Delay(50); + startedSecond.Task.IsCompleted.Should().BeFalse(); + } + finally + { + // Release first after the initial wait so the second task can proceed. + first.Dispose(); + } await secondTask.WaitAsync(TimeSpan.FromSeconds(1)); sw.Stop(); diff --git a/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs b/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs index 7e51ad4..961fcef 100644 --- a/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs +++ b/PerfectNumbers.Core.Tests/GpuWorkLimiterTests.cs @@ -17,19 +17,31 @@ 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(); - startedSecond.TrySetResult(true); + var second = GpuWorkLimiter.Acquire(); + try + { + startedSecond.TrySetResult(true); + } + finally + { + second.Dispose(); + } }); - await Task.Delay(50); - startedSecond.Task.IsCompleted.Should().BeFalse(); - - first.Dispose(); + try + { + await Task.Delay(50); + startedSecond.Task.IsCompleted.Should().BeFalse(); + } + finally + { + first.Dispose(); + } await secondTask.WaitAsync(TimeSpan.FromSeconds(1)); sw.Stop(); diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs index 8fbb41e..018911b 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorCpuTesterTests.cs @@ -30,18 +30,25 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(13UL); - using var session = tester.CreateDivisorSession(); - ulong[] primes = { 5UL, 7UL, 11UL, 13UL }; - byte[] hits = new byte[primes.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] primes = { 5UL, 7UL, 11UL, 13UL }; + byte[] hits = new byte[primes.Length]; - ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL)); - session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); + ulong cycle23 = MersenneDivisorCycles.CalculateCycleLength(23UL, MontgomeryDivisorData.FromModulus(23UL)); + session.CheckDivisor(23UL, MontgomeryDivisorData.FromModulus(23UL), cycle23, primes, hits); + hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); - Array.Fill(hits, (byte)0); - ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL)); - session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits); - hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 }); + Array.Fill(hits, (byte)0); + ulong cycle31 = MersenneDivisorCycles.CalculateCycleLength(31UL, MontgomeryDivisorData.FromModulus(31UL)); + session.CheckDivisor(31UL, MontgomeryDivisorData.FromModulus(31UL), cycle31, primes, hits); + hits.Should().ContainInOrder(new byte[] { 1, 0, 0, 0 }); + } + finally + { + session.Dispose(); + } } [Fact] @@ -51,15 +58,22 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(43UL); - using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; - byte[] hits = new byte[exponents.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; + byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); - session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); + session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Dispose(); + } } [Fact] @@ -69,15 +83,22 @@ public class MersenneNumberDivisorCpuTesterTests var tester = new MersenneNumberDivisorByDivisorCpuTester(); tester.ConfigureFromMaxPrime(61UL); - using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; - byte[] hits = new byte[exponents.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; + byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); - session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); + session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Dispose(); + } } } diff --git a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs index e19dc8e..44d820c 100644 --- a/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs +++ b/PerfectNumbers.Core.Tests/MersenneNumberDivisorGpuTesterTests.cs @@ -149,13 +149,20 @@ public class MersenneNumberDivisorGpuTesterTests var tester = new MersenneNumberDivisorByDivisorGpuTester(); tester.ConfigureFromMaxPrime(43UL); - using var session = tester.CreateDivisorSession(); - ulong[] primes = { 31UL, 37UL, 41UL }; - byte[] hits = new byte[primes.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] primes = { 31UL, 37UL, 41UL }; + byte[] hits = new byte[primes.Length]; - session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes, hits); + session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), 0UL, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0 }); + hits.Should().ContainInOrder(new byte[] { 0, 1, 0 }); + } + finally + { + session.Dispose(); + } } [Fact] @@ -165,18 +172,25 @@ public class MersenneNumberDivisorGpuTesterTests var tester = new MersenneNumberDivisorByDivisorGpuTester(); tester.ConfigureFromMaxPrime(43UL); - using var session = tester.CreateDivisorSession(); - ulong[] primes = { 31UL, 37UL, 41UL, 43UL }; - byte[] hits = new byte[primes.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] primes = { 31UL, 37UL, 41UL, 43UL }; + byte[] hits = new byte[primes.Length]; - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 }); + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); + session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); + hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0 }); - Array.Fill(hits, (byte)0); - ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL)); - session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); + Array.Fill(hits, (byte)0); + ulong cycle13367 = MersenneDivisorCycles.CalculateCycleLength(13367UL, MontgomeryDivisorData.FromModulus(13367UL)); + session.CheckDivisor(13367UL, MontgomeryDivisorData.FromModulus(13367UL), cycle13367, primes, hits); + hits.Should().ContainInOrder(new byte[] { 0, 0, 1, 0 }); + } + finally + { + session.Dispose(); + } } [Fact] @@ -190,14 +204,21 @@ public class MersenneNumberDivisorGpuTesterTests tester.ConfigureFromMaxPrime(47UL); - using var session = tester.CreateDivisorSession(); - ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; - byte[] hits = new byte[primes.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; + byte[] hits = new byte[primes.Length]; - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); + session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + } + finally + { + session.Dispose(); + } } [Fact] @@ -210,16 +231,23 @@ public class MersenneNumberDivisorGpuTesterTests }; tester.ConfigureFromMaxPrime(43UL); - using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; - byte[] hits = new byte[exponents.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] exponents = { 6UL, 7UL, 9UL, 10UL, 12UL }; + byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(7UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(7UL, divisorData); - session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); + session.CheckDivisor(7UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Dispose(); + } } [Fact] @@ -232,16 +260,23 @@ public class MersenneNumberDivisorGpuTesterTests }; tester.ConfigureFromMaxPrime(61UL); - using var session = tester.CreateDivisorSession(); - ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; - byte[] hits = new byte[exponents.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] exponents = { 10UL, 11UL, 20UL, 21UL, 30UL }; + byte[] hits = new byte[exponents.Length]; - MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL); - ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); + MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(11UL); + ulong cycle = MersenneDivisorCycles.CalculateCycleLength(11UL, divisorData); - session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); + session.CheckDivisor(11UL, divisorData, cycle, exponents, hits); - hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + hits.Should().Equal(new byte[] { 1, 0, 1, 0, 1 }); + } + finally + { + session.Dispose(); + } } [Fact] @@ -297,14 +332,21 @@ public class MersenneNumberDivisorGpuTesterTests tester.ConfigureFromMaxPrime(47UL); - using var session = tester.CreateDivisorSession(); - ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; - byte[] hits = new byte[primes.Length]; + var session = tester.CreateDivisorSession(); + try + { + ulong[] primes = { 31UL, 37UL, 41UL, 43UL, 47UL }; + byte[] hits = new byte[primes.Length]; - ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); - session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); + ulong cycle223 = MersenneDivisorCycles.CalculateCycleLength(223UL, MontgomeryDivisorData.FromModulus(223UL)); + session.CheckDivisor(223UL, MontgomeryDivisorData.FromModulus(223UL), cycle223, primes, hits); - hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + hits.Should().ContainInOrder(new byte[] { 0, 1, 0, 0, 0 }); + } + finally + { + session.Dispose(); + } } } diff --git a/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs b/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs index 2907fe6..02cc808 100644 --- a/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs +++ b/PerfectNumbers.Core/Cpu/MersenneCpuDivisorScanSession.cs @@ -2,11 +2,8 @@ namespace PerfectNumbers.Core.Cpu; internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession { - private bool _disposed; - public void Reset() { - _disposed = false; } public void CheckDivisor( @@ -67,12 +64,6 @@ internal sealed class MersenneCpuDivisorScanSession : IMersenneNumberDivisorByDi public void Dispose() { - if (_disposed) - { - throw new ObjectDisposedException(nameof(MersenneCpuDivisorScanSession), "Divisor scan session disposed twice."); - } - - _disposed = true; ThreadStaticPools.ReturnMersenneCpuDivisorSession(this); } } diff --git a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs index e68668d..2fb0fa5 100644 --- a/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs +++ b/PerfectNumbers.Core/Cpu/MersenneNumberDivisorByDivisorCpuTester.cs @@ -145,14 +145,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv byte step10; byte step8; - byte step5; byte step3; byte step7; byte step11; byte remainder10; byte remainder8; - byte remainder5; byte remainder3; byte remainder7; byte remainder11; @@ -161,14 +159,12 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { step10 = (byte)(stepLow % 10UL); step8 = (byte)(stepLow & 7UL); - step5 = (byte)(stepLow % 5UL); step3 = (byte)(stepLow % 3UL); step7 = (byte)(stepLow % 7UL); step11 = (byte)(stepLow % 11UL); remainder10 = (byte)(divisorLow % 10UL); remainder8 = (byte)(divisorLow & 7UL); - remainder5 = (byte)(divisorLow % 5UL); remainder3 = (byte)(divisorLow % 3UL); remainder7 = (byte)(divisorLow % 7UL); remainder11 = (byte)(divisorLow % 11UL); @@ -181,13 +177,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv decimalMask, step10, step8, - step5, step3, step7, step11, remainder10, remainder8, - remainder5, remainder3, remainder7, remainder11, @@ -197,21 +191,19 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv ulong divisorHigh = divisor.High; step10 = (byte)((((stepHigh % 10UL) * 6UL) + (stepLow % 10UL)) % 10UL); step8 = (byte)(stepLow % 8UL); - step5 = (byte)(((stepHigh % 5UL) + (stepLow % 5UL)) % 5UL); step3 = (byte)(((stepHigh % 3UL) + (stepLow % 3UL)) % 3UL); step7 = (byte)((((stepHigh % 7UL) * 2UL) + (stepLow % 7UL)) % 7UL); step11 = (byte)((((stepHigh % 11UL) * 5UL) + (stepLow % 11UL)) % 11UL); remainder10 = (byte)((((divisorHigh % 10UL) * 6UL) + (divisorLow % 10UL)) % 10UL); remainder8 = (byte)(divisorLow % 8UL); - remainder5 = (byte)(((divisorHigh % 5UL) + (divisorLow % 5UL)) % 5UL); remainder3 = (byte)(((divisorHigh % 3UL) + (divisorLow % 3UL)) % 3UL); remainder7 = (byte)((((divisorHigh % 7UL) * 2UL) + (divisorLow % 7UL)) % 7UL); remainder11 = (byte)((((divisorHigh % 11UL) * 5UL) + (divisorLow % 11UL)) % 11UL); while (divisor.CompareTo(limit) <= 0) { - bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0; + bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0; if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0) { ulong candidate = divisor.Low; @@ -261,7 +253,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv divisor.Add(step); remainder10 = AddMod10(remainder10, step10); remainder8 = AddMod8(remainder8, step8); - remainder5 = AddMod5(remainder5, step5); remainder3 = AddMod3(remainder3, step3); remainder7 = AddMod7(remainder7, step7); remainder11 = AddMod11(remainder11, step11); @@ -278,13 +269,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv ushort decimalMask, byte step10, byte step8, - byte step5, byte step3, byte step7, byte step11, byte remainder10, byte remainder8, - byte remainder5, byte remainder3, byte remainder7, byte remainder11, @@ -297,7 +286,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv { if (divisor <= limit) { - if (remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0 && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0) + if (remainder3 != 0 && remainder7 != 0 && remainder11 != 0 && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0) { MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); ulong divisorCycle; @@ -345,7 +334,7 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv while (true) { - bool passesSmallModuli = remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0; + bool passesSmallModuli = remainder3 != 0 && remainder7 != 0 && remainder11 != 0; if (passesSmallModuli && (remainder8 == 1 || remainder8 == 7) && ((decimalMask >> remainder10) & 1) != 0) { MontgomeryDivisorData divisorData = MontgomeryDivisorData.FromModulus(divisor); @@ -389,7 +378,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv divisor += step; remainder10 = AddMod10(remainder10, step10); remainder8 = AddMod8(remainder8, step8); - remainder5 = AddMod5(remainder5, step5); remainder3 = AddMod3(remainder3, step3); remainder7 = AddMod7(remainder7, step7); remainder11 = AddMod11(remainder11, step11); @@ -416,19 +404,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv return (byte)sum; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte AddMod5(byte value, byte delta) - { - const int Modulus = 5; - int sum = value + delta; - - if (sum >= Modulus) - { - sum -= Modulus; - } - - return (byte)sum; - } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte AddMod7(byte value, byte delta) diff --git a/PerfectNumbers.Core/Gpu/GpuContextPool.cs b/PerfectNumbers.Core/Gpu/GpuContextPool.cs index bbf8074..560dd7d 100644 --- a/PerfectNumbers.Core/Gpu/GpuContextPool.cs +++ b/PerfectNumbers.Core/Gpu/GpuContextPool.cs @@ -14,8 +14,6 @@ public static class GpuContextPool internal sealed class PooledContext { - private bool disposed; - public Context Context { get; } public Accelerator Accelerator { get; } public bool IsCpu { get; } @@ -34,10 +32,6 @@ public static class GpuContextPool public void Dispose() { - if (disposed) - { - return; - } // Ensure all cached GPU buffers for this accelerator are released. NttGpuMath.ClearCaches(Accelerator); @@ -48,7 +42,6 @@ public static class GpuContextPool Accelerator.Dispose(); Context.Dispose(); - disposed = true; } } @@ -131,7 +124,6 @@ public static class GpuContextPool public struct GpuContextLease { private readonly PooledContext _ctx; - private bool disposed; internal GpuContextLease(PooledContext ctx, object kernelInitLock) { @@ -150,13 +142,8 @@ public static class GpuContextPool public void Dispose() { - if (disposed) - { - return; - } Return(_ctx); - disposed = true; } } } diff --git a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs index f590795..66b2d7f 100644 --- a/PerfectNumbers.Core/Gpu/GpuKernelLease.cs +++ b/PerfectNumbers.Core/Gpu/GpuKernelLease.cs @@ -1,3 +1,4 @@ +using System; using System.Collections.Concurrent; using ILGPU; using ILGPU.Runtime; @@ -9,30 +10,28 @@ public sealed class GpuKernelLease { private static readonly ConcurrentQueue Pool = new(); - private IDisposable? _limiter; + private Action? _releaseLimiter; private GpuContextLease? _gpu; private KernelContainer? _kernels; private AcceleratorStream? _stream; private object? _executionLock; - private bool _disposed; private GpuKernelLease() { } - internal static GpuKernelLease Rent(IDisposable limiter, GpuContextLease gpu, KernelContainer kernels) + internal static GpuKernelLease Rent(GpuPrimeWorkLimiter.Lease limiter, GpuContextLease gpu, KernelContainer kernels) { if (!Pool.TryDequeue(out var lease)) { lease = new GpuKernelLease(); } - lease._limiter = limiter; + lease._releaseLimiter = limiter.Dispose; lease._gpu = gpu; lease._kernels = kernels; lease._stream = null; lease._executionLock = gpu.ExecutionLock; - lease._disposed = false; return lease; } @@ -158,35 +157,21 @@ public sealed class GpuKernelLease } } - private void Dispose(bool disposing) - { - if (_disposed) - { - return; - } - - if (disposing) - { - _stream?.Dispose(); - _stream = null; - - _gpu?.Dispose(); - _gpu = null; - - _executionLock = null; - - _limiter?.Dispose(); - _limiter = null; - } - - _kernels = null; - _disposed = true; - Pool.Enqueue(this); - } - public void Dispose() { - Dispose(disposing: false); + _stream?.Dispose(); + _stream = null; + + _gpu?.Dispose(); + _gpu = null; + + _executionLock = null; + + _releaseLimiter?.Invoke(); + _releaseLimiter = null; + + _kernels = null; + Pool.Enqueue(this); } public readonly struct ExecutionScope diff --git a/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs b/PerfectNumbers.Core/Gpu/GpuPrimeWorkLimiter.cs index 9602b0c..97c08ba 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. + // GPU jobs stop allocating Lease instances on every acquisition. var sem = _semaphore; sem.Wait(); - return new Releaser(sem); + return new Lease(sem); } public static void SetLimit(int value) @@ -36,25 +36,18 @@ public static class GpuPrimeWorkLimiter } } - private sealed class Releaser : IDisposable + public sealed class Lease { private readonly SemaphoreSlim _sem; - private bool _disposed; - public Releaser(SemaphoreSlim sem) + internal Lease(SemaphoreSlim sem) { _sem = sem; } public void Dispose() { - if (_disposed) - { - return; - } - _sem.Release(); - _disposed = true; } } } diff --git a/PerfectNumbers.Core/Gpu/GpuWorkLimiter.cs b/PerfectNumbers.Core/Gpu/GpuWorkLimiter.cs index a27b11c..684a25e 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 + // TODO: Replace the per-call Lease 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) @@ -32,7 +32,7 @@ public static class GpuWorkLimiter // Swap to a new semaphore for the new limit. We intentionally // do not dispose the old semaphore to avoid releasing handles - // that may still be in use by existing Releaser instances. + // that may still be in use by existing Lease instances. // TODO: Consolidate with GpuPrimeWorkLimiter so both limiters share a pooled SemaphoreSlim and avoid rebuilding the // limiter state whenever limits are adjusted from the CLI. _semaphore = new SemaphoreSlim(value, value); @@ -40,25 +40,18 @@ public static class GpuWorkLimiter } } - private sealed class Releaser : IDisposable + public sealed class Lease { private readonly SemaphoreSlim _sem; - private bool _disposed; - public Releaser(SemaphoreSlim sem) + internal Lease(SemaphoreSlim sem) { _sem = sem; } public void Dispose() { - if (_disposed) - { - return; - } - _sem.Release(); - _disposed = true; } } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index 5eaf421..f480afd 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -220,7 +220,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ulong stepValue = (ulong)twoP128; byte step10 = (byte)(stepValue % 10UL); byte step8 = (byte)(stepValue & 7UL); - byte step5 = (byte)(stepValue % 5UL); byte step3 = (byte)(stepValue % 3UL); byte step7 = (byte)(stepValue % 7UL); byte step11 = (byte)(stepValue % 11UL); @@ -229,7 +228,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ulong currentDivisorValue = (ulong)currentDivisor128; byte remainder10 = (byte)(currentDivisorValue % 10UL); byte remainder8 = (byte)(currentDivisorValue & 7UL); - byte remainder5 = (byte)(currentDivisorValue % 5UL); byte remainder3 = (byte)(currentDivisorValue % 3UL); byte remainder7 = (byte)(currentDivisorValue % 7UL); byte remainder11 = (byte)(currentDivisorValue % 11UL); @@ -275,14 +273,13 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv byte localRemainder10 = remainder10; byte localRemainder8 = remainder8; - byte localRemainder5 = remainder5; byte localRemainder3 = remainder3; byte localRemainder7 = remainder7; byte localRemainder11 = remainder11; for (int i = 0; i < chunkCount; i++) { - bool passesSmallModuli = localRemainder3 != 0 && localRemainder5 != 0 && localRemainder7 != 0 && localRemainder11 != 0; + bool passesSmallModuli = localRemainder3 != 0 && localRemainder7 != 0 && localRemainder11 != 0; if (passesSmallModuli && (localRemainder8 == 1 || localRemainder8 == 7) && ((decimalMask >> localRemainder10) & 1) != 0) { ulong divisorValue = (ulong)nextDivisor128; @@ -290,21 +287,16 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv ulong divisorCycle = ResolveDivisorCycle(divisorValue, prime, in montgomeryData); if (divisorCycle == prime) { - int targetIndex = admissibleCount; - divisorSpan[targetIndex] = divisorValue; - exponentSpan[targetIndex] = prime; - divisorDataSpan[targetIndex] = new GpuDivisorPartialData(divisorValue); - offsetSpan[targetIndex] = targetIndex; - countSpan[targetIndex] = 1; - cycleSpan[targetIndex] = divisorCycle; - admissibleCount++; + processedCount += (ulong)(i + 1); + lastProcessed = divisorValue; + coveredRange = true; + return true; } } nextDivisor128 += twoP128; localRemainder10 = AddMod10(localRemainder10, step10); localRemainder8 = AddMod8(localRemainder8, step8); - localRemainder5 = AddMod5(localRemainder5, step5); localRemainder3 = AddMod3(localRemainder3, step3); localRemainder7 = AddMod7(localRemainder7, step7); localRemainder11 = AddMod11(localRemainder11, step11); @@ -312,7 +304,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv remainder10 = localRemainder10; remainder8 = localRemainder8; - remainder5 = localRemainder5; remainder3 = localRemainder3; remainder7 = localRemainder7; remainder11 = localRemainder11; @@ -380,19 +371,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv return (byte)sum; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static byte AddMod5(byte value, byte delta) - { - const int Modulus = 5; - int sum = value + delta; - - if (sum >= Modulus) - { - sum -= Modulus; - } - - return (byte)sum; - } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static byte AddMod7(byte value, byte delta) @@ -490,7 +468,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private MemoryBuffer1D _hitBuffer = null!; private ulong[] _hostBuffer = null!; private int _capacity; - private bool _disposed; internal DivisorScanSession(MersenneNumberDivisorByDivisorGpuTester owner) { @@ -502,7 +479,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv internal void Reset() { - _disposed = false; } private void EnsureExecutionResourcesLocked(int requiredCapacity) @@ -549,11 +525,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan primes, Span hits) { - if (_disposed) - { - throw new ObjectDisposedException(nameof(DivisorScanSession)); - } - int length = primes.Length; // EvenPerfectBitScanner always supplies at least one exponent per divisor check, so the guard stays commented out. // if (length == 0) @@ -622,12 +593,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv int sentinel = int.MaxValue; firstHitView.CopyFromCPU(ref sentinel, 1); - var kernel = _kernel; - if (kernel is null) - { - Monitor.Exit(_lease.ExecutionLock); - throw new InvalidOperationException("GPU divisor kernel was not initialized."); - } + var kernel = _kernel!; kernel(1, divisorView, offsetView, countView, exponentView, cycleView, hitView, firstHitView); @@ -640,12 +606,6 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv public void Dispose() { - if (_disposed) - { - return; - } - - _disposed = true; _owner._sessionPool.Add(this); } } @@ -695,7 +655,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv private void ReturnBatchResources(BatchResources resources) => _resourcePool.Add(resources); - private sealed class BatchResources : IDisposable + private sealed class BatchResources { private Accelerator? _accelerator; private MemoryBuffer1D? _divisorDataBuffer; diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs index ccc1bd9..280288f 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberIncrementalGpuTester.cs @@ -47,88 +47,83 @@ public class MersenneNumberIncrementalGpuTester(GpuKernelType kernelType, bool u int currentSize; int i; UInt128 q = UInt128.Zero; - try + while (kStart <= maxK && Volatile.Read(ref isPrime)) { - while (kStart <= maxK && Volatile.Read(ref isPrime)) + remaining = maxK - kStart + UInt128.One; + currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; + Span orders = orderArray.AsSpan(0, currentSize); + // Precompute residue automaton bases for this batch + UInt128 q0 = twoP * kStart + UInt128.One; + if (_kernelType == GpuKernelType.Pow2Mod) { - remaining = maxK - kStart + UInt128.One; - currentSize = remaining > (UInt128)batchSize ? batchSize : (int)remaining; - Span orders = orderArray.AsSpan(0, currentSize); - // Precompute residue automaton bases for this batch - UInt128 q0 = twoP * kStart + UInt128.One; - if (_kernelType == GpuKernelType.Pow2Mod) - { - 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); - pow2Kernel( - stream, - currentSize, - exponent, - twoPGpu, - (GpuUInt128)kStart, - last, - divMul, - kernelArgs, - orderBuffer.View, - smallCyclesView, - primeViews.LastOne, - primeViews.LastSeven, - primeViews.LastOnePow2, - primeViews.LastSevenPow2); - } - else - { - q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); - incKernel( - stream, - currentSize, - exponent, - twoPGpu, - (GpuUInt128)kStart, - last, - divMul, - q0m10, - q0m8, - q0m3, - q0m5, - orderBuffer.View, - smallCyclesView); - } - - stream.Synchronize(); - orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize); - if (_kernelType == GpuKernelType.Pow2Mod) - { - for (i = 0; i < currentSize; i++) - { - if (orders[i] != 0UL) - { - Volatile.Write(ref isPrime, false); - break; - } - } - } - else - { - q = twoP * kStart + UInt128.One; - for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP) - { - if (orders[i] != 0UL && q.IsPrimeCandidate()) - { - Volatile.Write(ref isPrime, false); - } - } - } - - kStart += (UInt128)currentSize; + 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); + pow2Kernel( + stream, + currentSize, + exponent, + twoPGpu, + (GpuUInt128)kStart, + last, + divMul, + kernelArgs, + orderBuffer.View, + smallCyclesView, + primeViews.LastOne, + primeViews.LastSeven, + primeViews.LastOnePow2, + primeViews.LastSevenPow2); } + else + { + q0.Mod10_8_5_3(out ulong q0m10, out ulong q0m8, out ulong q0m5, out ulong q0m3); + incKernel( + stream, + currentSize, + exponent, + twoPGpu, + (GpuUInt128)kStart, + last, + divMul, + q0m10, + q0m8, + q0m3, + q0m5, + orderBuffer.View, + smallCyclesView); + } + + stream.Synchronize(); + orderBuffer.View.CopyToCPU(ref MemoryMarshal.GetReference(orders), currentSize); + if (_kernelType == GpuKernelType.Pow2Mod) + { + for (i = 0; i < currentSize; i++) + { + if (orders[i] != 0UL) + { + Volatile.Write(ref isPrime, false); + break; + } + } + } + else + { + q = twoP * kStart + UInt128.One; + for (i = 0; i < currentSize && Volatile.Read(ref isPrime); i++, q += twoP) + { + if (orders[i] != 0UL && q.IsPrimeCandidate()) + { + Volatile.Write(ref isPrime, false); + } + } + } + + kStart += (UInt128)currentSize; } - finally - { - ArrayPool.Shared.Return(orderArray); - orderBuffer.Dispose(); - execution.Dispose(); - gpuLease.Dispose(); - } + + ArrayPool.Shared.Return(orderArray); + orderBuffer.Dispose(); + execution.Dispose(); + gpuLease.Dispose(); } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs index 9734c2a..4a118c1 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberLucasLehmerGpuTester.cs @@ -152,18 +152,12 @@ public class MersenneNumberLucasLehmerGpuTester } GpuUInt128[] buffer = ArrayPool.Shared.Rent(count); - try + ComputeResidues(exponents, buffer.AsSpan(0, count)); + for (int i = 0; i < count; i++) { - ComputeResidues(exponents, buffer.AsSpan(0, count)); - for (int i = 0; i < count; i++) - { - results[i] = buffer[i].IsZero; - } - } - finally - { - ArrayPool.Shared.Return(buffer); + results[i] = buffer[i].IsZero; } + ArrayPool.Shared.Return(buffer); } private bool IsMersennePrimeNtt(ulong exponent, GpuUInt128 nttMod, GpuUInt128 primitiveRoot, bool runOnGpu) diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs index a4d0a60..2a355d7 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberOrderGpuTester.cs @@ -4,19 +4,19 @@ namespace PerfectNumbers.Core.Gpu; public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuOrder) { - private readonly GpuKernelType _kernelType = kernelType; - private readonly bool _useGpuOrder = useGpuOrder; + private readonly GpuKernelType _kernelType = kernelType; + private readonly bool _useGpuOrder = useGpuOrder; - public void Scan(ulong exponent, UInt128 twoP, LastDigit lastDigit, UInt128 maxK, ref bool isPrime) - { - var gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); - var execution = gpuLease.EnterExecutionScope(); - 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 gpuLease = GpuKernelPool.GetKernel(_useGpuOrder); + var execution = gpuLease.EnterExecutionScope(); + 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 var kernel = _kernelType switch { @@ -26,48 +26,42 @@ public class MersenneNumberOrderGpuTester(GpuKernelType kernelType, bool useGpuO var foundBuffer = accelerator.Allocate1D(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. exponent.Mod10_8_5_3Steps(out ulong step10, out ulong step8, out ulong step5, out ulong step3); - try + + UInt128 remaining; + int currentSize; + int found = 0; + while (kStart <= maxK && Volatile.Read(ref isPrime)) { - UInt128 remaining; - int currentSize; - int found = 0; - 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); + 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 + var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); + kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, + kernelArgs, foundBuffer.View, smallCyclesView); - // Ensure device has small cycles table for early rejections in order kernels - var smallCyclesView = GpuKernelPool.EnsureSmallCyclesOnDevice(accelerator); - kernel(stream, currentSize, exponent, (GpuUInt128)twoP, (GpuUInt128)kStart, last, divMul, - kernelArgs, foundBuffer.View, smallCyclesView); + stream.Synchronize(); + foundBuffer.View.CopyToCPU(ref found, 1); + if (found != 0) + { + Volatile.Write(ref isPrime, false); + break; + } - stream.Synchronize(); - foundBuffer.View.CopyToCPU(ref found, 1); - if (found != 0) - { - Volatile.Write(ref isPrime, false); - break; - } + kStart += (UInt128)currentSize; + } - kStart += (UInt128)currentSize; - } - } - finally - { - foundBuffer.Dispose(); - execution.Dispose(); - gpuLease.Dispose(); - } - - } + foundBuffer.Dispose(); + execution.Dispose(); + gpuLease.Dispose(); + } } diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs index dac5e5a..ca42ddd 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberResidueGpuTester.cs @@ -161,7 +161,7 @@ public class MersenneNumberResidueGpuTester(bool useGpuOrder) private void ReturnResources(Accelerator accelerator, ResidueResources resources) => _resourcePools.GetOrAdd(accelerator, static _ => new ConcurrentBag()).Add(resources); - private sealed class ResidueResources : IDisposable + private sealed class ResidueResources { internal ResidueResources(Accelerator accelerator, int capacity) { diff --git a/PerfectNumbers.Core/Gpu/NttGpuMath.cs b/PerfectNumbers.Core/Gpu/NttGpuMath.cs index 4f605ee..4dacb85 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/IMersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs index c0075e4..43be5dc 100644 --- a/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/IMersenneNumberDivisorByDivisorTester.cs @@ -16,8 +16,10 @@ public interface IMersenneNumberDivisorByDivisorTester void PrepareCandidates(in ReadOnlySpan primes, Span allowedMaxValues); - public interface IDivisorScanSession : IDisposable + public interface IDivisorScanSession { void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan primes, Span hits); + + void Dispose(); } } diff --git a/PerfectNumbers.Core/MersenneDivisorCycles.cs b/PerfectNumbers.Core/MersenneDivisorCycles.cs index 88f0aa5..bfcd972 100644 --- a/PerfectNumbers.Core/MersenneDivisorCycles.cs +++ b/PerfectNumbers.Core/MersenneDivisorCycles.cs @@ -137,20 +137,31 @@ public class MersenneDivisorCycles { // Binary pairs: (ulong divisor, ulong cycle) using var reader = new BinaryReader(compressor, Encoding.UTF8, leaveOpen: true); + Stream stream = reader.BaseStream; + byte[] buffer = new byte[16]; while (true) { - ulong d, c; - try + int read = 0; + Span span = buffer; + while (read < span.Length) { - d = reader.ReadUInt64(); - c = reader.ReadUInt64(); - } - catch (EndOfStreamException) - { - yield break; + int chunk = stream.Read(span[read..]); + if (chunk == 0) + { + if (read == 0) + { + yield break; + } + + throw new EndOfStreamException("Incomplete divisor-cycle entry."); + } + + read += chunk; } - yield return (d, c); + ulong divisor = BinaryPrimitives.ReadUInt64LittleEndian(span[..8]); + ulong cycle = BinaryPrimitives.ReadUInt64LittleEndian(span[8..]); + yield return (divisor, cycle); } } diff --git a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs index 131df86..423b186 100644 --- a/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs +++ b/PerfectNumbers.Core/MersenneNumberDivisorByDivisorTester.cs @@ -167,14 +167,10 @@ public static class MersenneNumberDivisorByDivisorTester if (primesToTest.Count == 0) { - if (applyStartPrime) - { - Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor."); - } - else - { - Console.WriteLine("No prime candidates remain for --mersenne=bydivisor after filtering."); - } + string message = applyStartPrime + ? $"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor." + : "No prime candidates remain for --mersenne=bydivisor after filtering."; + Console.WriteLine(message); return; } diff --git a/PerfectNumbers.Core/MersenneNumberTester.cs b/PerfectNumbers.Core/MersenneNumberTester.cs index cce3324..dcc17ea 100644 --- a/PerfectNumbers.Core/MersenneNumberTester.cs +++ b/PerfectNumbers.Core/MersenneNumberTester.cs @@ -152,56 +152,51 @@ public sealed class MersenneNumberTester( var stream = gpuLease.Stream; var orderKernel = gpuLease.OrderKernel!; - try + // Guard long-running kernels by chunking the warm-up across batches. + // Reuse the same ScanBatchSize knob used for scanning. + int batchSize = GpuConstants.ScanBatchSize; + ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; + int offset = 0; + while (offset < idx) { - // Guard long-running kernels by chunking the warm-up across batches. - // Reuse the same ScanBatchSize knob used for scanning. - int batchSize = GpuConstants.ScanBatchSize; - ulong divMul = (ulong)((((UInt128)1 << 64) - 1UL) / exponent) + 1UL; - int offset = 0; - while (offset < idx) + int count = Math.Min(batchSize, idx - offset); + var qBuffer = accelerator.Allocate1D(count); + var orderBuffer = accelerator.Allocate1D(count); + // Convert to device-friendly GpuUInt128 on the fly + var tmp = System.Buffers.ArrayPool.Shared.Rent(count); + for (int i = 0; i < count; i++) { - int count = Math.Min(batchSize, idx - offset); - var qBuffer = accelerator.Allocate1D(count); - var orderBuffer = accelerator.Allocate1D(count); - // Convert to device-friendly GpuUInt128 on the fly - var tmp = System.Buffers.ArrayPool.Shared.Rent(count); - for (int i = 0; i < count; i++) - { - tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i]; - } - // Copy only the populated elements to the device buffer to avoid overrun on pooled arrays. - qBuffer.View.CopyFromCPU(ref tmp[0], count); - System.Buffers.ArrayPool.Shared.Return(tmp); - orderKernel(stream, count, exponent, divMul, qBuffer.View, orderBuffer.View); - stream.Synchronize(); - - ulong[] orders = orderBuffer.GetAsArray1D(); - for (int i = 0; i < count; i++) - { - ulong order = orders[i]; - if (order == 0UL) - { - order = qs[offset + i].CalculateOrder(); - } - - if (useOrderCache) - { - OrderCache[qs[offset + i]] = order; - } - } - - offset += count; - orderBuffer.Dispose(); - qBuffer.Dispose(); + tmp[i] = (PerfectNumbers.Core.Gpu.GpuUInt128)qs[offset + i]; } + // Copy only the populated elements to the device buffer to avoid overrun on pooled arrays. + qBuffer.View.CopyFromCPU(ref tmp[0], count); + System.Buffers.ArrayPool.Shared.Return(tmp); + orderKernel(stream, count, exponent, divMul, qBuffer.View, orderBuffer.View); + stream.Synchronize(); + + ulong[] orders = orderBuffer.GetAsArray1D(); + for (int i = 0; i < count; i++) + { + ulong order = orders[i]; + if (order == 0UL) + { + order = qs[offset + i].CalculateOrder(); + } + + if (useOrderCache) + { + OrderCache[qs[offset + i]] = order; + } + } + + offset += count; + orderBuffer.Dispose(); + qBuffer.Dispose(); } - finally - { - execution.Dispose(); - gpuLease.Dispose(); - ArrayPool.Shared.Return(qs); - } + + execution.Dispose(); + gpuLease.Dispose(); + ArrayPool.Shared.Return(qs); } public bool IsMersennePrime(ulong exponent) diff --git a/PerfectNumbers.Core/MersennePrimeFactorTester.cs b/PerfectNumbers.Core/MersennePrimeFactorTester.cs index 6e2cc41..a160a3b 100644 --- a/PerfectNumbers.Core/MersennePrimeFactorTester.cs +++ b/PerfectNumbers.Core/MersennePrimeFactorTester.cs @@ -14,7 +14,7 @@ public static class MersennePrimeFactorTester [MethodImpl(MethodImplOptions.AggressiveInlining)] public static bool IsPrimeFactor(ulong p, ulong q, CancellationToken ct) { - if (q < 2UL || (q & 1UL) == 0) + if (q < 2UL) { return false; } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs index 4cb801c..4a9538b 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Gpu.cs @@ -9,86 +9,65 @@ namespace PerfectNumbers.Core; internal static partial class PrimeOrderCalculator { - private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2; + private static bool IsGpuPow2Allowed => s_pow2ModeInitialized && s_allowGpuPow2; - private const int GpuSmallPrimeFactorSlots = 64; + private const int GpuSmallPrimeFactorSlots = 64; - private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary counts, out int factorCount, out ulong remaining) - { - var primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots); - var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots); - Span primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); - Span exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); - // primeBuffer.Clear(); - // exponentBuffer.Clear(); - remaining = value; + private static bool TryPopulateSmallPrimeFactorsGpu(ulong value, uint limit, Dictionary counts, out int factorCount, out ulong remaining) + { + var primeBufferArray = ThreadStaticPools.UlongPool.Rent(GpuSmallPrimeFactorSlots); + var exponentBufferArray = ThreadStaticPools.IntPool.Rent(GpuSmallPrimeFactorSlots); + Span primeBuffer = primeBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); + Span exponentBuffer = exponentBufferArray.AsSpan(0, GpuSmallPrimeFactorSlots); + remaining = value; - try - { - var lease = GpuKernelPool.GetKernel(useGpuOrder: true); - var execution = lease.EnterExecutionScope(); - try - { - Accelerator accelerator = lease.Accelerator; - AcceleratorStream stream = lease.Stream; + var lease = GpuKernelPool.GetKernel(useGpuOrder: true); + var execution = lease.EnterExecutionScope(); + Accelerator accelerator = lease.Accelerator; + AcceleratorStream stream = lease.Stream; - SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(accelerator); - SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(accelerator, GpuSmallPrimeFactorSlots); - // scratch.Clear(); + SmallPrimeFactorTables tables = GpuKernelPool.EnsureSmallPrimeFactorTables(accelerator); + SmallPrimeFactorScratch scratch = GpuKernelPool.EnsureSmallPrimeFactorScratch(accelerator, GpuSmallPrimeFactorSlots); - var kernel = lease.SmallPrimeFactorKernel; - kernel( - stream, - 1, - value, - limit, - tables.PrimesView, - tables.SquaresView, - tables.Count, - scratch.PrimeSlots.View, - scratch.ExponentSlots.View, - scratch.CountSlot.View, - scratch.RemainingSlot.View); + var kernel = lease.SmallPrimeFactorKernel; + kernel( + stream, + 1, + value, + limit, + tables.PrimesView, + tables.SquaresView, + tables.Count, + scratch.PrimeSlots.View, + scratch.ExponentSlots.View, + scratch.CountSlot.View, + scratch.RemainingSlot.View); - stream.Synchronize(); + stream.Synchronize(); - factorCount = 0; - scratch.CountSlot.View.CopyToCPU(ref factorCount, 1); - factorCount = Math.Min(factorCount, GpuSmallPrimeFactorSlots); + factorCount = 0; + scratch.CountSlot.View.CopyToCPU(ref factorCount, 1); + factorCount = Math.Min(factorCount, GpuSmallPrimeFactorSlots); - if (factorCount > 0) - { - scratch.PrimeSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(primeBuffer), factorCount); - scratch.ExponentSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentBuffer), factorCount); - } + if (factorCount > 0) + { + scratch.PrimeSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(primeBuffer), factorCount); + scratch.ExponentSlots.View.CopyToCPU(ref MemoryMarshal.GetReference(exponentBuffer), factorCount); + } - scratch.RemainingSlot.View.CopyToCPU(ref remaining, 1); + scratch.RemainingSlot.View.CopyToCPU(ref remaining, 1); - for (int i = 0; i < factorCount; i++) - { - ulong primeValue = primeBuffer[i]; - int exponent = exponentBuffer[i]; - // This will never happen in production code - // if (primeValue == 0UL || exponent == 0) - // { - // continue; - // } + for (int i = 0; i < factorCount; i++) + { + ulong primeValue = primeBuffer[i]; + int exponent = exponentBuffer[i]; + counts.Add(primeValue, exponent); + } - counts.Add(primeValue, exponent); - } - - return true; - } - finally - { - execution.Dispose(); - lease.Dispose(); - } - } - finally - { - ThreadStaticPools.UlongPool.Return(primeBufferArray); - ThreadStaticPools.IntPool.Return(exponentBufferArray); - } - } + execution.Dispose(); + lease.Dispose(); + ThreadStaticPools.UlongPool.Return(primeBufferArray); + ThreadStaticPools.IntPool.Return(exponentBufferArray); + return true; + } } diff --git a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs index 24ba684..2f923c6 100644 --- a/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs +++ b/PerfectNumbers.Core/PrimeOrderCalculator.Shared.cs @@ -185,55 +185,49 @@ internal static partial class PrimeOrderCalculator private static UInt128 ExponentLoweringWide(UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors) { - ArrayPool pool = ThreadStaticPools.FactorEntry128Pool; - ReadOnlySpan factorSpan = factors.Factors; - int length = factors.Count; - // TODO: This should never trigger from production code - check - // if (length == 0) - // { - // return order; - // } - FactorEntry128[]? tempArray = pool.Rent(length + 1); - try + ArrayPool pool = ThreadStaticPools.FactorEntry128Pool; + ReadOnlySpan factorSpan = factors.Factors; + int length = factors.Count; + // TODO: This should never trigger from production code - check + // if (length == 0) + // { + // return order; + // } + FactorEntry128[] tempArray = pool.Rent(length + 1); + + Span buffer = tempArray.AsSpan(0, length); + factorSpan.CopyTo(buffer); + + if (!factors.FullyFactored && factors.Cofactor > UInt128.One && IsPrimeWide(factors.Cofactor)) { + buffer[length] = new FactorEntry128(factors.Cofactor, 1); + length++; + } - Span buffer = tempArray.AsSpan(0, length); - factorSpan.CopyTo(buffer); + buffer.Slice(0, length).Sort(static (a, b) => a.Value.CompareTo(b.Value)); - if (!factors.FullyFactored && factors.Cofactor > UInt128.One && IsPrimeWide(factors.Cofactor)) + for (int i = 0; i < length; i++) + { + UInt128 primeFactor = buffer[i].Value; + int exponent = buffer[i].Exponent; + for (int iteration = 0; iteration < exponent; iteration++) { - buffer[length] = new FactorEntry128(factors.Cofactor, 1); - length++; - } - - buffer.Slice(0, length).Sort(static (a, b) => a.Value.CompareTo(b.Value)); - - for (int i = 0; i < length; i++) - { - UInt128 primeFactor = buffer[i].Value; - int exponent = buffer[i].Exponent; - for (int iteration = 0; iteration < exponent; iteration++) + if ((order % primeFactor) == UInt128.Zero) { - if ((order % primeFactor) == UInt128.Zero) + UInt128 reduced = order / primeFactor; + if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One) { - UInt128 reduced = order / primeFactor; - if (Pow2ModWide(reduced, prime, divisorData) == UInt128.One) - { - order = reduced; - continue; - } + order = reduced; + continue; } - - break; } - } - return order; - } - finally - { - pool.Return(tempArray, clearArray: false); + break; + } } + + pool.Return(tempArray, clearArray: false); + return order; } private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config) diff --git a/PerfectNumbers.Core/PrimeTester.cs b/PerfectNumbers.Core/PrimeTester.cs index dd15c4e..d64cc30 100644 --- a/PerfectNumbers.Core/PrimeTester.cs +++ b/PerfectNumbers.Core/PrimeTester.cs @@ -149,9 +149,8 @@ public sealed class PrimeTester internal static class PrimeTesterGpuContextPool { - internal sealed class PooledContext : IDisposable + internal sealed class PooledContext { - private bool _disposed; public Context Context { get; } @@ -167,15 +166,9 @@ public sealed class PrimeTester public void Dispose() { - if (_disposed) - { - return; - } - PrimeTester.ClearGpuCaches(Accelerator); Accelerator.Dispose(); Context.Dispose(); - _disposed = true; } } @@ -209,7 +202,7 @@ public sealed class PrimeTester Pool.Enqueue(ctx); } - internal struct PrimeTesterGpuContextLease : IDisposable + internal struct PrimeTesterGpuContextLease { private PooledContext? _ctx; @@ -224,11 +217,7 @@ public sealed class PrimeTester public void Dispose() { - if (_ctx is { } ctx) - { - Return(ctx); - _ctx = null; - } + Return(_ctx ?? throw new InvalidOperationException("GPU context lease is not initialized.")); } } } @@ -257,7 +246,7 @@ public sealed class PrimeTester 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/TaskThreadPool.cs b/PerfectNumbers.Core/TaskThreadPool.cs index 81fdee8..a226c23 100644 --- a/PerfectNumbers.Core/TaskThreadPool.cs +++ b/PerfectNumbers.Core/TaskThreadPool.cs @@ -6,7 +6,7 @@ using System.Threading.Tasks; namespace PerfectNumbers.Core; -internal sealed class TaskThreadPool : IDisposable +internal sealed class TaskThreadPool { private readonly struct WorkItem { @@ -22,8 +22,6 @@ internal sealed class TaskThreadPool : IDisposable private readonly BlockingCollection _pendingTasks; private readonly Thread[] _threads; private readonly Action _taskExecutor; - private int _disposed; - public TaskThreadPool(int minimumThreads, Action taskExecutor) { if (minimumThreads <= 0) @@ -57,22 +55,18 @@ internal sealed class TaskThreadPool : IDisposable throw new ArgumentNullException(nameof(task)); } - if (Volatile.Read(ref _disposed) != 0) - { - throw new ObjectDisposedException(nameof(TaskThreadPool)); - } - try { _pendingTasks.Add(new WorkItem(task)); } - catch (InvalidOperationException ex) when (Volatile.Read(ref _disposed) != 0) + catch (Exception ex) { - throw new ObjectDisposedException(nameof(TaskThreadPool), ex); - } - catch (ObjectDisposedException ex) - { - throw new ObjectDisposedException(nameof(TaskThreadPool), ex); + if (ex is InvalidOperationException || ex is ObjectDisposedException) + { + throw new ObjectDisposedException(nameof(TaskThreadPool), ex); + } + + throw; } } @@ -97,11 +91,6 @@ internal sealed class TaskThreadPool : IDisposable public void Dispose() { - if (Interlocked.Exchange(ref _disposed, 1) != 0) - { - return; - } - _pendingTasks.CompleteAdding(); foreach (Thread thread in _threads) diff --git a/PerfectNumbers.Core/TextFileWriter.cs b/PerfectNumbers.Core/TextFileWriter.cs index 110ef1b..08061a9 100644 --- a/PerfectNumbers.Core/TextFileWriter.cs +++ b/PerfectNumbers.Core/TextFileWriter.cs @@ -6,7 +6,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;