diff --git a/EvenPerfectBitScanner.Benchmarks/MontgomeryMultiplyBenchmarks.cs b/EvenPerfectBitScanner.Benchmarks/MontgomeryMultiplyBenchmarks.cs new file mode 100644 index 0000000..b026855 --- /dev/null +++ b/EvenPerfectBitScanner.Benchmarks/MontgomeryMultiplyBenchmarks.cs @@ -0,0 +1,137 @@ +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); + } + } + + [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; + } + + [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; + } +} + diff --git a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs index a2da814..347a13d 100644 --- a/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs +++ b/PerfectNumbers.Core/Gpu/MersenneNumberDivisorByDivisorGpuTester.cs @@ -377,14 +377,10 @@ public sealed class MersenneNumberDivisorByDivisorGpuTester private static ulong MontgomeryMultiply(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 sumLow = unchecked(tLow + m * modulus); ulong carry = sumLow < tLow ? 1UL : 0UL; - ulong sumHigh = unchecked(tHigh + mnHigh + carry); + ulong sumHigh = unchecked(MultiplyHigh(a, b) + MultiplyHigh(m, modulus) + carry); ulong result = sumHigh; if (result >= modulus)