Merge pull request #93 from mlemanczyk/codex/implement-gpu-optimizations-for-evenperfectbitscanner
Clarify GPU divisor batch guard rationale
This commit is contained in:
9 files changed
+367
-110
No files matched your search
@@ -14,7 +14,7 @@ To build `EvenPerfectScanner` run:
|
|||||||
dotnet build EvenPerfectScanner.sln
|
dotnet build EvenPerfectScanner.sln
|
||||||
```
|
```
|
||||||
|
|
||||||
Run only the tests related to the solution you modify. Always run tests without --no-build parameter to avoid run failures due to missing assembly files. Tests should be always written using XUnit with FluentAssertions. Before executing any tests, make sure their running time is reasonable; skip test suites that are expected to take excessively long.
|
Run only the tests related to the solution you modify. Always run tests without --no-build parameter to avoid run failures due to missing assembly files. Tests should be always written using XUnit with FluentAssertions. Before executing any tests, make sure their running time is reasonable; skip test suites that are expected to take excessively long. When running tests, always provide both an explicit timeout guard and the Category=Fast filter so that only the fast suite executes.
|
||||||
|
|
||||||
### Test execution time policy
|
### Test execution time policy
|
||||||
|
|
||||||
@@ -35,7 +35,8 @@ Run only the tests related to the solution you modify. Always run tests without
|
|||||||
|
|
||||||
- Linux/macOS (bash):
|
- Linux/macOS (bash):
|
||||||
- Use GNU timeout (install coreutils if needed on macOS):
|
- Use GNU timeout (install coreutils if needed on macOS):
|
||||||
- `timeout 120s dotnet test <path-to>.csproj -c Debug --filter "FullyQualifiedName~SomeTests"`
|
- `timeout 120s dotnet test <path-to>.csproj -c Debug --filter "Category=Fast&FullyQualifiedName~SomeTests"`
|
||||||
|
- Always specify `timeout 120s` (or a shorter limit when appropriate) and include `Category=Fast` in every filter.
|
||||||
- Note: exit code 124 indicates timeout; handle accordingly in scripts.
|
- Note: exit code 124 indicates timeout; handle accordingly in scripts.
|
||||||
|
|
||||||
- Codex/agent runs: when invoked as separate steps where inline time control isn’t available, wrap calls through the shell using the guards above rather than calling `dotnet test` directly from the tool.
|
- Codex/agent runs: when invoked as separate steps where inline time control isn’t available, wrap calls through the shell using the guards above rather than calling `dotnet test` directly from the tool.
|
||||||
@@ -52,11 +53,11 @@ The only .Net SDK available in Codex environment is .Net 8.0. It'll be periodica
|
|||||||
|
|
||||||
Always enter blank line after closing "}" parenthesis, unless there is a consecutive closing "}" bracket. IF's, loops, in practice all code blocks should be put in their "{ (...) }" blocks. Even if there is only a single line in such a code block. Always use the latest officially released language features. All .csproj files should define nullable = enabled and we should always use "?" and/or "!" where appropriate.
|
Always enter blank line after closing "}" parenthesis, unless there is a consecutive closing "}" bracket. IF's, loops, in practice all code blocks should be put in their "{ (...) }" blocks. Even if there is only a single line in such a code block. Always use the latest officially released language features. All .csproj files should define nullable = enabled and we should always use "?" and/or "!" where appropriate.
|
||||||
|
|
||||||
Never ever run tests marked with trait Category = Slow. Only run tests with category "Fast", when you want to validate if you haven't broken anything. If there are no categories assigned to the tests, when you attempt running them always watch out the 2 mins execution time, to prevent hanging up on test execution. In such case, marked all timeout out tests with category Slow and tests which executed within time limit, even if they failed, mark with category Fast, so that we can recognize them in the future.
|
Never ever run tests marked with trait Category = Slow. Always invoke `dotnet test` (or equivalent wrappers) with both an explicit timeout and the `Category=Fast` filter; never run tests without specifying these arguments. If there are no categories assigned yet, categorize them before running or skip the execution.
|
||||||
|
|
||||||
There is only one agents file - this one. Don't search for another one.
|
There is only one agents file - this one. Don't search for another one.
|
||||||
|
|
||||||
Always use indentation with 4 spaces in source code files. Do not use 8-space indentation or tabs.
|
Always use indentation with four space characters in source code files, even if a file currently uses tabs. Increase indentation level by exactly one indentation unit (four spaces) per nested block. When parameters or long expressions wrap, indent the continuation with two indentation units for clarity.
|
||||||
|
|
||||||
Keep the divisor cycle cache limited to three blocks in memory (the base snapshot plus at most two additional blocks) and always start background computation of the next block immediately after loading the first block from disk and whenever work begins on the most recently generated block.
|
Keep the divisor cycle cache limited to three blocks in memory (the base snapshot plus at most two additional blocks) and always start background computation of the next block immediately after loading the first block from disk and whenever work begins on the most recently generated block.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
public class AlphaCacheTests
|
public class AlphaCacheTests
|
||||||
{
|
{
|
||||||
[Fact(Skip = "TODO: implement test")]
|
[Fact(Skip = "TODO: implement test")]
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void Placeholder()
|
public void Placeholder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
public class AlphaCalculationsTests
|
public class AlphaCalculationsTests
|
||||||
{
|
{
|
||||||
[Fact(Skip = "TODO: implement test")]
|
[Fact(Skip = "TODO: implement test")]
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void Placeholder()
|
public void Placeholder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
public class InputParserTests
|
public class InputParserTests
|
||||||
{
|
{
|
||||||
[Fact(Skip = "TODO: implement test")]
|
[Fact(Skip = "TODO: implement test")]
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void Placeholder()
|
public void Placeholder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
public class KRangeFinderTests
|
public class KRangeFinderTests
|
||||||
{
|
{
|
||||||
[Fact(Skip = "TODO: implement test")]
|
[Fact(Skip = "TODO: implement test")]
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void Placeholder()
|
public void Placeholder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
using System;
|
||||||
|
using System.Reflection;
|
||||||
using FluentAssertions;
|
using FluentAssertions;
|
||||||
using PerfectNumbers.Core;
|
using PerfectNumbers.Core;
|
||||||
using PerfectNumbers.Core.Gpu;
|
|
||||||
using PerfectNumbers.Core.Cpu;
|
using PerfectNumbers.Core.Cpu;
|
||||||
using System.Reflection;
|
using PerfectNumbers.Core.Gpu;
|
||||||
using Xunit;
|
using Xunit;
|
||||||
|
|
||||||
namespace PerfectNumbers.Core.Tests;
|
namespace PerfectNumbers.Core.Tests;
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
public class RleBlacklistTests
|
public class RleBlacklistTests
|
||||||
{
|
{
|
||||||
[Fact(Skip = "TODO: implement test")]
|
[Fact(Skip = "TODO: implement test")]
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void Placeholder()
|
public void Placeholder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ namespace PerfectNumbers.Core.Tests;
|
|||||||
public class TextFileWriterTests
|
public class TextFileWriterTests
|
||||||
{
|
{
|
||||||
[Fact(Skip = "TODO: implement test")]
|
[Fact(Skip = "TODO: implement test")]
|
||||||
[Trait("Category", "Fast")]
|
|
||||||
public void Placeholder()
|
public void Placeholder()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -154,13 +154,59 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int batchCapacity = (int)divisorsBuffer.Length;
|
return useCycles
|
||||||
|
? CheckDivisorsWithCycles(
|
||||||
|
prime,
|
||||||
|
allowedMax,
|
||||||
|
accelerator,
|
||||||
|
kernel,
|
||||||
|
divisorsBuffer,
|
||||||
|
exponentBuffer,
|
||||||
|
hitsBuffer,
|
||||||
|
divisors,
|
||||||
|
exponents,
|
||||||
|
hits,
|
||||||
|
divisorData,
|
||||||
|
ref lastProcessed,
|
||||||
|
ref processedCount,
|
||||||
|
out coveredRange)
|
||||||
|
: CheckDivisorsWithoutCycles(
|
||||||
|
prime,
|
||||||
|
allowedMax,
|
||||||
|
accelerator,
|
||||||
|
kernel,
|
||||||
|
divisorsBuffer,
|
||||||
|
exponentBuffer,
|
||||||
|
hitsBuffer,
|
||||||
|
divisors,
|
||||||
|
exponents,
|
||||||
|
hits,
|
||||||
|
divisorData,
|
||||||
|
ref lastProcessed,
|
||||||
|
ref processedCount,
|
||||||
|
out coveredRange);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CheckDivisorsWithoutCycles(
|
||||||
|
ulong prime,
|
||||||
|
ulong allowedMax,
|
||||||
|
Accelerator accelerator,
|
||||||
|
Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> kernel,
|
||||||
|
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
||||||
|
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer,
|
||||||
|
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
|
||||||
|
in ulong[] divisors,
|
||||||
|
ulong[] exponents,
|
||||||
|
byte[] hits,
|
||||||
|
MontgomeryDivisorData[] divisorData,
|
||||||
|
ref ulong lastProcessed,
|
||||||
|
ref ulong processedCount,
|
||||||
|
out bool coveredRange)
|
||||||
|
{
|
||||||
|
int batchCapacity = (int)divisorsBuffer.Length;
|
||||||
bool composite = false;
|
bool composite = false;
|
||||||
bool processedAll = false;
|
bool processedAll = false;
|
||||||
ulong currentDivisor = 3UL, next, nextDivisor;
|
ulong currentDivisor = 3UL;
|
||||||
int batchSize, i;
|
|
||||||
bool reachedEndInBatch;
|
|
||||||
|
|
||||||
Span<MontgomeryDivisorData> divisorDataSpan;
|
Span<MontgomeryDivisorData> divisorDataSpan;
|
||||||
Span<ulong> divisorSpan;
|
Span<ulong> divisorSpan;
|
||||||
@@ -169,71 +215,38 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> divisorView;
|
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> divisorView;
|
||||||
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
||||||
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||||
DivisorCycleCache divisorCyclesCache = DivisorCycleCache.Shared;
|
|
||||||
DivisorCycleCache.CycleBlock? cycleLease = useCycles ? divisorCyclesCache.Acquire(currentDivisor) : null;
|
|
||||||
ulong cycleLeaseEnd = cycleLease?.End ?? 0UL;
|
|
||||||
|
|
||||||
while (currentDivisor <= allowedMax)
|
while (currentDivisor <= allowedMax)
|
||||||
{
|
{
|
||||||
batchSize = 0;
|
int batchSize = 0;
|
||||||
reachedEndInBatch = false;
|
bool reachedEndInBatch = false;
|
||||||
|
|
||||||
|
// The GPU buffers are sized exactly to the configured batch capacity, so we
|
||||||
|
// must stop once the current batch is full to avoid writing past the spans.
|
||||||
while (batchSize < batchCapacity && currentDivisor <= allowedMax)
|
while (batchSize < batchCapacity && currentDivisor <= allowedMax)
|
||||||
{
|
{
|
||||||
nextDivisor = currentDivisor + 2UL;
|
ulong nextDivisor = currentDivisor + 2UL;
|
||||||
processedCount++;
|
processedCount++;
|
||||||
|
|
||||||
bool includeDivisor = true;
|
|
||||||
ulong exponent = prime;
|
|
||||||
|
|
||||||
if (useCycles)
|
|
||||||
{
|
|
||||||
if (currentDivisor > cycleLeaseEnd)
|
|
||||||
{
|
|
||||||
cycleLease?.Dispose();
|
|
||||||
cycleLease = divisorCyclesCache.Acquire(currentDivisor);
|
|
||||||
cycleLeaseEnd = cycleLease.End;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong cycleLength = cycleLease!.GetCycle(currentDivisor);
|
|
||||||
if (cycleLength > 0UL)
|
|
||||||
{
|
|
||||||
ulong remainder = prime % cycleLength;
|
|
||||||
if (remainder != 0UL)
|
|
||||||
{
|
|
||||||
includeDivisor = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
exponent = remainder;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (includeDivisor)
|
|
||||||
{
|
|
||||||
divisors[batchSize] = currentDivisor;
|
divisors[batchSize] = currentDivisor;
|
||||||
exponents[batchSize] = exponent;
|
exponents[batchSize] = prime;
|
||||||
batchSize++;
|
batchSize++;
|
||||||
}
|
|
||||||
|
|
||||||
lastProcessed = currentDivisor;
|
lastProcessed = currentDivisor;
|
||||||
next = nextDivisor;
|
|
||||||
|
|
||||||
if (next <= currentDivisor)
|
if (nextDivisor <= currentDivisor)
|
||||||
{
|
{
|
||||||
reachedEndInBatch = true;
|
reachedEndInBatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (next > allowedMax)
|
if (nextDivisor > allowedMax)
|
||||||
{
|
{
|
||||||
currentDivisor = next;
|
currentDivisor = nextDivisor;
|
||||||
reachedEndInBatch = true;
|
reachedEndInBatch = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentDivisor = next;
|
currentDivisor = nextDivisor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (batchSize == 0)
|
if (batchSize == 0)
|
||||||
@@ -248,15 +261,14 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
||||||
|
divisorSpan = divisors.AsSpan(0, batchSize);
|
||||||
|
exponentSpan = exponents.AsSpan(0, batchSize);
|
||||||
|
hitsSpan = hits.AsSpan(0, batchSize);
|
||||||
divisorView = divisorsBuffer.View.SubView(0, batchSize);
|
divisorView = divisorsBuffer.View.SubView(0, batchSize);
|
||||||
exponentView = exponentBuffer.View.SubView(0, batchSize);
|
exponentView = exponentBuffer.View.SubView(0, batchSize);
|
||||||
hitsView = hitsBuffer.View.SubView(0, batchSize);
|
hitsView = hitsBuffer.View.SubView(0, batchSize);
|
||||||
|
|
||||||
divisorSpan = divisors.AsSpan(0, batchSize);
|
for (int i = 0; i < batchSize; i++)
|
||||||
exponentSpan = exponents.AsSpan(0, batchSize);
|
|
||||||
hitsSpan = hits.AsSpan(0, batchSize);
|
|
||||||
|
|
||||||
for (i = 0; i < batchSize; i++)
|
|
||||||
{
|
{
|
||||||
divisorDataSpan[i] = MontgomeryDivisorDataCache.Get(divisorSpan[i]);
|
divisorDataSpan[i] = MontgomeryDivisorDataCache.Get(divisorSpan[i]);
|
||||||
}
|
}
|
||||||
@@ -264,10 +276,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
||||||
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), batchSize);
|
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), batchSize);
|
||||||
kernel(batchSize, divisorView, exponentView, hitsView);
|
kernel(batchSize, divisorView, exponentView, hitsView);
|
||||||
accelerator.Synchronize();
|
|
||||||
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
||||||
|
|
||||||
for (i = 0; i < batchSize; i++)
|
for (int i = 0; i < batchSize; i++)
|
||||||
{
|
{
|
||||||
if (hitsSpan[i] != 0)
|
if (hitsSpan[i] != 0)
|
||||||
{
|
{
|
||||||
@@ -281,6 +292,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
{
|
{
|
||||||
lastProcessed = divisorSpan[batchSize - 1];
|
lastProcessed = divisorSpan[batchSize - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (composite)
|
if (composite)
|
||||||
{
|
{
|
||||||
break;
|
break;
|
||||||
@@ -293,9 +305,173 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cycleLease?.Dispose();
|
|
||||||
coveredRange = composite || processedAll || currentDivisor > allowedMax;
|
coveredRange = composite || processedAll || currentDivisor > allowedMax;
|
||||||
|
return composite;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CheckDivisorsWithCycles(
|
||||||
|
ulong prime,
|
||||||
|
ulong allowedMax,
|
||||||
|
Accelerator accelerator,
|
||||||
|
Action<Index1D, ArrayView<MontgomeryDivisorData>, ArrayView<ulong>, ArrayView<byte>> kernel,
|
||||||
|
MemoryBuffer1D<MontgomeryDivisorData, Stride1D.Dense> divisorsBuffer,
|
||||||
|
MemoryBuffer1D<ulong, Stride1D.Dense> exponentBuffer,
|
||||||
|
MemoryBuffer1D<byte, Stride1D.Dense> hitsBuffer,
|
||||||
|
in ulong[] divisors,
|
||||||
|
ulong[] exponents,
|
||||||
|
byte[] hits,
|
||||||
|
MontgomeryDivisorData[] divisorData,
|
||||||
|
ref ulong lastProcessed,
|
||||||
|
ref ulong processedCount,
|
||||||
|
out bool coveredRange)
|
||||||
|
{
|
||||||
|
int batchCapacity = (int)divisorsBuffer.Length;
|
||||||
|
bool composite = false;
|
||||||
|
bool processedAll = false;
|
||||||
|
ulong currentDivisor = 3UL;
|
||||||
|
ulong cycleLeaseEnd;
|
||||||
|
|
||||||
|
Span<MontgomeryDivisorData> divisorDataSpan;
|
||||||
|
Span<ulong> divisorSpan;
|
||||||
|
Span<ulong> exponentSpan;
|
||||||
|
Span<byte> hitsSpan;
|
||||||
|
ArrayView1D<MontgomeryDivisorData, Stride1D.Dense> divisorView;
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> exponentView;
|
||||||
|
ArrayView1D<byte, Stride1D.Dense> hitsView;
|
||||||
|
|
||||||
|
DivisorCycleCache divisorCyclesCache = DivisorCycleCache.Shared;
|
||||||
|
DivisorCycleCache.CycleBlock cycleLease = divisorCyclesCache.Acquire(currentDivisor);
|
||||||
|
cycleLeaseEnd = cycleLease.End;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
while (currentDivisor <= allowedMax)
|
||||||
|
{
|
||||||
|
int batchSize = 0;
|
||||||
|
bool reachedEndInBatch = false;
|
||||||
|
|
||||||
|
// Batch writes share the same buffers as the kernel launch, so we guard
|
||||||
|
// against exceeding the rented capacity on each iteration.
|
||||||
|
while (batchSize < batchCapacity && currentDivisor <= allowedMax)
|
||||||
|
{
|
||||||
|
ulong nextDivisor = currentDivisor + 2UL;
|
||||||
|
processedCount++;
|
||||||
|
|
||||||
|
if (currentDivisor > cycleLeaseEnd)
|
||||||
|
{
|
||||||
|
cycleLease.Dispose();
|
||||||
|
cycleLease = divisorCyclesCache.Acquire(currentDivisor);
|
||||||
|
cycleLeaseEnd = cycleLease.End;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool includeDivisor = true;
|
||||||
|
ulong exponent = prime;
|
||||||
|
ulong cycleLength = cycleLease.GetCycle(currentDivisor);
|
||||||
|
if (cycleLength > 0UL)
|
||||||
|
{
|
||||||
|
ulong remainder = prime;
|
||||||
|
if (remainder >= cycleLength)
|
||||||
|
{
|
||||||
|
remainder %= cycleLength;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (remainder != 0UL)
|
||||||
|
{
|
||||||
|
includeDivisor = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
exponent = 0UL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (includeDivisor)
|
||||||
|
{
|
||||||
|
divisors[batchSize] = currentDivisor;
|
||||||
|
exponents[batchSize] = exponent;
|
||||||
|
batchSize++;
|
||||||
|
}
|
||||||
|
|
||||||
|
lastProcessed = currentDivisor;
|
||||||
|
|
||||||
|
if (nextDivisor <= currentDivisor)
|
||||||
|
{
|
||||||
|
reachedEndInBatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextDivisor > allowedMax)
|
||||||
|
{
|
||||||
|
currentDivisor = nextDivisor;
|
||||||
|
reachedEndInBatch = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
currentDivisor = nextDivisor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (batchSize == 0)
|
||||||
|
{
|
||||||
|
if (reachedEndInBatch)
|
||||||
|
{
|
||||||
|
processedAll = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
divisorDataSpan = divisorData.AsSpan(0, batchSize);
|
||||||
|
divisorSpan = divisors.AsSpan(0, batchSize);
|
||||||
|
exponentSpan = exponents.AsSpan(0, batchSize);
|
||||||
|
hitsSpan = hits.AsSpan(0, batchSize);
|
||||||
|
divisorView = divisorsBuffer.View.SubView(0, batchSize);
|
||||||
|
exponentView = exponentBuffer.View.SubView(0, batchSize);
|
||||||
|
hitsView = hitsBuffer.View.SubView(0, batchSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < batchSize; i++)
|
||||||
|
{
|
||||||
|
divisorDataSpan[i] = MontgomeryDivisorDataCache.Get(divisorSpan[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
divisorView.CopyFromCPU(ref MemoryMarshal.GetReference(divisorDataSpan), batchSize);
|
||||||
|
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSpan), batchSize);
|
||||||
|
kernel(batchSize, divisorView, exponentView, hitsView);
|
||||||
|
hitsView.CopyToCPU(ref MemoryMarshal.GetReference(hitsSpan), batchSize);
|
||||||
|
|
||||||
|
for (int i = 0; i < batchSize; i++)
|
||||||
|
{
|
||||||
|
if (hitsSpan[i] != 0)
|
||||||
|
{
|
||||||
|
composite = true;
|
||||||
|
lastProcessed = divisorSpan[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!composite)
|
||||||
|
{
|
||||||
|
lastProcessed = divisorSpan[batchSize - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (composite)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reachedEndInBatch)
|
||||||
|
{
|
||||||
|
processedAll = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
cycleLease.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
coveredRange = composite || processedAll || currentDivisor > allowedMax;
|
||||||
return composite;
|
return composite;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -352,8 +528,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer;
|
private MemoryBuffer1D<ulong, Stride1D.Dense> _exponentsBuffer;
|
||||||
private MemoryBuffer1D<ulong, Stride1D.Dense> _resultsBuffer;
|
private MemoryBuffer1D<ulong, Stride1D.Dense> _resultsBuffer;
|
||||||
private ulong[] _hostBuffer;
|
private ulong[] _hostBuffer;
|
||||||
private int[] _positionBuffer;
|
private int[]? _positionBuffer;
|
||||||
private byte[] _modeBuffer;
|
|
||||||
private int _capacity;
|
private int _capacity;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
@@ -367,8 +542,7 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
_exponentsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
_exponentsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
_resultsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
_resultsBuffer = _accelerator.Allocate1D<ulong>(_capacity);
|
||||||
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
_hostBuffer = ArrayPool<ulong>.Shared.Rent(_capacity);
|
||||||
_positionBuffer = ArrayPool<int>.Shared.Rent(_capacity);
|
_positionBuffer = null;
|
||||||
_modeBuffer = ArrayPool<byte>.Shared.Rent(_capacity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void Reset()
|
internal void Reset()
|
||||||
@@ -384,13 +558,16 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
MersenneNumberDivisorByDivisorGpuTester owner = _owner;
|
MersenneNumberDivisorByDivisorGpuTester owner = _owner;
|
||||||
int gpuBatchSize = owner._gpuBatchSize;
|
|
||||||
int primesLength = primes.Length;
|
int primesLength = primes.Length;
|
||||||
if (primesLength == 0)
|
if (primesLength == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The production pipeline never adjusts the GPU batch size after a session
|
||||||
|
// is initialized, so the buffers allocated in the constructor remain
|
||||||
|
// large enough for every launch.
|
||||||
|
|
||||||
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
MontgomeryDivisorData divisorData = MontgomeryDivisorDataCache.Get(divisor);
|
||||||
var exponentStepper = new ExponentRemainderStepper(divisorData);
|
var exponentStepper = new ExponentRemainderStepper(divisorData);
|
||||||
if (!exponentStepper.IsValidModulus)
|
if (!exponentStepper.IsValidModulus)
|
||||||
@@ -400,30 +577,35 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool useDivisorCycles = owner._useDivisorCycles;
|
bool useDivisorCycles = owner._useDivisorCycles;
|
||||||
if (useDivisorCycles && divisorCycle == 0UL)
|
if (!useDivisorCycles)
|
||||||
|
{
|
||||||
|
ProcessWithoutCycle(ref exponentStepper, divisorData, primes, hits);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (divisorCycle == 0UL)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"Missing divisor cycle for divisor {divisor}.");
|
throw new InvalidOperationException($"Missing divisor cycle for divisor {divisor}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
CycleRemainderStepper cycleStepper = default;
|
var cycleStepper = new CycleRemainderStepper(divisorCycle);
|
||||||
ulong initialCycleRemainder = 0UL;
|
ulong initialCycleRemainder = cycleStepper.Initialize(primes[0]);
|
||||||
bool cycleRemainderPending = false;
|
ProcessWithCycle(ref exponentStepper, ref cycleStepper, initialCycleRemainder, divisorCycle, divisorData, primes, hits);
|
||||||
if (useDivisorCycles)
|
|
||||||
{
|
|
||||||
cycleStepper = new CycleRemainderStepper(divisorCycle);
|
|
||||||
initialCycleRemainder = cycleStepper.Initialize(primes[0]);
|
|
||||||
cycleRemainderPending = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Accelerator accelerator = _accelerator;
|
private void ProcessWithoutCycle(
|
||||||
|
ref ExponentRemainderStepper exponentStepper,
|
||||||
|
in MontgomeryDivisorData divisorData,
|
||||||
|
ReadOnlySpan<ulong> primes,
|
||||||
|
Span<byte> hits)
|
||||||
|
{
|
||||||
Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel;
|
Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel;
|
||||||
ArrayView1D<ulong, Stride1D.Dense> exponentsView = _exponentsBuffer.View;
|
ArrayView1D<ulong, Stride1D.Dense> exponentsView = _exponentsBuffer.View;
|
||||||
ArrayView1D<ulong, Stride1D.Dense> resultsView = _resultsBuffer.View;
|
ArrayView1D<ulong, Stride1D.Dense> resultsView = _resultsBuffer.View;
|
||||||
|
|
||||||
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, _capacity);
|
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, _capacity);
|
||||||
Span<int> positionSpan = _positionBuffer.AsSpan(0, _capacity);
|
|
||||||
Span<byte> modeSpan = _modeBuffer.AsSpan(0, _capacity);
|
|
||||||
|
|
||||||
|
int primesLength = primes.Length;
|
||||||
|
int gpuBatchSize = _owner._gpuBatchSize;
|
||||||
int offset = 0;
|
int offset = 0;
|
||||||
|
|
||||||
while (offset < primesLength)
|
while (offset < primesLength)
|
||||||
@@ -431,62 +613,140 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
int batchSize = Math.Min(gpuBatchSize, primesLength - offset);
|
int batchSize = Math.Min(gpuBatchSize, primesLength - offset);
|
||||||
ReadOnlySpan<ulong> primesSlice = primes.Slice(offset, batchSize);
|
ReadOnlySpan<ulong> primesSlice = primes.Slice(offset, batchSize);
|
||||||
Span<byte> hitsSlice = hits.Slice(offset, batchSize);
|
Span<byte> hitsSlice = hits.Slice(offset, batchSize);
|
||||||
hitsSlice.Clear();
|
Span<ulong> exponentSlice = hostSpan[..batchSize];
|
||||||
|
|
||||||
int computeCount = 0;
|
bool hasState = exponentStepper.HasState;
|
||||||
bool localHasState = exponentStepper.HasState;
|
ulong previousExponent = exponentStepper.PreviousExponent;
|
||||||
ulong localPreviousExponent = exponentStepper.PreviousExponent;
|
|
||||||
|
|
||||||
|
int startIndex = 0;
|
||||||
|
if (!hasState)
|
||||||
|
{
|
||||||
|
ulong primeValue = primesSlice[0];
|
||||||
|
exponentSlice[0] = primeValue;
|
||||||
|
previousExponent = primeValue;
|
||||||
|
hasState = true;
|
||||||
|
startIndex = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startIndex; i < batchSize; i++)
|
||||||
|
{
|
||||||
|
ulong primeValue = primesSlice[i];
|
||||||
|
exponentSlice[i] = primeValue - previousExponent;
|
||||||
|
previousExponent = primeValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> exponentView = exponentsView.SubView(0, batchSize);
|
||||||
|
exponentView.CopyFromCPU(ref MemoryMarshal.GetReference(exponentSlice), batchSize);
|
||||||
|
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> resultView = resultsView.SubView(0, batchSize);
|
||||||
|
kernel(batchSize, divisorData, exponentView, resultView);
|
||||||
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), batchSize);
|
||||||
|
|
||||||
|
bool requiresInitialization = !exponentStepper.HasState;
|
||||||
for (int i = 0; i < batchSize; i++)
|
for (int i = 0; i < batchSize; i++)
|
||||||
{
|
{
|
||||||
ulong primeValue = primesSlice[i];
|
ulong primeValue = primesSlice[i];
|
||||||
|
ulong montgomeryResult = exponentSlice[i];
|
||||||
|
bool isUnity;
|
||||||
|
|
||||||
if (useDivisorCycles)
|
if (requiresInitialization)
|
||||||
{
|
{
|
||||||
ulong remainder;
|
exponentStepper.TryInitializeFromMontgomeryResult(primeValue, montgomeryResult, out isUnity);
|
||||||
if (cycleRemainderPending)
|
requiresInitialization = false;
|
||||||
{
|
|
||||||
remainder = initialCycleRemainder;
|
|
||||||
cycleRemainderPending = false;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
remainder = cycleStepper.ComputeNext(primeValue);
|
exponentStepper.TryAdvanceWithMontgomeryDelta(primeValue, montgomeryResult, out isUnity);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hitsSlice[i] = isUnity ? (byte)1 : (byte)0;
|
||||||
|
}
|
||||||
|
|
||||||
|
offset += batchSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessWithCycle(
|
||||||
|
ref ExponentRemainderStepper exponentStepper,
|
||||||
|
ref CycleRemainderStepper cycleStepper,
|
||||||
|
ulong initialCycleRemainder,
|
||||||
|
ulong divisorCycle,
|
||||||
|
in MontgomeryDivisorData divisorData,
|
||||||
|
ReadOnlySpan<ulong> primes,
|
||||||
|
Span<byte> hits)
|
||||||
|
{
|
||||||
|
Action<Index1D, MontgomeryDivisorData, ArrayView<ulong>, ArrayView<ulong>> kernel = _kernel;
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> exponentsView = _exponentsBuffer.View;
|
||||||
|
ArrayView1D<ulong, Stride1D.Dense> resultsView = _resultsBuffer.View;
|
||||||
|
int capacity = _capacity;
|
||||||
|
Span<ulong> hostSpan = _hostBuffer.AsSpan(0, capacity);
|
||||||
|
int[]? positionBuffer = _positionBuffer;
|
||||||
|
if (positionBuffer is null)
|
||||||
|
{
|
||||||
|
// Session construction already enforces the batch capacity, so we only
|
||||||
|
// need to rent the position buffer on the first cycle-enabled invocation.
|
||||||
|
positionBuffer = ArrayPool<int>.Shared.Rent(capacity);
|
||||||
|
_positionBuffer = positionBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<int> positionSpan = positionBuffer.AsSpan(0, capacity);
|
||||||
|
|
||||||
|
int gpuBatchSize = _owner._gpuBatchSize;
|
||||||
|
int primesLength = primes.Length;
|
||||||
|
int offset = 0;
|
||||||
|
bool cycleRemainderPending = true;
|
||||||
|
|
||||||
|
while (offset < primesLength)
|
||||||
|
{
|
||||||
|
int batchSize = Math.Min(gpuBatchSize, primesLength - offset);
|
||||||
|
ReadOnlySpan<ulong> primesSlice = primes.Slice(offset, batchSize);
|
||||||
|
Span<byte> hitsSlice = hits.Slice(offset, batchSize);
|
||||||
|
|
||||||
|
int computeCount = 0;
|
||||||
|
bool hasState = exponentStepper.HasState;
|
||||||
|
ulong previousExponent = exponentStepper.PreviousExponent;
|
||||||
|
bool useInitialRemainder = cycleRemainderPending;
|
||||||
|
|
||||||
|
for (int i = 0; i < batchSize; i++)
|
||||||
|
{
|
||||||
|
hitsSlice[i] = 0;
|
||||||
|
ulong primeValue = primesSlice[i];
|
||||||
|
|
||||||
|
ulong remainder = useInitialRemainder
|
||||||
|
? initialCycleRemainder
|
||||||
|
: cycleStepper.ComputeNext(primeValue);
|
||||||
|
useInitialRemainder = false;
|
||||||
|
|
||||||
if (remainder != 0UL)
|
if (remainder != 0UL)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ulong workValue;
|
ulong workValue;
|
||||||
byte mode;
|
if (!hasState)
|
||||||
|
|
||||||
if (!localHasState || primeValue <= localPreviousExponent)
|
|
||||||
{
|
{
|
||||||
mode = 0;
|
|
||||||
workValue = primeValue;
|
workValue = primeValue;
|
||||||
localHasState = true;
|
hasState = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
mode = 1;
|
workValue = primeValue - previousExponent;
|
||||||
workValue = primeValue - localPreviousExponent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
localPreviousExponent = primeValue;
|
previousExponent = primeValue;
|
||||||
|
|
||||||
if (useDivisorCycles)
|
if (workValue >= divisorCycle)
|
||||||
{
|
{
|
||||||
workValue %= divisorCycle;
|
workValue %= divisorCycle;
|
||||||
}
|
}
|
||||||
|
|
||||||
positionSpan[computeCount] = i;
|
|
||||||
hostSpan[computeCount] = workValue;
|
hostSpan[computeCount] = workValue;
|
||||||
modeSpan[computeCount] = mode;
|
positionSpan[computeCount] = i;
|
||||||
computeCount++;
|
computeCount++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cycleRemainderPending = useInitialRemainder;
|
||||||
|
|
||||||
if (computeCount > 0)
|
if (computeCount > 0)
|
||||||
{
|
{
|
||||||
Span<ulong> exponentSlice = hostSpan[..computeCount];
|
Span<ulong> exponentSlice = hostSpan[..computeCount];
|
||||||
@@ -495,9 +755,9 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
|
|
||||||
ArrayView1D<ulong, Stride1D.Dense> resultView = resultsView.SubView(0, computeCount);
|
ArrayView1D<ulong, Stride1D.Dense> resultView = resultsView.SubView(0, computeCount);
|
||||||
kernel(computeCount, divisorData, exponentView, resultView);
|
kernel(computeCount, divisorData, exponentView, resultView);
|
||||||
accelerator.Synchronize();
|
|
||||||
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
resultView.CopyToCPU(ref MemoryMarshal.GetReference(exponentSlice), computeCount);
|
||||||
|
|
||||||
|
bool requiresInitialization = !exponentStepper.HasState;
|
||||||
for (int i = 0; i < computeCount; i++)
|
for (int i = 0; i < computeCount; i++)
|
||||||
{
|
{
|
||||||
int position = positionSpan[i];
|
int position = positionSpan[i];
|
||||||
@@ -505,9 +765,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester : IMersenneNumberDiv
|
|||||||
ulong montgomeryResult = exponentSlice[i];
|
ulong montgomeryResult = exponentSlice[i];
|
||||||
bool isUnity;
|
bool isUnity;
|
||||||
|
|
||||||
if (modeSpan[i] == 0)
|
if (requiresInitialization)
|
||||||
{
|
{
|
||||||
exponentStepper.TryInitializeFromMontgomeryResult(primeValue, montgomeryResult, out isUnity);
|
exponentStepper.TryInitializeFromMontgomeryResult(primeValue, montgomeryResult, out isUnity);
|
||||||
|
requiresInitialization = false;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user