MersenneNumberDivisorByDivisorCpuTesterWithTemplate optimizations

This commit is contained in:
mlemanczyk committed 2025-12-19 00:10:09 +01:00
1 parent 4edad10be8
commit 870819dec6
2 files changed
+85 -77

No files matched your search

@@ -40,6 +40,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
_stateCounter = 0; _stateCounter = 0;
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor) public bool IsPrime(ulong prime, out bool divisorsExhausted, out BigInteger divisor)
{ {
BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit); BigInteger allowedMax = MersenneNumberDivisorByDivisorTester.ComputeAllowedMaxDivisorBig(prime, DivisorLimit);
@@ -100,6 +101,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
#endif #endif
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors( private bool CheckDivisors(
ulong prime, ulong prime,
BigInteger allowedMax, BigInteger allowedMax,
@@ -107,35 +109,41 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
out bool processedAll, out bool processedAll,
out BigInteger foundDivisor) out BigInteger foundDivisor)
{ {
BigInteger normalizedMinK = minK < BigInteger.One ? BigInteger.One : minK; // TODO: Is minK ever < 1? Can this be optimized
BigInteger step = ((BigInteger)prime) << 1; BigInteger normalizedMinK = minK >= BigInteger.One ? minK : BigInteger.One,
BigInteger firstDivisor = (step * normalizedMinK) + BigInteger.One; step = ((BigInteger)prime) << 1,
bool unlimited = allowedMax.IsZero; firstDivisor = (step * normalizedMinK) + BigInteger.One;
bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue; bool fits64 = normalizedMinK <= ulong.MaxValue && firstDivisor <= ulong.MaxValue,
bool limitFits64 = unlimited || allowedMax <= ulong.MaxValue; unlimited = allowedMax.IsZero,
if (fits64 && limitFits64) // This checks if the limit fits 64-bit
composite64 = unlimited || allowedMax <= ulong.MaxValue;
ulong allowedMax64,
foundDivisor64;
if (fits64 && composite64)
{ {
ulong allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax; allowedMax64 = unlimited ? ulong.MaxValue : (ulong)allowedMax;
bool composite64 = CheckDivisors64Bit( composite64 = CheckDivisors64Bit(
prime, prime,
allowedMax64, allowedMax64,
(ulong)normalizedMinK, (ulong)normalizedMinK,
out processedAll, out processedAll,
out ulong foundDivisor64); out foundDivisor64);
foundDivisor = foundDivisor64; foundDivisor = foundDivisor64;
return composite64; return composite64;
} }
if (fits64 && allowedMax > ulong.MaxValue) if (fits64 && allowedMax > ulong.MaxValue)
{ {
ulong allowedMax64 = ulong.MaxValue; allowedMax64 = ulong.MaxValue;
bool composite64 = CheckDivisors64Bit( composite64 = CheckDivisors64Bit(
prime, prime,
allowedMax64, allowedMax64,
(ulong)normalizedMinK, (ulong)normalizedMinK,
out processedAll, out processedAll,
out ulong foundDivisor64); out foundDivisor64);
if (composite64) if (composite64)
{ {
foundDivisor = foundDivisor64; foundDivisor = foundDivisor64;
@@ -162,6 +170,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
out foundDivisor); out foundDivisor);
} }
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
private bool CheckDivisors64Bit( private bool CheckDivisors64Bit(
ulong prime, ulong prime,
ulong allowedMax, ulong allowedMax,
@@ -170,7 +179,6 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
out ulong foundDivisor) out ulong foundDivisor)
{ {
foundDivisor = 0UL; foundDivisor = 0UL;
ulong currentK = minK < 1UL ? 1UL : minK;
// The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs. // The EvenPerfectBitScanner feeds primes >= 138,000,000 here, so allowedMax >= 3 in production runs.
// Keeping the guard commented out documents the reasoning for benchmarks and tests. // Keeping the guard commented out documents the reasoning for benchmarks and tests.
@@ -211,35 +219,44 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
// Intentionally recomputes factorizations without a per-thread cache. // Intentionally recomputes factorizations without a per-thread cache.
// The previous factor cache recorded virtually no hits and only slowed down the scan. // The previous factor cache recorded virtually no hits and only slowed down the scan.
ulong stepHigh = step.High; ulong currentK = minK,
ulong stepLow = step.Low; // We're reusing candidate variable as stepLow to limit registry pressure
ulong limitHigh = limit.High; candidate = step.Low,
// We're reusing computedCycle variable as lowerEnd to limit registry pressure
computedCycle,
// We're reusing divisorCycle variable as maxK to limit registry pressure
divisorCycle;
if (stepHigh == 0UL && limitHigh == 0UL) // We're reusing computed variable as processedTop to limit registry pressure
bool computed,
// We're reusing primeOrderFailed variable as processedBottom to limit registry pressure
primeOrderFailed,
composite;
if (step.High == 0UL && limit.High == 0UL)
{ {
ulong maxK = allowedMax > 0UL ? (allowedMax - 1UL) / stepLow : 0UL; divisorCycle = allowedMax > 0UL ? (allowedMax - 1UL) / candidate : 0UL;
if (maxK == 0UL) if (divisorCycle == 0UL)
{ {
processedAll = true; processedAll = true;
foundDivisor = 0UL; foundDivisor = 0UL;
return false; return false;
} }
ulong startK = minK < 1UL ? 1UL : minK; computed = true;
bool processedTop = true; primeOrderFailed = true;
bool processedBottom = true; composite = false;
bool composite = false;
if (startK <= maxK) if (minK <= divisorCycle)
{ {
composite = CheckDivisors64Range( composite = CheckDivisors64Range(
prime, prime,
stepLow, candidate,
allowedMax, allowedMax,
startK, minK,
maxK, divisorCycle,
ref currentK, ref currentK,
out processedTop, out computed,
out foundDivisor); out foundDivisor);
if (composite) if (composite)
{ {
@@ -248,23 +265,23 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
} }
} }
ulong lowerEnd = startK > 1UL ? startK - 1UL : 0UL; computedCycle = minK > 1UL ? minK - 1UL : 0UL;
if (lowerEnd >= 1UL && startK == 1UL) if (computedCycle >= 1UL && minK == 1UL)
{ {
lowerEnd = Math.Min(lowerEnd, maxK); computedCycle = Math.Min(computedCycle, divisorCycle);
currentK = 1UL; currentK = 1UL;
composite = CheckDivisors64Range( composite = CheckDivisors64Range(
prime, prime,
stepLow, candidate,
allowedMax, allowedMax,
1UL, 1UL,
lowerEnd, computedCycle,
ref currentK, ref currentK,
out processedBottom, out primeOrderFailed,
out foundDivisor); out foundDivisor);
} }
processedAll = processedTop && processedBottom; processedAll = computed && primeOrderFailed;
return composite; return composite;
} }
@@ -273,11 +290,6 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
#endif #endif
var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor); var residueStepper = new MersenneDivisorResidueStepper(prime, step, divisor);
ulong computedCycle;
bool primeOrderFailed;
ulong candidate;
ulong divisorCycle;
bool computed;
while (divisor.CompareTo(limit) <= 0) while (divisor.CompareTo(limit) <= 0)
{ {
if (residueStepper.IsAdmissible()) if (residueStepper.IsAdmissible())
@@ -309,7 +321,11 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
out computedCycle, out computedCycle,
out primeOrderFailed); out primeOrderFailed);
#endif #endif
if (!computed || computedCycle == 0UL) if (computed && computedCycle != 0UL)
{
divisorCycle = computedCycle;
}
else
{ {
// Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles // Divisors produced by 2 * k * p + 1 always exceed PerfectNumberConstants.MaxQForDivisorCycles
// for the exponents scanned here, so skip the unused cache fallback and compute directly. // for the exponents scanned here, so skip the unused cache fallback and compute directly.
@@ -332,10 +348,6 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
skipPrimeOrderHeuristic: primeOrderFailed); skipPrimeOrderHeuristic: primeOrderFailed);
#endif #endif
} }
else
{
divisorCycle = computedCycle;
}
RecordState(currentK); RecordState(currentK);
if (divisorCycle == prime) if (divisorCycle == prime)
@@ -374,7 +386,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
{ {
processedAll = true; processedAll = true;
foundDivisor = BigInteger.Zero; foundDivisor = BigInteger.Zero;
BigInteger currentK = minK < BigInteger.One ? BigInteger.One : minK; BigInteger currentK = minK;
BigInteger divisor = (step * currentK) + BigInteger.One; BigInteger divisor = (step * currentK) + BigInteger.One;
if (allowedMax.IsZero) if (allowedMax.IsZero)
{ {
@@ -386,8 +398,8 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
return false; return false;
} }
LastDigit lastDigit = (prime & 3UL) == 3UL ? LastDigit.Seven : LastDigit.One; bool lastIsSeven = (prime & 3UL) == 3UL;
ushort decimalMask = DivisorGenerator.GetDecimalMask(lastDigit); ushort decimalMask = DivisorGenerator.GetDecimalMask(lastIsSeven);
var rem10 = new BigIntegerCycleRemainderStepper(10); var rem10 = new BigIntegerCycleRemainderStepper(10);
var rem8 = new BigIntegerCycleRemainderStepper(8); var rem8 = new BigIntegerCycleRemainderStepper(8);
@@ -431,7 +443,7 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
} }
} }
currentK += BigInteger.One; currentK++;
divisor += step; divisor += step;
remainder10 = (byte)rem10.ComputeNext(divisor); remainder10 = (byte)rem10.ComputeNext(divisor);
remainder8 = (byte)rem8.ComputeNext(divisor); remainder8 = (byte)rem8.ComputeNext(divisor);
@@ -521,10 +533,9 @@ public struct MersenneNumberDivisorByDivisorCpuTesterWithTemplate() : IMersenneN
#endif #endif
ulong computedCycle; ulong computedCycle;
bool primeOrderFailed; bool primeOrderFailed;
bool canAdvance = step <= limit;
ulong divisorCycle; ulong divisorCycle;
bool computed; bool computed;
if (!canAdvance) if (step > limit)
{ {
if (divisor <= limit && residueStepper.IsAdmissible()) if (divisor <= limit && residueStepper.IsAdmissible())
{ {
+22 -25
View File
@@ -47,19 +47,19 @@ internal static class DivisorGenerator
out SmallPrimesPow2LastNineWithoutLastThree); out SmallPrimesPow2LastNineWithoutLastThree);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort GetDecimalMask(LastDigit lastDigit) public static ushort GetDecimalMask(LastDigit lastDigit) => lastDigit switch
{ {
return lastDigit switch LastDigit.Seven => DecimalMaskWhenLastIsSeven,
{ LastDigit.Three => DecimalMaskWhenLastIsThree,
LastDigit.Seven => DecimalMaskWhenLastIsSeven, LastDigit.Nine => DecimalMaskWhenLastIsNine,
LastDigit.Three => DecimalMaskWhenLastIsThree, _ => DecimalMaskOtherwise,
LastDigit.Nine => DecimalMaskWhenLastIsNine, };
_ => DecimalMaskOtherwise,
};
}
private static void BuildSmallPrimes( [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
public static ushort GetDecimalMask(bool lastIsSeven) => lastIsSeven ? DecimalMaskWhenLastIsSeven : DecimalMaskOtherwise;
private static void BuildSmallPrimes(
out uint[] all, out uint[] all,
out ulong[] allPow2, out ulong[] allPow2,
out uint[] lastOne, out uint[] lastOne,
@@ -210,6 +210,8 @@ internal static class DivisorGenerator
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static LastDigit GetLastDigit(uint prime) private static LastDigit GetLastDigit(uint prime)
{ {
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path
// reuses the benchmarked residue helper instead of repeated divisions.
return (prime % 10U) switch return (prime % 10U) switch
{ {
1U => LastDigit.One, 1U => LastDigit.One,
@@ -221,20 +223,15 @@ internal static class DivisorGenerator
}; };
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)]
private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit) private static bool IsAllowedForLastOne(uint prime, LastDigit lastDigit) => lastDigit switch
{ {
// TODO: Swap the `% 10` usage for ULongExtensions.Mod10 so the hot classification path LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
// reuses the benchmarked residue helper instead of repeated divisions. LastDigit.Seven => prime == 7U,
return lastDigit switch _ => prime == 11U,
{ };
LastDigit.One or LastDigit.Three or LastDigit.Nine => true,
LastDigit.Seven => prime == 7U,
_ => prime == 11U,
};
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit) private static bool IsAllowedForLastSeven(uint prime, LastDigit lastDigit)
{ {
// TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster // TODO: Route this `% 10` classification through ULongExtensions.Mod10 to match the faster