Merge pull request #28 from mlemanczyk/codex/optimize-resultsparser-for-better-performance
Buffer pending results as consecutive ranges
This commit is contained in:
1 file changed
+245
-68
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user