Buffer pending results as consecutive ranges

This commit is contained in:
Marek Lemańczyk committed 2025-09-19 22:12:12 +02:00
1 parent 4b4cbf3049
commit cb5020b060
1 file changed
+245 -68
+245 -68
View File
@@ -1,6 +1,8 @@
using System.Collections.Concurrent;
using System.Globalization; using System.Globalization;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks;
using Open.Numeric.Primes; using Open.Numeric.Primes;
namespace EvenPerfectBitScanner.ResultsParser; namespace EvenPerfectBitScanner.ResultsParser;
@@ -161,6 +163,8 @@ internal static class Program
Console.WriteLine(" EvenPerfectBitScanner.ResultsParser results.csv --p-min 89 --p-max 107"); Console.WriteLine(" EvenPerfectBitScanner.ResultsParser results.csv --p-min 89 --p-max 107");
} }
private const int CandidateBufferCapacity = 512;
private static void ProcessFile(in string inputPath, ulong pMin, ulong pMax) private static void ProcessFile(in string inputPath, ulong pMin, ulong pMax)
{ {
using IEnumerator<string> enumerator = File.ReadLines(inputPath).GetEnumerator(); using IEnumerator<string> enumerator = File.ReadLines(inputPath).GetEnumerator();
@@ -184,9 +188,12 @@ internal static class Program
} }
ulong currentPrime = primeEnumerator.Current; ulong currentPrime = primeEnumerator.Current;
List<CandidateResult> pendingResults = []; PendingCandidateList pendingResults = new();
List<CandidateResult> primeResults = []; List<CandidateResult> primeResults = [];
int consoleProgress = 0; int consoleProgress = 0;
ulong lastReportedCandidate = 0UL;
CandidateResult[] candidateBuffer = new CandidateResult[CandidateBufferCapacity];
int bufferedCount = 0;
ulong pEmpty = CandidateResult.Empty.P; ulong pEmpty = CandidateResult.Empty.P;
while (enumerator.MoveNext()) while (enumerator.MoveNext())
{ {
@@ -202,15 +209,28 @@ internal static class Program
continue; continue;
} }
InsertPendingResult(pendingResults, result); candidateBuffer[bufferedCount++] = result;
lastReportedCandidate = result.P;
if (bufferedCount == CandidateBufferCapacity)
{
ProcessCandidateBuffer(candidateBuffer.AsSpan(0, bufferedCount), pendingResults);
DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, false); DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, false);
bufferedCount = 0;
}
if (++consoleProgress == 10_000) if (++consoleProgress == 10_000)
{ {
Console.WriteLine($"Processed {result.P}"); Console.WriteLine($"Processed {lastReportedCandidate}");
consoleProgress = 0; consoleProgress = 0;
} }
} }
if (bufferedCount > 0)
{
ProcessCandidateBuffer(candidateBuffer.AsSpan(0, bufferedCount), pendingResults);
DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, false);
}
DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, true); DrainPrimeMatches(pendingResults, primeResults, ref currentPrime, primeEnumerator, true);
string rawOutputPath = BuildOutputPath(inputPath, "raw-primes-"); string rawOutputPath = BuildOutputPath(inputPath, "raw-primes-");
@@ -230,21 +250,7 @@ internal static class Program
SortByPrime(sortedResults); SortByPrime(sortedResults);
Console.WriteLine("Splitting results..."); Console.WriteLine("Splitting results...");
List<CandidateResult> passedResults = new(sortedResults.Count); SplitResults(sortedResults, out List<CandidateResult> passedResults, out List<CandidateResult> rejectedResults);
List<CandidateResult> rejectedResults = new(sortedResults.Count);
for (int i = 0; i < sortedResults.Count; i++)
{
CandidateResult candidate = sortedResults[i];
if (candidate.PassedAllTests)
{
passedResults.Add(candidate);
}
else
{
rejectedResults.Add(candidate);
}
}
Console.WriteLine("Saving files..."); Console.WriteLine("Saving files...");
string sortedOutputPath = BuildOutputPath(inputPath, "sorted-primes-"); string sortedOutputPath = BuildOutputPath(inputPath, "sorted-primes-");
@@ -323,6 +329,38 @@ internal static class Program
} }
} }
private static void ProcessCandidateBuffer(Span<CandidateResult> buffer, PendingCandidateList pendingResults)
{
if (buffer.Length == 0)
{
return;
}
buffer.Sort(CandidateResultComparer.Instance);
int start = 0;
int length = buffer.Length;
while (start < length)
{
int end = start + 1;
ulong previous = buffer[start].P;
while (end < length)
{
ulong current = buffer[end].P;
if (current > previous + 1)
{
break;
}
previous = current;
end++;
}
pendingResults.AddRange(buffer[start..end]);
start = end;
}
}
private static void SortByPrime(List<CandidateResult> results) private static void SortByPrime(List<CandidateResult> results)
{ {
if (results.Count < 2) if (results.Count < 2)
@@ -333,6 +371,86 @@ internal static class Program
results.Sort(CandidateResultComparer.Instance); results.Sort(CandidateResultComparer.Instance);
} }
private static void SplitResults(
List<CandidateResult> sortedResults,
out List<CandidateResult> passedResults,
out List<CandidateResult> rejectedResults)
{
int count = sortedResults.Count;
if (count == 0)
{
passedResults = [];
rejectedResults = [];
return;
}
if (count < Environment.ProcessorCount * 4)
{
passedResults = new List<CandidateResult>(count);
rejectedResults = new List<CandidateResult>(count);
for (int i = 0; i < count; i++)
{
CandidateResult candidate = sortedResults[i];
if (candidate.PassedAllTests)
{
passedResults.Add(candidate);
continue;
}
rejectedResults.Add(candidate);
}
return;
}
List<CandidateResult> sharedPassed = new(count);
List<CandidateResult> sharedRejected = new(count);
object passedLock = new();
object rejectedLock = new();
Parallel.ForEach(
Partitioner.Create(0, count),
() => new SplitAccumulator(),
(range, _, local) =>
{
List<CandidateResult> localPassed = local.Passed;
List<CandidateResult> localRejected = local.Rejected;
for (int i = range.Item1; i < range.Item2; i++)
{
CandidateResult candidate = sortedResults[i];
if (candidate.PassedAllTests)
{
localPassed.Add(candidate);
continue;
}
localRejected.Add(candidate);
}
return local;
},
local =>
{
if (local.Passed.Count > 0)
{
lock (passedLock)
{
sharedPassed.AddRange(local.Passed);
}
}
if (local.Rejected.Count > 0)
{
lock (rejectedLock)
{
sharedRejected.AddRange(local.Rejected);
}
}
});
passedResults = sharedPassed;
rejectedResults = sharedRejected;
}
private sealed class CandidateResultComparer : IComparer<CandidateResult> private sealed class CandidateResultComparer : IComparer<CandidateResult>
{ {
public static readonly CandidateResultComparer Instance = new(); public static readonly CandidateResultComparer Instance = new();
@@ -349,7 +467,7 @@ internal static class Program
} }
private static void DrainPrimeMatches( private static void DrainPrimeMatches(
List<CandidateResult> pendingResults, PendingCandidateList pendingResults,
List<CandidateResult> primeResults, List<CandidateResult> primeResults,
ref ulong currentPrime, ref ulong currentPrime,
IEnumerator<ulong> primeEnumerator, IEnumerator<ulong> primeEnumerator,
@@ -360,7 +478,7 @@ internal static class Program
return; return;
} }
RemoveEntriesBelowPrime(pendingResults, currentPrime); pendingResults.RemoveLessThan(currentPrime);
bool extractedPrime; bool extractedPrime;
while (pendingResults.Count > 0) while (pendingResults.Count > 0)
@@ -374,7 +492,7 @@ internal static class Program
return; return;
} }
RemoveEntriesBelowPrime(pendingResults, currentPrime); pendingResults.RemoveLessThan(currentPrime);
continue; continue;
} }
@@ -389,12 +507,12 @@ internal static class Program
return; return;
} }
RemoveEntriesBelowPrime(pendingResults, currentPrime); pendingResults.RemoveLessThan(currentPrime);
} }
} }
private static bool ExtractPrimeEntries( private static bool ExtractPrimeEntries(
List<CandidateResult> pendingResults, PendingCandidateList pendingResults,
List<CandidateResult> primeResults, List<CandidateResult> primeResults,
ulong prime) ulong prime)
{ {
@@ -403,54 +521,28 @@ internal static class Program
return false; return false;
} }
int startIndex = FindFirstIndexAtLeast(pendingResults, prime); if (!pendingResults.TryPeek(out CandidateResult next) || next.P != prime)
if (startIndex >= pendingResults.Count)
{ {
return false; return false;
} }
if (pendingResults[startIndex].P != prime) do
{ {
return false; primeResults.Add(pendingResults.Dequeue());
} }
while (pendingResults.TryPeek(out CandidateResult candidate) && candidate.P == prime);
int endIndex = startIndex + 1;
while (endIndex < pendingResults.Count && pendingResults[endIndex].P == prime)
{
endIndex++;
}
for (int i = startIndex; i < endIndex; i++)
{
primeResults.Add(pendingResults[i]);
}
pendingResults.RemoveRange(startIndex, endIndex - startIndex);
return true; return true;
} }
private static void RemoveEntriesBelowPrime(List<CandidateResult> pendingResults, ulong currentPrime)
{
if (pendingResults.Count == 0)
{
return;
}
int removeCount = FindFirstIndexAtLeast(pendingResults, currentPrime);
if (removeCount == 0)
{
return;
}
pendingResults.RemoveRange(0, removeCount);
}
private static bool TryAdvancePrime( private static bool TryAdvancePrime(
IEnumerator<ulong> primeEnumerator, IEnumerator<ulong> primeEnumerator,
ref ulong currentPrime, ref ulong currentPrime,
List<CandidateResult> pendingResults) PendingCandidateList pendingResults)
{ {
ulong minimumPending = pendingResults.Count > 0 ? pendingResults[0].P : 0UL; ulong minimumPending = pendingResults.Count > 0 && pendingResults.TryPeek(out CandidateResult pending)
? pending.P
: 0UL;
do do
{ {
if (!primeEnumerator.MoveNext()) if (!primeEnumerator.MoveNext())
@@ -466,29 +558,114 @@ internal static class Program
return true; return true;
} }
private sealed class PendingCandidateList
{
private readonly List<CandidateResult> _items = [];
public int Count => _items.Count;
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void InsertPendingResult(List<CandidateResult> pendingResults, CandidateResult result) => pendingResults.Insert(FindFirstIndexAtLeast(pendingResults, result.P), result); public void AddRange(ReadOnlySpan<CandidateResult> candidates)
private static int FindFirstIndexAtLeast(List<CandidateResult> pendingResults, ulong value)
{ {
int low = 0, if (candidates.Length == 0)
high = pendingResults.Count - 1,
mid;
while (low <= high)
{ {
mid = low + ((high - low) >> 1); return;
if (pendingResults[mid].P < value) }
List<CandidateResult> items = _items;
items.EnsureCapacity(items.Count + candidates.Length);
int insertIndex = FindInsertIndex(items, candidates[0].P);
int candidateIndex = 0;
int candidateCount = candidates.Length;
while (candidateIndex < candidateCount)
{
CandidateResult candidate = candidates[candidateIndex];
while (insertIndex < items.Count && items[insertIndex].P <= candidate.P)
{
insertIndex++;
}
items.Insert(insertIndex, candidate);
insertIndex++;
candidateIndex++;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryPeek(out CandidateResult result)
{
if (_items.Count == 0)
{
result = default;
return false;
}
result = _items[0];
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public CandidateResult Dequeue()
{
CandidateResult value = _items[0];
_items.RemoveAt(0);
return value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void RemoveLessThan(ulong prime)
{
int removeCount = 0;
List<CandidateResult> items = _items;
int count = items.Count;
while (removeCount < count && items[removeCount].P < prime)
{
removeCount++;
}
if (removeCount > 0)
{
items.RemoveRange(0, removeCount);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
_items.Clear();
}
private static int FindInsertIndex(List<CandidateResult> items, ulong value)
{
int low = 0;
int high = items.Count;
while (low < high)
{
int mid = (low + high) >> 1;
if (items[mid].P < value)
{ {
low = mid + 1; low = mid + 1;
continue; continue;
} }
high = mid - 1; high = mid;
} }
return low; return low;
} }
}
private readonly struct SplitAccumulator
{
public readonly List<CandidateResult> Passed;
public readonly List<CandidateResult> Rejected;
public SplitAccumulator()
{
Passed = [];
Rejected = [];
}
}
private readonly record struct CandidateResult(ulong P, bool PassedAllTests, string Csv) private readonly record struct CandidateResult(ulong P, bool PassedAllTests, string Csv)
{ {