Skip to content

Instantly share code, notes, and snippets.

@MichalBrylka
Last active October 13, 2023 03:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MichalBrylka/19ae10e62d55ce7cbb2cc9ab21e7e879 to your computer and use it in GitHub Desktop.
Save MichalBrylka/19ae10e62d55ce7cbb2cc9ab21e7e879 to your computer and use it in GitHub Desktop.
Generic math performance
using System.Numerics;
namespace Benchmarks;
readonly struct DoubleVector
{
private readonly double[] _values;
public DoubleVector(double[] values) => _values = values;
public double Average()
{
var sum = 0.0;
for (int i = 0; i < _values.Length; i++)
sum += _values[i];
return sum / _values.Length;
}
}
using System.Numerics;
using BenchmarkDotNet.Attributes;
namespace Benchmarks;
[MemoryDiagnoser]
[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByCategory)]
[CategoriesColumn]
public class GenericMathBench
{
[Params(100, 10_000)]
public int Size { get; set; }
private long[] _longs = null!;
private double[] _doubles = null!;
public const uint ITERATIONS = 100;
[GlobalSetup]
public void GlobalSetup()
{
_longs = Enumerable.Range(1, Size).Select(i => (long)i).ToArray();
_doubles = Enumerable.Range(1, Size).Select(i => i * 1.1).ToArray();
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("Double")]
public double DoubleBench()
{
var vector = new DoubleVector(_doubles);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average();
return avg;
}
[Benchmark]
[BenchmarkCategory("Double")]
public double Vector1_Double()
{
var vector = new Vector1<double>(_doubles);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average<double>();
return avg;
}
[Benchmark]
[BenchmarkCategory("Double")]
public double Vector2_Double()
{
var vector = new Vector2<double>(_doubles);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average<double>();
return avg;
}
[Benchmark]
[BenchmarkCategory("Double")]
public double Span_Double()
{
var vector = new SpanVector<double>(_doubles);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average<double>();
return avg;
}
[Benchmark(Baseline = true)]
[BenchmarkCategory("Long")]
public double LongBench()
{
var vector = new LongVector(_longs);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average();
return avg;
}
[Benchmark]
[BenchmarkCategory("Long")]
public double Vector1_Long()
{
var vector = new Vector1<long>(_longs);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average<double>();
return avg;
}
[Benchmark]
[BenchmarkCategory("Long")]
public double Vector2_Long()
{
var vector = new Vector2<long>(_longs);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average<double>();
return avg;
}
[Benchmark]
[BenchmarkCategory("Long")]
public double Span_Long()
{
var vector = new SpanVector<long>(_longs);
var avg = 0.0;
for (uint i = 0; i < ITERATIONS; i++)
avg = vector.Average<double>();
return avg;
}
}
using System.Numerics;
namespace Benchmarks;
readonly struct LongVector
{
private readonly long[] _values;
public LongVector(long[] values) => _values = values;
public double Average()
{
long sum = 0;
for (int i = 0; i < _values.Length; i++)
sum += _values[i];
return (double)sum / _values.Length;
}
}
readonly struct Vector2<TNumber> where TNumber : unmanaged, INumberBase<TNumber>
{
private readonly TNumber[] _values;
public Vector2(TNumber[] values) => _values = values;
public unsafe TResult Average<TResult>() where TResult : INumber<TResult>
{
var sum = TNumber.Zero;
var size = _values.Length;
fixed (TNumber* pData = _values)
{
var p = pData;
for (int i = 0; i < size; i++)
sum += *p++;
}
return TResult.CreateChecked(sum) / TResult.CreateChecked(size);
}
}
readonly ref struct SpanVector<TNumber> where TNumber : INumberBase<TNumber>
{
private readonly ReadOnlySpan<TNumber> _values;
public SpanVector(ReadOnlySpan<TNumber> values) => _values = values;
public TResult Average<TResult>() where TResult : INumber<TResult>
{
var sum = TNumber.Zero;
for (int i = 0; i < _values.Length; i++)
sum += _values[i];
return TResult.CreateChecked(sum) / TResult.CreateChecked(_values.Length);
}
}

Runtime

BenchmarkDotNet=v0.13.2, OS=Windows 10 (10.0.19044.2251/21H2/November2021Update) Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores .NET SDK=7.0.100 [Host] : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2 DefaultJob : .NET 7.0.0 (7.0.22.51805), X64 RyuJIT AVX2

Method Categories Size Mean Error StdDev Ratio Allocated Alloc Ratio
DoubleBench Double 100 8.387 us 0.0545 us 0.0455 us 1.00 - NA
Vector1_Double Double 100 8.462 us 0.0117 us 0.0104 us 1.01 - NA
Vector2_Double Double 100 7.428 us 0.0167 us 0.0148 us 0.89 - NA
Span_Double Double 100 7.687 us 0.0216 us 0.0191 us 0.92 - NA
DoubleBench Double 10000 935.063 us 1.4331 us 1.2704 us 1.00 - NA
Vector1_Double Double 10000 935.315 us 2.0107 us 1.6790 us 1.00 - NA
Vector2_Double Double 10000 935.157 us 2.0961 us 1.8581 us 1.00 - NA
Span_Double Double 10000 934.439 us 2.0086 us 1.7805 us 1.00 - NA
LongBench Long 100 4.712 us 0.0371 us 0.0347 us 1.00 - NA
Vector1_Long Long 100 5.616 us 0.0367 us 0.0306 us 1.19 - NA
Vector2_Long Long 100 5.567 us 0.0105 us 0.0093 us 1.18 - NA
Span_Long Long 100 4.583 us 0.0230 us 0.0192 us 0.97 - NA
LongBench Long 10000 430.674 us 2.0188 us 1.6858 us 1.00 - NA
Vector1_Long Long 10000 401.085 us 2.8027 us 2.4845 us 0.93 - NA
Vector2_Long Long 10000 443.050 us 2.1181 us 1.8776 us 1.03 - NA
Span_Long Long 10000 393.092 us 2.3554 us 1.9669 us 0.91 - NA
using System.Numerics;
namespace Benchmarks;
readonly struct Vector1<TNumber> where TNumber : INumberBase<TNumber>
{
private readonly TNumber[] _values;
public Vector1(TNumber[] values) => _values = values;
public TResult Average<TResult>() where TResult : INumber<TResult>
{
var sum = TNumber.Zero;
for (int i = 0; i < _values.Length; i++)
sum += _values[i];
return TResult.CreateChecked(sum) / TResult.CreateChecked(_values.Length);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment