150 lines
4.5 KiB
C#
150 lines
4.5 KiB
C#
using BenchmarkDotNet.Attributes;
|
||
using BenchmarkDotNet.Jobs;
|
||
|
||
namespace EvenPerfectBitScanner.Benchmarks;
|
||
|
||
[MemoryDiagnoser]
|
||
[SimpleJob(RuntimeMoniker.Net80, launchCount: 1, warmupCount: 1, iterationCount: 10)]
|
||
public class MontgomeryMultiplyBenchmarks
|
||
{
|
||
private const int MaxBatchSize = 1024;
|
||
|
||
private readonly ulong[] _operandsA = new ulong[MaxBatchSize];
|
||
private readonly ulong[] _operandsB = new ulong[MaxBatchSize];
|
||
private readonly ulong[] _moduli = new ulong[MaxBatchSize];
|
||
private readonly ulong[] _nPrimes = new ulong[MaxBatchSize];
|
||
|
||
private readonly Random _random = new(1979);
|
||
|
||
[Params(64, 256, 1024)]
|
||
public int BatchSize { get; set; }
|
||
|
||
[GlobalSetup]
|
||
public void Setup()
|
||
{
|
||
for (int i = 0; i < MaxBatchSize; i++)
|
||
{
|
||
ulong modulus = NextOddModulus();
|
||
_moduli[i] = modulus;
|
||
_nPrimes[i] = ComputeMontgomeryNPrime(modulus);
|
||
_operandsA[i] = (ulong)_random.NextInt64(0, (long)modulus);
|
||
_operandsB[i] = (ulong)_random.NextInt64(0, (long)modulus);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// Baseline Montgomery multiply used in earlier revisions; measured 247 ns (64 batch), 974 ns (256), and 3.94 μs (1024).
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Observed means: BatchSize 64 → 247.0 ns (1.00×), 256 → 974.2 ns, 1024 → 3,940.6 ns.
|
||
/// </remarks>
|
||
[Benchmark(Baseline = true)]
|
||
public ulong OriginalImplementation()
|
||
{
|
||
ulong checksum = 0UL;
|
||
for (int i = 0; i < BatchSize; i++)
|
||
{
|
||
checksum ^= OriginalMontgomeryMultiply(_operandsA[i], _operandsB[i], _moduli[i], _nPrimes[i]);
|
||
}
|
||
|
||
return checksum;
|
||
}
|
||
|
||
/// <summary>
|
||
/// Optimized Montgomery multiply; consistently 3–5% faster with 234.9 ns (64), 942.6 ns (256), and 3.75 μs (1024).
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Observed means: BatchSize 64 → 234.9 ns (0.95×), 256 → 942.6 ns, 1024 → 3,751.6 ns.
|
||
/// </remarks>
|
||
[Benchmark]
|
||
public ulong OptimizedImplementation()
|
||
{
|
||
ulong checksum = 0UL;
|
||
for (int i = 0; i < BatchSize; i++)
|
||
{
|
||
checksum ^= OptimizedMontgomeryMultiply(_operandsA[i], _operandsB[i], _moduli[i], _nPrimes[i]);
|
||
}
|
||
|
||
return checksum;
|
||
}
|
||
|
||
private ulong NextOddModulus()
|
||
{
|
||
ulong modulus = (ulong)_random.NextInt64(3, long.MaxValue);
|
||
if ((modulus & 1UL) == 0UL)
|
||
{
|
||
modulus++;
|
||
}
|
||
|
||
return modulus;
|
||
}
|
||
|
||
private static ulong OriginalMontgomeryMultiply(ulong a, ulong b, ulong modulus, ulong nPrime)
|
||
{
|
||
ulong tLow = unchecked(a * b);
|
||
ulong tHigh = MultiplyHigh(a, b);
|
||
ulong m = unchecked(tLow * nPrime);
|
||
ulong mnLow = unchecked(m * modulus);
|
||
ulong mnHigh = MultiplyHigh(m, modulus);
|
||
|
||
ulong sumLow = unchecked(tLow + mnLow);
|
||
ulong carry = sumLow < tLow ? 1UL : 0UL;
|
||
ulong sumHigh = unchecked(tHigh + mnHigh + carry);
|
||
|
||
ulong result = sumHigh;
|
||
if (result >= modulus)
|
||
{
|
||
result -= modulus;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private static ulong OptimizedMontgomeryMultiply(ulong a, ulong b, ulong modulus, ulong nPrime)
|
||
{
|
||
ulong tLow = unchecked(a * b);
|
||
ulong m = unchecked(tLow * nPrime);
|
||
ulong sumLow = unchecked(tLow + m * modulus);
|
||
ulong carry = sumLow < tLow ? 1UL : 0UL;
|
||
ulong sumHigh = unchecked(MultiplyHigh(a, b) + MultiplyHigh(m, modulus) + carry);
|
||
|
||
ulong result = sumHigh;
|
||
if (result >= modulus)
|
||
{
|
||
result -= modulus;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private static ulong ComputeMontgomeryNPrime(ulong modulus)
|
||
{
|
||
ulong inv = modulus;
|
||
inv *= unchecked(2UL - modulus * inv);
|
||
inv *= unchecked(2UL - modulus * inv);
|
||
inv *= unchecked(2UL - modulus * inv);
|
||
inv *= unchecked(2UL - modulus * inv);
|
||
inv *= unchecked(2UL - modulus * inv);
|
||
inv *= unchecked(2UL - modulus * inv);
|
||
return unchecked(0UL - inv);
|
||
}
|
||
|
||
private static ulong MultiplyHigh(ulong x, ulong y)
|
||
{
|
||
ulong xLow = (uint)x;
|
||
ulong xHigh = x >> 32;
|
||
ulong yLow = (uint)y;
|
||
ulong yHigh = y >> 32;
|
||
|
||
ulong lowProduct = xLow * yLow;
|
||
ulong mid = xHigh * yLow + (lowProduct >> 32);
|
||
ulong midLow = mid & 0xFFFFFFFFUL;
|
||
ulong midHigh = mid >> 32;
|
||
midLow += xLow * yHigh;
|
||
|
||
ulong resultHigh = xHigh * yHigh + midHigh + (midLow >> 32);
|
||
return resultHigh;
|
||
}
|
||
}
|
||
|