Improve by-divisor scanning concurrency

This commit is contained in:
Marek Lemańczyk committed 2025-09-21 14:38:16 +02:00
1 parent d117921c3a
commit bee6b69793
1 file changed
+228 -200
+228 -200
View File
@@ -3,6 +3,7 @@ using System.Globalization;
using System.Numerics; using System.Numerics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading;
using Open.Collections; using Open.Collections;
using Open.Numeric.Primes; using Open.Numeric.Primes;
using PerfectNumbers.Core; using PerfectNumbers.Core;
@@ -54,12 +55,17 @@ internal static class Program
private static double _zeroFracConj = -1.0; // disabled when < 0 private static double _zeroFracConj = -1.0; // disabled when < 0
private static int _maxZeroConj = -1; // disabled when < 0 private static int _maxZeroConj = -1; // disabled when < 0
private struct ByDivisorPrimeState private const int ByDivisorStateActive = 0;
{ private const int ByDivisorStateComposite = 1;
internal ulong Prime; private const int ByDivisorStateCompleted = 2;
internal ulong AllowedMax; private const int ByDivisorStateCompletedDetailed = 3;
internal bool Completed;
internal bool Composite; private struct ByDivisorPrimeState
{
internal ulong Prime;
internal ulong AllowedMax;
internal bool Completed;
internal bool Composite;
internal bool DetailedCheck; internal bool DetailedCheck;
} }
@@ -793,231 +799,253 @@ internal static class Program
return; return;
} }
object stateLock = new(); int stateCount = states.Count;
ulong divisorLimit = _byDivisorTester!.DivisorLimit; ulong[] primeValues = new ulong[stateCount];
ulong nextDivisor = 3UL; ulong[] allowedMaxValues = new ulong[stateCount];
bool divisorsExhausted = false; int[] stateFlags = new int[stateCount];
ulong finalDivisorValue = 3UL;
int remainingStates = states.Count;
Task[] workers = new Task[Math.Max(1, threadCount)]; for (int i = 0; i < stateCount; i++)
{
var state = states[i];
primeValues[i] = state.Prime;
allowedMaxValues[i] = state.AllowedMax;
}
for (int i = 0; i < workers.Length; i++) states.Clear();
{
workers[i] = Task.Run(() =>
{
using var session = _byDivisorTester!.CreateDivisorSession();
byte[] hitsBuffer = ArrayPool<byte>.Shared.Rent(states.Count);
ulong[] primeBuffer = ArrayPool<ulong>.Shared.Rent(states.Count);
int[] indexBuffer = ArrayPool<int>.Shared.Rent(states.Count);
List<PendingResult> completions = new(states.Count);
List<PendingResult> composites = new(states.Count);
try ulong divisorLimit = _byDivisorTester!.DivisorLimit;
{ ulong nextDivisor = 3UL;
while (true) long finalDivisorBits = unchecked((long)3UL);
{ int divisorsExhaustedFlag = 0;
ulong divisor; int finalizerState = 0;
int activeCount; int finalizationCompleted = 0;
bool shouldTerminate; int remainingStates = stateCount;
bool hasWork;
lock (stateLock) Task[] workers = new Task[Math.Max(1, threadCount)];
{
hasWork = TryAcquireDivisorWork(states, divisorLimit, primeBuffer, indexBuffer, completions, ref nextDivisor, ref divisorsExhausted, ref finalDivisorValue, ref remainingStates, out divisor, out activeCount, out shouldTerminate);
}
if (completions.Count > 0) for (int workerIndex = 0; workerIndex < workers.Length; workerIndex++)
{ {
foreach (PendingResult result in completions) workers[workerIndex] = Task.Run(() =>
{ {
PrintResult(result.Prime, searchedMersenne: true, detailedCheck: result.DetailedCheck, passedAllTests: result.PassedAllTests); using var session = _byDivisorTester!.CreateDivisorSession();
} byte[] hitsBuffer = ArrayPool<byte>.Shared.Rent(stateCount);
ulong[] primeBuffer = ArrayPool<ulong>.Shared.Rent(stateCount);
int[] indexBuffer = ArrayPool<int>.Shared.Rent(stateCount);
List<PendingResult> completions = new(stateCount);
List<PendingResult> composites = new(stateCount);
completions.Clear(); try
} {
while (true)
{
if (Volatile.Read(ref remainingStates) == 0)
{
if (Volatile.Read(ref finalizationCompleted) != 0 || Volatile.Read(ref divisorsExhaustedFlag) == 0)
{
break;
}
}
if (!hasWork) bool exhausted;
{ ulong divisor = AcquireNextDivisor(ref nextDivisor, divisorLimit, ref divisorsExhaustedFlag, ref finalDivisorBits, out exhausted);
if (shouldTerminate)
{
break;
}
continue; if (divisor == 0UL)
} {
if (!exhausted)
{
if (Volatile.Read(ref remainingStates) == 0)
{
break;
}
Span<byte> hitsSpan = hitsBuffer.AsSpan(0, activeCount); Thread.Yield();
hitsSpan.Clear(); continue;
session.CheckDivisor(divisor, primeBuffer.AsSpan(0, activeCount), hitsSpan); }
bool exitAfterIteration; if (Interlocked.CompareExchange(ref finalizerState, 1, 0) == 0)
composites.Clear(); {
completions.Clear();
FinalizeRemainingStates(primeValues, allowedMaxValues, stateFlags, ref remainingStates, ref finalDivisorBits, completions);
lock (stateLock) if (completions.Count > 0)
{ {
for (int j = 0; j < activeCount; j++) foreach (PendingResult result in completions)
{ {
if (hitsSpan[j] == 0) PrintResult(result.Prime, searchedMersenne: true, detailedCheck: result.DetailedCheck, passedAllTests: result.PassedAllTests);
{ }
continue;
}
int index = indexBuffer[j]; completions.Clear();
var state = states[index]; }
if (state.Completed)
{
continue;
}
state.Completed = true; Volatile.Write(ref finalizationCompleted, 1);
state.Composite = true; }
state.DetailedCheck = true; else
states[index] = state; {
remainingStates--; while (Volatile.Read(ref finalizationCompleted) == 0 && Volatile.Read(ref remainingStates) > 0)
composites.Add(new PendingResult(state.Prime, state.DetailedCheck, false)); {
} Thread.Yield();
}
}
exitAfterIteration = remainingStates == 0; if (Volatile.Read(ref remainingStates) == 0)
if (exitAfterIteration && !divisorsExhausted) {
{ break;
divisorsExhausted = true; }
finalDivisorValue = Math.Max(finalDivisorValue, nextDivisor);
}
}
if (composites.Count > 0) continue;
{ }
foreach (PendingResult result in composites)
{
PrintResult(result.Prime, searchedMersenne: true, detailedCheck: result.DetailedCheck, passedAllTests: result.PassedAllTests);
}
}
if (exitAfterIteration) int activeCount = BuildPrimeBuffer(divisor, primeValues, allowedMaxValues, stateFlags, primeBuffer, indexBuffer, completions, ref remainingStates);
{
break;
}
}
}
finally
{
ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true);
ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true);
ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true);
}
});
}
Task.WaitAll(workers); if (completions.Count > 0)
} {
foreach (PendingResult result in completions)
{
PrintResult(result.Prime, searchedMersenne: true, detailedCheck: result.DetailedCheck, passedAllTests: result.PassedAllTests);
}
private static bool TryAcquireDivisorWork(List<ByDivisorPrimeState> states, ulong divisorLimit, ulong[] primeBuffer, int[] indexBuffer, List<PendingResult> completions, ref ulong nextDivisor, ref bool divisorsExhausted, ref ulong finalDivisorValue, ref int remainingStates, out ulong divisor, out int activeCount, out bool shouldTerminate) completions.Clear();
{ }
completions.Clear();
while (true) if (activeCount == 0)
{ {
if (remainingStates == 0) continue;
{ }
divisor = 0UL;
activeCount = 0;
shouldTerminate = true;
return false;
}
if (divisorsExhausted || nextDivisor > divisorLimit) Span<byte> hitsSpan = hitsBuffer.AsSpan(0, activeCount);
{ hitsSpan.Clear();
if (!divisorsExhausted) session.CheckDivisor(divisor, primeBuffer.AsSpan(0, activeCount), hitsSpan);
{
divisorsExhausted = true;
finalDivisorValue = nextDivisor;
}
ulong completionDivisor = finalDivisorValue; for (int j = 0; j < activeCount; j++)
for (int i = 0; i < states.Count; i++) {
{ if (hitsSpan[j] == 0)
var state = states[i]; {
if (state.Completed) continue;
{ }
continue;
}
state.Completed = true; int index = indexBuffer[j];
state.DetailedCheck = completionDivisor > state.AllowedMax; if (Interlocked.CompareExchange(ref stateFlags[index], ByDivisorStateComposite, ByDivisorStateActive) == ByDivisorStateActive)
states[i] = state; {
remainingStates--; Interlocked.Decrement(ref remainingStates);
completions.Add(new PendingResult(state.Prime, state.DetailedCheck, true)); composites.Add(new PendingResult(primeValues[index], detailedCheck: true, passedAllTests: false));
} }
}
divisor = 0UL; if (composites.Count > 0)
activeCount = 0; {
shouldTerminate = true; foreach (PendingResult result in composites)
return false; {
} PrintResult(result.Prime, searchedMersenne: true, detailedCheck: result.DetailedCheck, passedAllTests: result.PassedAllTests);
}
ulong divisorValue = nextDivisor; composites.Clear();
ulong candidate = divisorValue + 2UL; }
if (candidate <= divisorValue) }
{ }
divisorsExhausted = true; finally
finalDivisorValue = divisorValue; {
} ArrayPool<int>.Shared.Return(indexBuffer, clearArray: true);
else ArrayPool<ulong>.Shared.Return(primeBuffer, clearArray: true);
{ ArrayPool<byte>.Shared.Return(hitsBuffer, clearArray: true);
nextDivisor = candidate; }
if (nextDivisor > divisorLimit) });
{ }
divisorsExhausted = true;
finalDivisorValue = nextDivisor;
}
}
activeCount = 0; Task.WaitAll(workers);
for (int i = 0; i < states.Count; i++) }
{
var state = states[i];
if (state.Completed)
{
continue;
}
if (state.AllowedMax < divisorValue) private static ulong AcquireNextDivisor(ref ulong nextDivisor, ulong divisorLimit, ref int divisorsExhaustedFlag, ref long finalDivisorBits, out bool exhausted)
{ {
state.Completed = true; ref long nextDivisorBits = ref Unsafe.As<ulong, long>(ref nextDivisor);
state.DetailedCheck = true;
states[i] = state;
remainingStates--;
completions.Add(new PendingResult(state.Prime, state.DetailedCheck, true));
continue;
}
primeBuffer[activeCount] = state.Prime; while (true)
indexBuffer[activeCount] = i; {
activeCount++; if (Volatile.Read(ref divisorsExhaustedFlag) != 0)
} {
exhausted = true;
return 0UL;
}
if (activeCount == 0) long currentBits = Volatile.Read(ref nextDivisorBits);
{ ulong currentValue = unchecked((ulong)currentBits);
if (completions.Count == 0) if (currentValue > divisorLimit)
{ {
continue; if (Interlocked.CompareExchange(ref divisorsExhaustedFlag, 1, 0) == 0)
} {
Volatile.Write(ref finalDivisorBits, currentBits);
}
divisor = 0UL; exhausted = true;
shouldTerminate = remainingStates == 0 && (divisorsExhausted || nextDivisor > divisorLimit); return 0UL;
return false; }
}
divisor = divisorValue; ulong nextValue = currentValue + 2UL;
shouldTerminate = false; long nextBits = unchecked((long)nextValue);
return true; if (Interlocked.CompareExchange(ref nextDivisorBits, nextBits, currentBits) == currentBits)
} {
} exhausted = false;
return currentValue;
}
}
}
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction) private static int BuildPrimeBuffer(ulong divisor, ulong[] primeValues, ulong[] allowedMaxValues, int[] stateFlags, ulong[] primeBuffer, int[] indexBuffer, List<PendingResult> completions, ref int remainingStates)
{ {
using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); int activeCount = 0;
for (int i = 0; i < primeValues.Length; i++)
{
if (allowedMaxValues[i] < divisor)
{
if (Interlocked.CompareExchange(ref stateFlags[i], ByDivisorStateCompletedDetailed, ByDivisorStateActive) == ByDivisorStateActive)
{
Interlocked.Decrement(ref remainingStates);
completions.Add(new PendingResult(primeValues[i], detailedCheck: true, passedAllTests: true));
}
continue;
}
if (Volatile.Read(ref stateFlags[i]) != ByDivisorStateActive)
{
continue;
}
primeBuffer[activeCount] = primeValues[i];
indexBuffer[activeCount] = i;
activeCount++;
}
return activeCount;
}
private static void FinalizeRemainingStates(ulong[] primeValues, ulong[] allowedMaxValues, int[] stateFlags, ref int remainingStates, ref long finalDivisorBits, List<PendingResult> completions)
{
ulong finalDivisorValue = unchecked((ulong)Volatile.Read(ref finalDivisorBits));
for (int i = 0; i < primeValues.Length; i++)
{
if (Volatile.Read(ref stateFlags[i]) != ByDivisorStateActive)
{
continue;
}
bool detailed = finalDivisorValue > allowedMaxValues[i];
int newState = detailed ? ByDivisorStateCompletedDetailed : ByDivisorStateCompleted;
if (Interlocked.CompareExchange(ref stateFlags[i], newState, ByDivisorStateActive) != ByDivisorStateActive)
{
continue;
}
Interlocked.Decrement(ref remainingStates);
completions.Add(new PendingResult(primeValues[i], detailedCheck: detailed, passedAllTests: true));
}
}
private static unsafe void LoadResultsFile(string resultsFileName, Action<ulong, bool, bool> lineProcessorAction)
{
using FileStream readStream = new(resultsFileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using StreamReader reader = new(readStream); using StreamReader reader = new(readStream);
string? line; string? line;
bool headerSkipped = false; bool headerSkipped = false;