Pools made thread static. Various small optimizations. "in" parameter added to read-only method parameters

This commit is contained in:
mlemanczyk committed 2025-10-15 02:17:13 +02:00
1 parent 7c71d5165e
commit d3695a3dc3
10 files changed
+1713 -1775

No files matched your search

@@ -70,7 +70,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
return new DummySession(); return new DummySession();
} }
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues) public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{ {
for (int i = 0; i < primes.Length; i++) for (int i = 0; i < primes.Length; i++)
{ {
@@ -80,7 +80,7 @@ public class MersenneNumberDivisorByDivisorTesterTests
private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession private sealed class DummySession : IMersenneNumberDivisorByDivisorTester.IDivisorScanSession
{ {
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits) public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
{ {
throw new NotSupportedException(); throw new NotSupportedException();
} }
@@ -1,18 +1,14 @@
using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Runtime.CompilerServices;
using System.Numerics;
using PerfectNumbers.Core;
namespace PerfectNumbers.Core.Cpu; namespace PerfectNumbers.Core.Cpu;
public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDivisorByDivisorTester
{ {
private readonly object _sync = new(); private readonly object _sync = new();
private readonly ConcurrentBag<DivisorScanSession> _sessionPool = new(); private readonly ConcurrentBag<DivisorScanSession> _sessionPool = [];
private ulong _divisorLimit; private ulong _divisorLimit;
private ulong _lastStatusDivisor; private ulong _lastStatusDivisor;
private bool _isConfigured;
private int _batchSize = 1_024; private int _batchSize = 1_024;
@@ -24,55 +20,25 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public void ConfigureFromMaxPrime(ulong maxPrime) public void ConfigureFromMaxPrime(ulong maxPrime)
{ {
lock (_sync) _divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
{ _lastStatusDivisor = 0UL;
_divisorLimit = ComputeDivisorLimitFromMaxPrime(maxPrime);
_lastStatusDivisor = 0UL;
_isConfigured = true;
}
} }
public ulong DivisorLimit public ulong DivisorLimit
{ {
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get get
{ {
lock (_sync) return _divisorLimit;
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return _divisorLimit;
}
} }
} }
public ulong GetAllowedMaxDivisor(ulong prime) [MethodImpl(MethodImplOptions.AggressiveInlining)]
public ulong GetAllowedMaxDivisor(ulong prime) => ComputeAllowedMaxDivisor(prime, _divisorLimit);
public bool IsPrime(ulong prime, out bool divisorsExhausted)
{ {
lock (_sync) ulong allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
return ComputeAllowedMaxDivisor(prime, _divisorLimit);
}
}
public bool IsPrime(ulong prime, out bool divisorsExhausted)
{
ulong allowedMax;
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
allowedMax = ComputeAllowedMaxDivisor(prime, _divisorLimit);
}
if (allowedMax < 3UL) if (allowedMax < 3UL)
{ {
@@ -81,13 +47,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
} }
ulong processedCount; ulong processedCount;
ulong lastProcessed;
bool processedAll; bool processedAll;
bool composite = CheckDivisors( bool composite = CheckDivisors(
prime, prime,
allowedMax, allowedMax,
out lastProcessed,
out processedAll, out processedAll,
out processedCount); out processedCount);
@@ -109,26 +73,11 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
return true; return true;
} }
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues) public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{ {
if (allowedMaxValues.Length < primes.Length) ulong divisorLimit = _divisorLimit;
{ int length = primes.Length;
throw new ArgumentException("allowedMaxValues span must be at least as long as primes span.", nameof(allowedMaxValues)); for (int index = 0; index < length; index++)
}
ulong divisorLimit;
lock (_sync)
{
if (!_isConfigured)
{
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester.");
}
divisorLimit = _divisorLimit;
}
for (int index = 0; index < primes.Length; index++)
{ {
allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit); allowedMaxValues[index] = ComputeAllowedMaxDivisor(primes[index], divisorLimit);
} }
@@ -136,21 +85,13 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession() public IMersenneNumberDivisorByDivisorTester.IDivisorScanSession CreateDivisorSession()
{ {
lock (_sync) if (_sessionPool.TryTake(out DivisorScanSession? session))
{ {
if (!_isConfigured) session.Reset();
{ return session;
throw new InvalidOperationException("ConfigureFromMaxPrime must be called before using the tester."); }
}
if (_sessionPool.TryTake(out DivisorScanSession? session)) return new DivisorScanSession(this);
{
session.Reset();
return session;
}
return new DivisorScanSession(this);
}
} }
private void ReturnSession(DivisorScanSession session) private void ReturnSession(DivisorScanSession session)
@@ -161,11 +102,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
private bool CheckDivisors( private bool CheckDivisors(
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
out ulong lastProcessed,
out bool processedAll, out bool processedAll,
out ulong processedCount) out ulong processedCount)
{ {
lastProcessed = 0UL;
processedCount = 0UL; processedCount = 0UL;
processedAll = false; processedAll = false;
@@ -214,7 +153,6 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{ {
ulong candidate = (ulong)divisor; ulong candidate = (ulong)divisor;
processedCount++; processedCount++;
lastProcessed = candidate;
bool admissible = lastIsSeven bool admissible = lastIsSeven
? (remainder10 == 3 || remainder10 == 7 || remainder10 == 9) ? (remainder10 == 3 || remainder10 == 7 || remainder10 == 9)
@@ -348,13 +286,8 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
_disposed = false; _disposed = false;
} }
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits) public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
{ {
if (_disposed)
{
throw new ObjectDisposedException(nameof(DivisorScanSession));
}
int length = primes.Length; int length = primes.Length;
if (length == 0) if (length == 0)
{ {
+8
View File
@@ -0,0 +1,8 @@
namespace PerfectNumbers.Core;
public readonly struct FactorEntry128(in UInt128 value, int exponent)
{
public readonly UInt128 Value = value;
public readonly int Exponent = exponent;
}
@@ -148,7 +148,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
return true; return true;
} }
public void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues) public void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues)
{ {
if (allowedMaxValues.Length < primes.Length) if (allowedMaxValues.Length < primes.Length)
{ {
@@ -583,7 +583,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
_disposed = false; _disposed = false;
} }
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits) public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits)
{ {
if (_disposed) if (_disposed)
{ {
@@ -16,10 +16,10 @@ public interface IMersenneNumberDivisorByDivisorTester
IDivisorScanSession CreateDivisorSession(); IDivisorScanSession CreateDivisorSession();
void PrepareCandidates(ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues); void PrepareCandidates(in ReadOnlySpan<ulong> primes, Span<ulong> allowedMaxValues);
public interface IDivisorScanSession : IDisposable public interface IDivisorScanSession : IDisposable
{ {
void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits); void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, in ReadOnlySpan<ulong> primes, Span<byte> hits);
} }
} }
@@ -10,129 +10,126 @@ namespace PerfectNumbers.Core;
public static class MersenneNumberDivisorByDivisorTester public static class MersenneNumberDivisorByDivisorTester
{ {
public static void Run( public static void Run(
List<ulong> candidates, List<ulong> candidates,
IMersenneNumberDivisorByDivisorTester tester, IMersenneNumberDivisorByDivisorTester tester,
Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults, Dictionary<ulong, (bool DetailedCheck, bool PassedAllTests)>? previousResults,
ulong startPrime, ulong startPrime,
Action markComposite, Action markComposite,
Action clearComposite, Action clearComposite,
Action<ulong, bool, bool, bool> printResult, Action<ulong, bool, bool, bool> printResult,
int threadCount) int threadCount)
{ {
if (candidates.Count == 0) if (candidates.Count == 0)
{ {
Console.WriteLine("No candidates were provided for --mersenne=bydivisor."); Console.WriteLine("No candidates were provided for --mersenne=bydivisor.");
return; return;
} }
_ = threadCount; _ = threadCount;
candidates.Sort(); candidates.Sort();
Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates); Span<ulong> candidateSpan = CollectionsMarshal.AsSpan(candidates);
int candidateCount = candidateSpan.Length; int candidateCount = candidateSpan.Length;
bool applyStartPrime = startPrime > 0UL; bool applyStartPrime = startPrime > 0UL;
int skippedByPreviousResults = 0; int skippedByPreviousResults = 0;
if (previousResults is not null && previousResults.Count > 0) ArrayPool<ulong> pool = ThreadStaticPools.UlongPool;
{ if (previousResults is not null && previousResults.Count > 0)
int recordedCount = previousResults.Count; {
ulong[] recordedCandidates = ArrayPool<ulong>.Shared.Rent(recordedCount); int recordedCount = previousResults.Count;
try ulong[] recordedCandidates = pool.Rent(recordedCount);
{
Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
int recordedIndex = 0;
foreach (ulong value in previousResults.Keys)
{
recordedSpan[recordedIndex++] = value;
}
recordedSpan.Sort(); Span<ulong> recordedSpan = recordedCandidates.AsSpan(0, recordedCount);
int recordedIndex = 0;
foreach (ulong value in previousResults.Keys)
{
recordedSpan[recordedIndex++] = value;
}
int writeIndex = 0; recordedSpan.Sort();
int skipIndex = 0;
for (int readIndex = 0; readIndex < candidateCount; readIndex++) int writeIndex = 0;
{ int skipIndex = 0;
ulong candidate = candidateSpan[readIndex];
while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate) for (int readIndex = 0; readIndex < candidateCount; readIndex++)
{ {
skipIndex++; ulong candidate = candidateSpan[readIndex];
}
if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate) while (skipIndex < recordedCount && recordedSpan[skipIndex] < candidate)
{ {
skippedByPreviousResults++; skipIndex++;
skipIndex++; }
continue;
}
candidateSpan[writeIndex++] = candidate; if (skipIndex < recordedCount && recordedSpan[skipIndex] == candidate)
} {
skippedByPreviousResults++;
skipIndex++;
continue;
}
if (writeIndex < candidateCount) candidateSpan[writeIndex++] = candidate;
{ }
candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
candidateSpan = CollectionsMarshal.AsSpan(candidates);
candidateCount = candidateSpan.Length;
}
}
finally
{
ArrayPool<ulong>.Shared.Return(recordedCandidates, clearArray: true);
}
}
if (skippedByPreviousResults > 0) if (writeIndex < candidateCount)
{ {
Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results."); candidates.RemoveRange(writeIndex, candidateCount - writeIndex);
} candidateSpan = CollectionsMarshal.AsSpan(candidates);
candidateCount = candidateSpan.Length;
}
int startIndex = 0; pool.Return(recordedCandidates, clearArray: true);
if (applyStartPrime) }
{
startIndex = candidates.BinarySearch(startPrime);
if (startIndex < 0)
{
startIndex = ~startIndex;
}
else
{
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime)
{
startIndex--;
}
}
}
List<ulong> primesToTest = candidates; if (skippedByPreviousResults > 0)
Span<ulong> primesSpan = CollectionsMarshal.AsSpan(primesToTest); {
ulong maxPrime = 0UL; Console.WriteLine($"Skipped {skippedByPreviousResults.ToString(CultureInfo.InvariantCulture)} candidates excluded by previous results.");
int primeWriteIndex = 0; }
int startIndex = 0;
if (applyStartPrime)
{
startIndex = candidates.BinarySearch(startPrime);
if (startIndex < 0)
{
startIndex = ~startIndex;
}
else
{
while (startIndex > 0 && candidates[startIndex - 1] >= startPrime)
{
startIndex--;
}
}
}
List<ulong> primesToTest = candidates;
Span<ulong> primesSpan = CollectionsMarshal.AsSpan(primesToTest);
ulong maxPrime = 0UL;
int primeWriteIndex = 0;
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already. // This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
// We don't need to additionally check it. // We don't need to additionally check it.
// using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator(); // using IEnumerator<ulong> primeEnumerator = Prime.Numbers.GetEnumerator();
// bool hasPrime = primeEnumerator.MoveNext(); // bool hasPrime = primeEnumerator.MoveNext();
// ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL; // ulong currentPrime = hasPrime ? primeEnumerator.Current : 0UL;
for (int index = startIndex; index < candidateCount; index++) for (int index = startIndex; index < candidateCount; index++)
{ {
ulong candidate = primesSpan[index]; ulong candidate = primesSpan[index];
if (candidate <= 1UL) if (candidate <= 1UL)
{ {
markComposite(); markComposite();
printResult(candidate, false, false, false); printResult(candidate, false, false, false);
continue; continue;
} }
// This implementation is terribly slow, while this method expect prime p given as --filter-p input already. // This implementation is terribly slow, while this method expect prime p given as --filter-p input already.
// We don't need to additionally check it. // We don't need to additionally check it.
// while (hasPrime && currentPrime < candidate) // while (hasPrime && currentPrime < candidate)
// { // {
// hasPrime = primeEnumerator.MoveNext(); // hasPrime = primeEnumerator.MoveNext();
@@ -154,123 +151,118 @@ public static class MersenneNumberDivisorByDivisorTester
primesSpan[primeWriteIndex++] = candidate; primesSpan[primeWriteIndex++] = candidate;
if (candidate > maxPrime) if (candidate > maxPrime)
{ {
maxPrime = candidate; maxPrime = candidate;
} }
} }
if (primeWriteIndex < primesToTest.Count) if (primeWriteIndex < primesToTest.Count)
{ {
primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex); primesToTest.RemoveRange(primeWriteIndex, primesToTest.Count - primeWriteIndex);
} }
if (primesToTest.Count == 0) if (primesToTest.Count == 0)
{ {
if (applyStartPrime) if (applyStartPrime)
{ {
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor."); Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor.");
} }
else else
{ {
Console.WriteLine("No prime candidates remain for --mersenne=bydivisor after filtering."); Console.WriteLine("No prime candidates remain for --mersenne=bydivisor after filtering.");
} }
return; return;
} }
if (maxPrime <= 1UL) if (maxPrime <= 1UL)
{ {
Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor."); Console.WriteLine("The filter specified by --filter-p must contain at least one prime exponent greater than 1 for --mersenne=bydivisor.");
return; return;
} }
tester.ConfigureFromMaxPrime(maxPrime); tester.ConfigureFromMaxPrime(maxPrime);
int primeCount = primesToTest.Count; int primeCount = primesToTest.Count;
ulong[] primeBatch = ArrayPool<ulong>.Shared.Rent(primeCount); ulong[] primeBatch = pool.Rent(primeCount);
ulong[] allowedMaxBatch = ArrayPool<ulong>.Shared.Rent(primeCount); ulong[] allowedMaxBatch = pool.Rent(primeCount);
List<ulong> filteredPrimes = new(primeCount); List<ulong> filteredPrimes = new(primeCount);
try Span<ulong> primeSpan = primeBatch.AsSpan(0, primeCount);
{ Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, primeCount);
Span<ulong> primeSpan = primeBatch.AsSpan(0, primeCount);
Span<ulong> allowedMaxSpan = allowedMaxBatch.AsSpan(0, primeCount);
for (int i = 0; i < primeCount; i++) for (int i = 0; i < primeCount; i++)
{ {
primeSpan[i] = primesToTest[i]; primeSpan[i] = primesToTest[i];
} }
tester.PrepareCandidates(primeSpan, allowedMaxSpan); tester.PrepareCandidates(primeSpan, allowedMaxSpan);
for (int i = 0; i < primeCount; i++) for (int i = 0; i < primeCount; i++)
{ {
ulong prime = primeSpan[i]; ulong prime = primeSpan[i];
ulong allowedMax = allowedMaxSpan[i]; ulong allowedMax = allowedMaxSpan[i];
if (allowedMax < 3UL) if (allowedMax < 3UL)
{ {
clearComposite(); clearComposite();
printResult(prime, true, true, true); printResult(prime, true, true, true);
continue; continue;
} }
filteredPrimes.Add(prime); filteredPrimes.Add(prime);
} }
}
finally
{
ArrayPool<ulong>.Shared.Return(allowedMaxBatch, clearArray: true);
ArrayPool<ulong>.Shared.Return(primeBatch, clearArray: true);
}
if (filteredPrimes.Count == 0) pool.Return(allowedMaxBatch, clearArray: true);
{ pool.Return(primeBatch, clearArray: true);
if (applyStartPrime)
{
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor.");
}
return; if (filteredPrimes.Count == 0)
} {
if (applyStartPrime)
{
Console.WriteLine($"No primes greater than or equal to {startPrime.ToString(CultureInfo.InvariantCulture)} were found for --mersenne=bydivisor.");
}
int workerCount = threadCount <= 0 ? Environment.ProcessorCount : threadCount; return;
if (workerCount < 1) }
{
workerCount = 1;
}
void ProcessPrime(ulong prime) int workerCount = threadCount <= 0 ? Environment.ProcessorCount : threadCount;
{ if (workerCount < 1)
bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted); {
workerCount = 1;
}
if (!isPrime) void ProcessPrime(ulong prime)
{ {
markComposite(); bool isPrime = tester.IsPrime(prime, out bool divisorsExhausted);
printResult(prime, true, true, false);
return;
}
clearComposite(); if (!isPrime)
printResult(prime, true, divisorsExhausted, true); {
} markComposite();
printResult(prime, true, true, false);
return;
}
if (workerCount == 1) clearComposite();
{ printResult(prime, true, divisorsExhausted, true);
foreach (ulong prime in filteredPrimes) }
{
ProcessPrime(prime);
}
}
else
{
ParallelOptions options = new()
{
MaxDegreeOfParallelism = workerCount
};
Parallel.ForEach(filteredPrimes, options, ProcessPrime); if (workerCount == 1)
} {
} foreach (ulong prime in filteredPrimes)
{
ProcessPrime(prime);
}
}
else
{
ParallelOptions options = new()
{
MaxDegreeOfParallelism = workerCount
};
Parallel.ForEach(filteredPrimes, options, ProcessPrime);
}
}
} }
+3 -3
View File
@@ -38,8 +38,8 @@ namespace PerfectNumbers.Core
return instance; return instance;
} }
public static readonly PartialFactorResult Empty = new PartialFactorResult public static readonly PartialFactorResult Empty = new()
{ {
Cofactor = 1UL, Cofactor = 1UL,
FullyFactored = true, FullyFactored = true,
}; };
@@ -70,7 +70,7 @@ namespace PerfectNumbers.Core
if (factors is not null) if (factors is not null)
{ {
Factors = null; Factors = null;
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false); ThreadStaticPools.FactorEntryPool.Return(factors, clearArray: false);
} }
_next = s_poolHead; _next = s_poolHead;
+69 -100
View File
@@ -10,10 +10,10 @@ namespace PerfectNumbers.Core;
internal static partial class PrimeOrderCalculator internal static partial class PrimeOrderCalculator
{ {
public static UInt128 Calculate( public static UInt128 Calculate(
UInt128 prime, in UInt128 prime,
UInt128? previousOrder, in UInt128? previousOrder,
PrimeOrderSearchConfig config, in PrimeOrderSearchConfig config,
PrimeOrderHeuristicDevice device) in PrimeOrderHeuristicDevice device)
{ {
var scope = UsePow2Mode(device); var scope = UsePow2Mode(device);
MontgomeryDivisorData divisorData; MontgomeryDivisorData divisorData;
@@ -49,7 +49,7 @@ internal static partial class PrimeOrderCalculator
return result; return result;
} }
private static UInt128 CalculateWideInternal(UInt128 prime, UInt128? previousOrder, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config) private static UInt128 CalculateWideInternal(in UInt128 prime, in UInt128? previousOrder, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
{ {
if (prime <= UInt128.One) if (prime <= UInt128.One)
{ {
@@ -79,12 +79,12 @@ internal static partial class PrimeOrderCalculator
} }
private static UInt128 RunHeuristicPipelineWide( private static UInt128 RunHeuristicPipelineWide(
UInt128 prime, in UInt128 prime,
UInt128? previousOrder, in UInt128? previousOrder,
in MontgomeryDivisorData divisorData, in MontgomeryDivisorData divisorData,
PrimeOrderSearchConfig config, in PrimeOrderSearchConfig config,
UInt128 phi, in UInt128 phi,
PartialFactorResult128 phiFactors) in PartialFactorResult128 phiFactors)
{ {
if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors)) if (phiFactors.FullyFactored && TrySpecialMaxWide(phi, prime, divisorData, phiFactors))
{ {
@@ -112,12 +112,12 @@ internal static partial class PrimeOrderCalculator
return candidateOrder; return candidateOrder;
} }
private static UInt128 FinishStrictlyWide(UInt128 prime, in MontgomeryDivisorData divisorData) private static UInt128 FinishStrictlyWide(in UInt128 prime, in MontgomeryDivisorData divisorData)
{ {
return CalculateByFactorizationWide(prime, divisorData); return CalculateByFactorizationWide(prime, divisorData);
} }
private static bool TrySpecialMaxWide(UInt128 phi, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors) private static bool TrySpecialMaxWide(in UInt128 phi, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
{ {
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors; ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count; int length = factors.Count;
@@ -134,7 +134,7 @@ internal static partial class PrimeOrderCalculator
return true; return true;
} }
private static UInt128 InitializeStartingOrderWide(UInt128 prime, UInt128 phi, in MontgomeryDivisorData divisorData) private static UInt128 InitializeStartingOrderWide(in UInt128 prime, in UInt128 phi, in MontgomeryDivisorData divisorData)
{ {
UInt128 order = phi; UInt128 order = phi;
UInt128 mod8 = prime & (UInt128)7UL; UInt128 mod8 = prime & (UInt128)7UL;
@@ -150,19 +150,20 @@ internal static partial class PrimeOrderCalculator
return order; return order;
} }
private static UInt128 ExponentLoweringWide(UInt128 order, UInt128 prime, in MontgomeryDivisorData divisorData, PartialFactorResult128 factors) private static UInt128 ExponentLoweringWide(UInt128 order, in UInt128 prime, in MontgomeryDivisorData divisorData, in PartialFactorResult128 factors)
{ {
FactorEntry128[]? tempArray = null; ArrayPool<FactorEntry128> pool = ThreadStaticPools.FactorEntry128Pool;
ReadOnlySpan<FactorEntry128> 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 try
{ {
ReadOnlySpan<FactorEntry128> factorSpan = factors.Factors;
int length = factors.Count;
if (length == 0)
{
return order;
}
tempArray = ArrayPool<FactorEntry128>.Shared.Rent(length + 1);
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length); Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
factorSpan.CopyTo(buffer); factorSpan.CopyTo(buffer);
@@ -198,14 +199,11 @@ internal static partial class PrimeOrderCalculator
} }
finally finally
{ {
if (tempArray is not null) pool.Return(tempArray, clearArray: false);
{
ArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
}
} }
} }
private static bool TryConfirmOrderWide(UInt128 prime, UInt128 order, in MontgomeryDivisorData divisorData, PrimeOrderSearchConfig config) private static bool TryConfirmOrderWide(in UInt128 prime, in UInt128 order, in MontgomeryDivisorData divisorData, in PrimeOrderSearchConfig config)
{ {
if (order == UInt128.Zero) if (order == UInt128.Zero)
{ {
@@ -263,11 +261,11 @@ internal static partial class PrimeOrderCalculator
} }
private static bool TryHeuristicFinishWide( private static bool TryHeuristicFinishWide(
UInt128 prime, in UInt128 prime,
UInt128 order, in UInt128 order,
UInt128? previousOrder, in UInt128? previousOrder,
in MontgomeryDivisorData divisorData, in MontgomeryDivisorData divisorData,
PrimeOrderSearchConfig config, in PrimeOrderSearchConfig config,
out UInt128 result) out UInt128 result)
{ {
result = UInt128.Zero; result = UInt128.Zero;
@@ -339,7 +337,7 @@ internal static partial class PrimeOrderCalculator
return false; return false;
} }
private static void SortCandidatesWide(UInt128 prime, UInt128? previousOrder, List<UInt128> candidates) private static void SortCandidatesWide(UInt128 prime, in UInt128? previousOrder, List<UInt128> candidates)
{ {
UInt128 previous = previousOrder ?? UInt128.Zero; UInt128 previous = previousOrder ?? UInt128.Zero;
int previousGroup = previousOrder.HasValue ? GetGroupWide(previousOrder.Value, prime) : 1; int previousGroup = previousOrder.HasValue ? GetGroupWide(previousOrder.Value, prime) : 1;
@@ -367,9 +365,9 @@ internal static partial class PrimeOrderCalculator
} }
private static CandidateKey128 BuildKeyWide( private static CandidateKey128 BuildKeyWide(
UInt128 value, in UInt128 value,
UInt128 prime, in UInt128 prime,
UInt128 previous, in UInt128 previous,
int previousGroup, int previousGroup,
bool hasPrevious) bool hasPrevious)
{ {
@@ -391,7 +389,7 @@ internal static partial class PrimeOrderCalculator
return new CandidateKey128(primary, false, distance, false, value); return new CandidateKey128(primary, false, distance, false, value);
} }
private static int CompareComponents(bool descendingX, UInt128 valueX, bool descendingY, UInt128 valueY) private static int CompareComponents(bool descendingX, in UInt128 valueX, bool descendingY, in UInt128 valueY)
{ {
if (descendingX == descendingY) if (descendingX == descendingY)
{ {
@@ -416,7 +414,7 @@ internal static partial class PrimeOrderCalculator
return valueX > valueY ? 1 : -1; return valueX > valueY ? 1 : -1;
} }
private static int GetGroupWide(UInt128 value, UInt128 prime) private static int GetGroupWide(in UInt128 value, in UInt128 prime)
{ {
UInt128 threshold1 = prime >> 3; UInt128 threshold1 = prime >> 3;
if (value <= threshold1) if (value <= threshold1)
@@ -439,7 +437,7 @@ internal static partial class PrimeOrderCalculator
return 0; return 0;
} }
private static void BuildCandidatesWide(UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit) private static void BuildCandidatesWide(in UInt128 order, FactorEntry128[] factors, int count, List<UInt128> candidates, int limit)
{ {
if (count == 0) if (count == 0)
{ {
@@ -452,10 +450,10 @@ internal static partial class PrimeOrderCalculator
} }
private static void BuildCandidatesRecursiveWide( private static void BuildCandidatesRecursiveWide(
UInt128 order, in UInt128 order,
ReadOnlySpan<FactorEntry128> factors, in ReadOnlySpan<FactorEntry128> factors,
int index, int index,
UInt128 divisorProduct, in UInt128 divisorProduct,
List<UInt128> candidates, List<UInt128> candidates,
int limit) int limit)
{ {
@@ -511,7 +509,7 @@ internal static partial class PrimeOrderCalculator
} }
} }
private static bool TryConfirmCandidateWide(UInt128 prime, UInt128 candidate, PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData) private static bool TryConfirmCandidateWide(in UInt128 prime, in UInt128 candidate, in PrimeOrderSearchConfig config, ref int powUsed, int powBudget, in MontgomeryDivisorData divisorData)
{ {
PartialFactorResult128 factorization = PartialFactorWide(candidate, config); PartialFactorResult128 factorization = PartialFactorWide(candidate, config);
if (factorization.Factors is null) if (factorization.Factors is null)
@@ -564,7 +562,7 @@ internal static partial class PrimeOrderCalculator
return true; return true;
} }
private static PartialFactorResult128 PartialFactorWide(UInt128 value, PrimeOrderSearchConfig config) private static PartialFactorResult128 PartialFactorWide(in UInt128 value, in PrimeOrderSearchConfig config)
{ {
if (value <= UInt128.One) if (value <= UInt128.One)
{ {
@@ -664,7 +662,8 @@ internal static partial class PrimeOrderCalculator
return new PartialFactorResult128(null, value, false, 0); return new PartialFactorResult128(null, value, false, 0);
} }
FactorEntry128[]? rented = counts.Count > 0 ? ArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null; ArrayPool<FactorEntry128>? pool = null;
FactorEntry128[]? rented = counts.Count > 0 ? (pool = ThreadStaticPools.FactorEntry128Pool).Rent(counts.Count) : null;
int index = 0; int index = 0;
if (rented is not null) if (rented is not null)
{ {
@@ -676,20 +675,20 @@ internal static partial class PrimeOrderCalculator
} }
FactorEntry128[]? resultArray = null; FactorEntry128[]? resultArray = null;
if (rented is not null) if (pool is not null)
{ {
Span<FactorEntry128> span = rented.AsSpan(0, index); Span<FactorEntry128> span = rented.AsSpan(0, index);
span.Sort(static (a, b) => a.Value.CompareTo(b.Value)); span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
resultArray = new FactorEntry128[index]; resultArray = new FactorEntry128[index];
span.CopyTo(resultArray); span.CopyTo(resultArray);
ArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false); pool.Return(rented!, clearArray: false);
} }
bool fullyFactored = cofactor == UInt128.One; bool fullyFactored = cofactor == UInt128.One;
return new PartialFactorResult128(resultArray, cofactor, fullyFactored, index); return new PartialFactorResult128(resultArray, cofactor, fullyFactored, index);
} }
private static UInt128 PopulateSmallPrimeFactorsCpuWide(UInt128 value, uint limit, Dictionary<UInt128, int> counts) private static UInt128 PopulateSmallPrimeFactorsCpuWide(in UInt128 value, uint limit, Dictionary<UInt128, int> counts)
{ {
UInt128 remaining = value; UInt128 remaining = value;
uint[] primes = PrimesGenerator.SmallPrimes; uint[] primes = PrimesGenerator.SmallPrimes;
@@ -729,7 +728,7 @@ internal static partial class PrimeOrderCalculator
return remaining; return remaining;
} }
private static bool TryPollardRhoWide(UInt128 n, Stopwatch stopwatch, long budgetTicks, out UInt128 factor) private static bool TryPollardRhoWide(in UInt128 n, Stopwatch stopwatch, long budgetTicks, out UInt128 factor)
{ {
factor = UInt128.Zero; factor = UInt128.Zero;
if ((n & UInt128.One) == UInt128.Zero) if ((n & UInt128.One) == UInt128.Zero)
@@ -771,7 +770,7 @@ internal static partial class PrimeOrderCalculator
} }
} }
private static UInt128 AdvancePolynomialWide(UInt128 x, UInt128 c, UInt128 modulus) private static UInt128 AdvancePolynomialWide(in UInt128 x, in UInt128 c, in UInt128 modulus)
{ {
BigInteger value = (BigInteger)x; BigInteger value = (BigInteger)x;
value = (value * value + (BigInteger)c) % (BigInteger)modulus; value = (value * value + (BigInteger)c) % (BigInteger)modulus;
@@ -809,7 +808,7 @@ internal static partial class PrimeOrderCalculator
} }
} }
private static int TrailingZeroCountWide(UInt128 value) private static int TrailingZeroCountWide(in UInt128 value)
{ {
if (value == UInt128.Zero) if (value == UInt128.Zero)
{ {
@@ -865,7 +864,7 @@ internal static partial class PrimeOrderCalculator
return order; return order;
} }
private static void FactorCompletelyWide(UInt128 value, Dictionary<UInt128, int> counts) private static void FactorCompletelyWide(in UInt128 value, Dictionary<UInt128, int> counts)
{ {
if (value <= UInt128.One) if (value <= UInt128.One)
{ {
@@ -883,7 +882,7 @@ internal static partial class PrimeOrderCalculator
FactorCompletelyWide(value / factor, counts); FactorCompletelyWide(value / factor, counts);
} }
private static UInt128 PollardRhoWide(UInt128 n) private static UInt128 PollardRhoWide(in UInt128 n)
{ {
if ((n & UInt128.One) == UInt128.Zero) if ((n & UInt128.One) == UInt128.Zero)
{ {
@@ -916,7 +915,7 @@ internal static partial class PrimeOrderCalculator
} }
} }
private static bool IsPrimeWide(UInt128 value) private static bool IsPrimeWide(in UInt128 value)
{ {
if (value <= ulong.MaxValue) if (value <= ulong.MaxValue)
{ {
@@ -996,7 +995,7 @@ internal static partial class PrimeOrderCalculator
return true; return true;
} }
private static UInt128 Pow2ModWide(UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData) private static UInt128 Pow2ModWide(in UInt128 exponent, in UInt128 modulus, in MontgomeryDivisorData divisorData)
{ {
if (modulus == UInt128.One) if (modulus == UInt128.One)
{ {
@@ -1026,7 +1025,7 @@ internal static partial class PrimeOrderCalculator
return exponent.Pow2MontgomeryModWindowed(modulus); return exponent.Pow2MontgomeryModWindowed(modulus);
} }
private static void AddFactor(Dictionary<UInt128, int> counts, UInt128 prime, int exponent) private static void AddFactor(Dictionary<UInt128, int> counts, in UInt128 prime, int exponent)
{ {
if (counts.TryGetValue(prime, out int existing)) if (counts.TryGetValue(prime, out int existing))
{ {
@@ -1038,60 +1037,30 @@ internal static partial class PrimeOrderCalculator
} }
} }
private readonly struct CandidateKey128 private readonly struct CandidateKey128(int primary, bool secondaryDescending, in UInt128 secondary, bool tertiaryDescending, in UInt128 tertiary)
{ {
public CandidateKey128(int primary, bool secondaryDescending, UInt128 secondary, bool tertiaryDescending, UInt128 tertiary) public int Primary { get; } = primary;
{
Primary = primary;
SecondaryDescending = secondaryDescending;
Secondary = secondary;
TertiaryDescending = tertiaryDescending;
Tertiary = tertiary;
}
public int Primary { get; } public bool SecondaryDescending { get; } = secondaryDescending;
public bool SecondaryDescending { get; } public UInt128 Secondary { get; } = secondary;
public UInt128 Secondary { get; } public bool TertiaryDescending { get; } = tertiaryDescending;
public bool TertiaryDescending { get; } public UInt128 Tertiary { get; } = tertiary;
}
public UInt128 Tertiary { get; } private readonly struct PartialFactorResult128(FactorEntry128[]? factors, UInt128 cofactor, bool fullyFactored, int count)
} {
public FactorEntry128[]? Factors { get; } = factors;
private readonly struct FactorEntry128 public UInt128 Cofactor { get; } = cofactor;
{
public FactorEntry128(UInt128 value, int exponent)
{
Value = value;
Exponent = exponent;
}
public UInt128 Value { get; } public bool FullyFactored { get; } = fullyFactored;
public int Exponent { get; } public int Count { get; } = count;
}
private readonly struct PartialFactorResult128 public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
{
public PartialFactorResult128(FactorEntry128[]? factors, UInt128 cofactor, bool fullyFactored, int count)
{
Factors = factors;
Cofactor = cofactor;
FullyFactored = fullyFactored;
Count = count;
}
public FactorEntry128[]? Factors { get; }
public UInt128 Cofactor { get; }
public bool FullyFactored { get; }
public int Count { get; }
public static PartialFactorResult128 Empty => new(null, UInt128.One, true, 0);
public PartialFactorResult128 WithAdditionalPrime(UInt128 prime) public PartialFactorResult128 WithAdditionalPrime(UInt128 prime)
{ {
File diff suppressed because it is too large. Load diff
+41
View File
@@ -0,0 +1,41 @@
using System.Buffers;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core
{
public static class ThreadStaticPools
{
[ThreadStatic]
private static ArrayPool<FactorEntry>? _factorEntryPool;
public static ArrayPool<FactorEntry> FactorEntryPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorEntryPool ??= ArrayPool<FactorEntry>.Create();
}
}
[ThreadStatic]
private static ArrayPool<FactorEntry128>? _factorEntry128Pool;
public static ArrayPool<FactorEntry128> FactorEntry128Pool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _factorEntry128Pool ??= ArrayPool<FactorEntry128>.Create();
}
}
[ThreadStatic]
private static ArrayPool<ulong>? _ulongPool;
public static ArrayPool<ulong> UlongPool
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
return _ulongPool ??= ArrayPool<ulong>.Create();
}
}
}
}