Merge pull request #153 from mlemanczyk/codex/analyze-caching-efficiency-in-evenperfectbitscanner

Remove Montgomery divisor cache
This commit is contained in:
Marek Lemańczyk authored and GitHub committed 2025-10-11 21:17:45 +02:00
commit ec265bde8b
10 files changed
+172 -89

No files matched your search

@@ -192,6 +192,10 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
Dictionary<ulong, MersenneDivisorCycles.FactorCacheEntry>? factorCache = null;
DivisorCycleCache cycleCache = DivisorCycleCache.Shared;
ulong cachedModulus = 0UL;
MontgomeryDivisorData cachedDivisorData = default;
bool hasCachedDivisorData = false;
byte step10 = (byte)(step % 10UL);
byte step8 = (byte)(step % 8UL);
byte step5 = (byte)(step % 5UL);
@@ -222,7 +226,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
if (admissible && (remainder8 == 1 || remainder8 == 7) && remainder3 != 0 && remainder5 != 0 && remainder7 != 0 && remainder11 != 0)
{
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(candidate);
if (!hasCachedDivisorData || cachedModulus != candidate)
{
cachedDivisorData = MontgomeryDivisorData.FromModulus(candidate);
cachedModulus = candidate;
hasCachedDivisorData = true;
}
MontgomeryDivisorData divisorData = cachedDivisorData;
ulong divisorCycle;
if (candidate <= PerfectNumberConstants.MaxQForDivisorCycles)
@@ -340,6 +351,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
{
private readonly MersenneNumberDivisorByDivisorCpuTester _owner;
private bool _disposed;
private bool _hasCachedDivisorData;
private ulong _cachedDivisor;
private MontgomeryDivisorData _cachedDivisorData;
internal DivisorScanSession(MersenneNumberDivisorByDivisorCpuTester owner)
{
@@ -349,6 +363,9 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
internal void Reset()
{
_disposed = false;
_hasCachedDivisorData = false;
_cachedDivisor = 0UL;
_cachedDivisorData = default;
}
public void CheckDivisor(ulong divisor, in MontgomeryDivisorData divisorData, ulong divisorCycle, ReadOnlySpan<ulong> primes, Span<byte> hits)
@@ -367,7 +384,14 @@ public sealed class MersenneNumberDivisorByDivisorCpuTester : IMersenneNumberDiv
MontgomeryDivisorData cachedData = divisorData;
if (cachedData.Modulus != divisor)
{
cachedData = MontgomeryDivisorDataCache.Get(divisor);
if (!_hasCachedDivisorData || _cachedDivisor != divisor)
{
_cachedDivisorData = MontgomeryDivisorData.FromModulus(divisor);
_cachedDivisor = divisor;
_hasCachedDivisorData = true;
}
cachedData = _cachedDivisorData;
}
if (divisorCycle == 0UL)
+44 -14
View File
@@ -63,7 +63,9 @@ public sealed class DivisorCycleCache
int[]? rentedMissing = null;
Span<int> missingBuffer = length <= StackBufferThreshold
? stackalloc int[length]
: new Span<int>(rentedMissing = ArrayPool<int>.Shared.Rent(length), 0, length);
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<int>(new int[length])
: new Span<int>(rentedMissing = ThreadLocalArrayPool<int>.Shared.Rent(length), 0, length);
int missingCount = 0;
@@ -106,7 +108,7 @@ public sealed class DivisorCycleCache
if (rentedMissing is not null)
{
ArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
ThreadLocalArrayPool<int>.Shared.Return(rentedMissing, clearArray: false);
}
}
@@ -128,12 +130,16 @@ public sealed class DivisorCycleCache
ulong[]? rentedDivisors = null;
Span<ulong> workDivisors = count <= StackBufferThreshold
? stackalloc ulong[count]
: new Span<ulong>(rentedDivisors = ArrayPool<ulong>.Shared.Rent(count), 0, count);
: count < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[count])
: new Span<ulong>(rentedDivisors = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count);
ulong[]? rentedResults = null;
Span<ulong> workResults = count <= StackBufferThreshold
? stackalloc ulong[count]
: new Span<ulong>(rentedResults = ArrayPool<ulong>.Shared.Rent(count), 0, count);
: count < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[count])
: new Span<ulong>(rentedResults = ThreadLocalArrayPool<ulong>.Shared.Rent(count), 0, count);
for (int i = 0; i < count; i++)
{
@@ -149,8 +155,12 @@ public sealed class DivisorCycleCache
if (rentedDivisors is not null)
{
ArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedResults!, clearArray: false);
ThreadLocalArrayPool<ulong>.Shared.Return(rentedDivisors, clearArray: false);
}
if (rentedResults is not null)
{
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResults, clearArray: false);
}
}
@@ -172,22 +182,30 @@ public sealed class DivisorCycleCache
ulong[]? rentedPow = null;
Span<ulong> powSpan = length <= StackBufferThreshold
? stackalloc ulong[length]
: new Span<ulong>(rentedPow = ArrayPool<ulong>.Shared.Rent(length), 0, length);
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[length])
: new Span<ulong>(rentedPow = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
ulong[]? rentedOrder = null;
Span<ulong> orderSpan = length <= StackBufferThreshold
? stackalloc ulong[length]
: new Span<ulong>(rentedOrder = ArrayPool<ulong>.Shared.Rent(length), 0, length);
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[length])
: new Span<ulong>(rentedOrder = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
ulong[]? rentedResult = null;
Span<ulong> resultSpan = length <= StackBufferThreshold
? stackalloc ulong[length]
: new Span<ulong>(rentedResult = ArrayPool<ulong>.Shared.Rent(length), 0, length);
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<ulong>(new ulong[length])
: new Span<ulong>(rentedResult = ThreadLocalArrayPool<ulong>.Shared.Rent(length), 0, length);
byte[]? rentedStatus = null;
Span<byte> statusSpan = length <= StackBufferThreshold
? stackalloc byte[length]
: new Span<byte>(rentedStatus = ArrayPool<byte>.Shared.Rent(length), 0, length);
: length < PerfectNumberConstants.PooledArrayThreshold
? new Span<byte>(new byte[length])
: new Span<byte>(rentedStatus = ThreadLocalArrayPool<byte>.Shared.Rent(length), 0, length);
ref ulong divisorRef = ref MemoryMarshal.GetReference(divisors);
divisorBuffer.View.CopyFromCPU(ref divisorRef, length);
@@ -234,10 +252,22 @@ public sealed class DivisorCycleCache
resultSpan.CopyTo(destination);
if (rentedPow is not null)
{
ArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedOrder!, clearArray: false);
ArrayPool<ulong>.Shared.Return(rentedResult!, clearArray: false);
ArrayPool<byte>.Shared.Return(rentedStatus!, clearArray: false);
ThreadLocalArrayPool<ulong>.Shared.Return(rentedPow, clearArray: false);
}
if (rentedOrder is not null)
{
ThreadLocalArrayPool<ulong>.Shared.Return(rentedOrder, clearArray: false);
}
if (rentedResult is not null)
{
ThreadLocalArrayPool<ulong>.Shared.Return(rentedResult, clearArray: false);
}
if (rentedStatus is not null)
{
ThreadLocalArrayPool<byte>.Shared.Return(rentedStatus, clearArray: false);
}
execution.Dispose();
@@ -7,7 +7,6 @@ using System.Numerics;
using ILGPU;
using ILGPU.Runtime;
using MontgomeryDivisorData = PerfectNumbers.Core.MontgomeryDivisorData;
using MontgomeryDivisorDataCache = PerfectNumbers.Core.MontgomeryDivisorDataCache;
using CycleRemainderStepper = PerfectNumbers.Core.CycleRemainderStepper;
namespace PerfectNumbers.Core.Gpu;
+18 -5
View File
@@ -567,7 +567,10 @@ public class MersenneDivisorCycles
}
int count = factorCounts.Count;
ulong[] primes = ArrayPool<ulong>.Shared.Rent(count);
bool rentFromPool = count >= PerfectNumberConstants.PooledArrayThreshold;
ulong[] primes = rentFromPool
? ThreadLocalArrayPool<ulong>.Shared.Rent(count)
: new ulong[count];
try
{
int index = 0;
@@ -607,7 +610,10 @@ public class MersenneDivisorCycles
}
finally
{
ArrayPool<ulong>.Shared.Return(primes, clearArray: false);
if (rentFromPool)
{
ThreadLocalArrayPool<ulong>.Shared.Return(primes, clearArray: false);
}
}
}
@@ -793,7 +799,11 @@ public class MersenneDivisorCycles
tasks[taskIndex] = Task.Run(() =>
{
ulong rangeLength = localEnd - localStart + 1UL;
(ulong divisor, ulong cycle)[] localCycles = ArrayPool<(ulong, ulong)>.Shared.Rent(checked((int)rangeLength));
int localLength = checked((int)rangeLength);
bool rentLocalCycles = localLength >= PerfectNumberConstants.PooledArrayThreshold;
(ulong divisor, ulong cycle)[] localCycles = rentLocalCycles
? ThreadLocalArrayPool<(ulong, ulong)>.Shared.Rent(localLength)
: new (ulong divisor, ulong cycle)[localLength];
int localCycleIndex = 0;
try
@@ -841,7 +851,10 @@ public class MersenneDivisorCycles
}
finally
{
ArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
if (rentLocalCycles)
{
ThreadLocalArrayPool<(ulong, ulong)>.Shared.Return(localCycles, clearArray: false);
}
}
});
}
@@ -856,7 +869,7 @@ public class MersenneDivisorCycles
ulong start = 3UL;
var locker = new object();
var pool = ArrayPool<ulong>.Shared;
var pool = ThreadLocalArrayPool<ulong>.Shared;
ulong batchSizeUL = (ulong)batchSize, d, end;
int count = (int)Math.Min(batchSizeUL, maxDivisor), i, idx;
ulong[] divisors, outCycles, validDivisors;
@@ -45,32 +45,4 @@ public readonly struct MontgomeryDivisorData(ulong modulus, ulong nPrime, ulong
montgomeryTwo,
montgomeryTwoSquared);
}
}
internal static class MontgomeryDivisorDataCache
{
[ThreadStatic]
private static bool s_hasCachedValue;
[ThreadStatic]
private static ulong s_cachedModulus;
[ThreadStatic]
private static MontgomeryDivisorData s_cachedData;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static MontgomeryDivisorData Get(ulong modulus)
{
if (s_hasCachedValue && s_cachedModulus == modulus)
{
return s_cachedData;
}
MontgomeryDivisorData computed = MontgomeryDivisorData.FromModulus(modulus);
s_cachedModulus = modulus;
s_cachedData = computed;
s_hasCachedValue = true;
return computed;
}
}
+40 -27
View File
@@ -1,19 +1,26 @@
using System;
using System.Buffers;
namespace PerfectNumbers.Core
{
public sealed class PartialFactorResult
{
[ThreadStatic]
private static PartialFactorResult? s_poolHead;
[ThreadStatic]
private static PartialFactorResult? s_poolHead;
public static readonly PartialFactorResult Empty = new()
{
Cofactor = 1UL,
FullyFactored = true,
};
public FactorEntry[]? Factors;
private PartialFactorResult? _next;
public ulong Cofactor;
public bool FullyFactored;
public int Count;
private PartialFactorResult? _next;
private bool _factorsFromPool;
private PartialFactorResult()
{
}
@@ -30,52 +37,58 @@ namespace PerfectNumbers.Core
s_poolHead = instance._next;
}
FactorEntry[]? factorArray = factors;
instance._next = null;
instance.Factors = factors;
instance.Factors = factorArray;
instance.Cofactor = cofactor;
instance.FullyFactored = fullyFactored;
instance.Count = count;
instance._factorsFromPool = factorArray is not null && factorArray.Length >= PerfectNumberConstants.PooledArrayThreshold;
return instance;
}
public static readonly PartialFactorResult Empty = new()
{
Cofactor = 1UL,
FullyFactored = true,
};
public PartialFactorResult WithAdditionalPrime(ulong prime)
{
FactorEntry[]? source = Factors;
if (source is null || Count == 0)
{
FactorEntry[] local = ArrayPool<FactorEntry>.Shared.Rent(1);
local[0] = new FactorEntry(prime);
return Rent(local, 1UL, true, 1);
FactorEntry[] single = new FactorEntry[1];
single[0] = new FactorEntry(prime);
return Rent(single, 1UL, true, 1);
}
FactorEntry[] extended = ArrayPool<FactorEntry>.Shared.Rent(Count + 1);
int newCount = Count + 1;
FactorEntry[] extended = newCount >= PerfectNumberConstants.PooledArrayThreshold
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(newCount)
: new FactorEntry[newCount];
Array.Copy(source, 0, extended, 0, Count);
extended[Count] = new FactorEntry(prime);
Span<FactorEntry> span = extended.AsSpan(0, Count + 1);
Span<FactorEntry> span = extended.AsSpan(0, newCount);
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
return Rent(extended, 1UL, true, Count + 1);
return Rent(extended, 1UL, true, newCount);
}
public void Dispose()
{
if (this != Empty)
if (this == Empty)
{
FactorEntry[]? factors = Factors;
if (factors is not null)
{
Factors = null;
ArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
}
_next = s_poolHead;
s_poolHead = this;
return;
}
FactorEntry[]? factors = Factors;
if (factors is not null)
{
Factors = null;
if (_factorsFromPool)
{
ThreadLocalArrayPool<FactorEntry>.Shared.Return(factors, clearArray: false);
}
}
_factorsFromPool = false;
_next = s_poolHead;
s_poolHead = this;
}
}
}
@@ -81,12 +81,12 @@ namespace PerfectNumbers.Core
{
if (_primes is not null)
{
ArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
ThreadLocalArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
}
if (_exponents is not null)
{
ArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
ThreadLocalArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
}
_arraysFromPool = false;
@@ -119,12 +119,12 @@ namespace PerfectNumbers.Core
{
if (_primes is not null)
{
ArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
ThreadLocalArrayPool<ulong>.Shared.Return(_primes, clearArray: false);
}
if (_exponents is not null)
{
ArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
ThreadLocalArrayPool<byte>.Shared.Return(_exponents, clearArray: false);
}
_arraysFromPool = false;
@@ -134,8 +134,8 @@ namespace PerfectNumbers.Core
if (capacity >= PerfectNumberConstants.PooledArrayThreshold)
{
_primes = ArrayPool<ulong>.Shared.Rent(capacity);
_exponents = ArrayPool<byte>.Shared.Rent(capacity);
_primes = ThreadLocalArrayPool<ulong>.Shared.Rent(capacity);
_exponents = ThreadLocalArrayPool<byte>.Shared.Rent(capacity);
_arraysFromPool = true;
}
else
@@ -162,7 +162,7 @@ internal static partial class PrimeOrderCalculator
return order;
}
tempArray = ArrayPool<FactorEntry128>.Shared.Rent(length + 1);
tempArray = ThreadLocalArrayPool<FactorEntry128>.Shared.Rent(length + 1);
Span<FactorEntry128> buffer = tempArray.AsSpan(0, length);
factorSpan.CopyTo(buffer);
@@ -200,7 +200,7 @@ internal static partial class PrimeOrderCalculator
{
if (tempArray is not null)
{
ArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
ThreadLocalArrayPool<FactorEntry128>.Shared.Return(tempArray, clearArray: false);
}
}
}
@@ -664,7 +664,7 @@ internal static partial class PrimeOrderCalculator
return new PartialFactorResult128(null, value, false, 0);
}
FactorEntry128[]? rented = counts.Count > 0 ? ArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
FactorEntry128[]? rented = counts.Count > 0 ? ThreadLocalArrayPool<FactorEntry128>.Shared.Rent(counts.Count) : null;
int index = 0;
if (rented is not null)
{
@@ -682,7 +682,7 @@ internal static partial class PrimeOrderCalculator
span.Sort(static (a, b) => a.Value.CompareTo(b.Value));
resultArray = new FactorEntry128[index];
span.CopyTo(resultArray);
ArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
ThreadLocalArrayPool<FactorEntry128>.Shared.Return(rented, clearArray: false);
}
bool fullyFactored = cofactor == UInt128.One;
+7 -2
View File
@@ -1058,7 +1058,10 @@ internal static partial class PrimeOrderCalculator
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
}
FactorEntry[] factors = ArrayPool<FactorEntry>.Shared.Rent(counts.Count);
int dictionaryCount = counts.Count;
FactorEntry[] factors = dictionaryCount >= PerfectNumberConstants.PooledArrayThreshold
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(dictionaryCount)
: new FactorEntry[dictionaryCount];
int index = 0;
foreach (KeyValuePair<ulong, int> entry in counts)
{
@@ -1092,7 +1095,9 @@ internal static partial class PrimeOrderCalculator
return PartialFactorResult.Rent(null, cofactor, cofactor == 1UL, 0);
}
FactorEntry[] array = ArrayPool<FactorEntry>.Shared.Rent(actualCount);
FactorEntry[] array = actualCount >= PerfectNumberConstants.PooledArrayThreshold
? ThreadLocalArrayPool<FactorEntry>.Shared.Rent(actualCount)
: new FactorEntry[actualCount];
int arrayIndex = 0;
for (int i = 0; i < factorCount; i++)
{
@@ -0,0 +1,27 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace PerfectNumbers.Core;
internal static class ThreadLocalArrayPool<T>
{
[ThreadStatic]
private static ArrayPool<T>? s_threadPool;
public static ArrayPool<T> Shared
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get
{
ArrayPool<T>? pool = s_threadPool;
if (pool is null)
{
pool = ArrayPool<T>.Create();
s_threadPool = pool;
}
return pool;
}
}
}