Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions csharp/Platform.Data.Doublets.Benchmarks/DynamicVsGenericBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Numerics;
using BenchmarkDotNet.Attributes;

#pragma warning disable CA1822 // Mark members as static

namespace Platform.Data.Doublets.Benchmarks
{
/// <summary>
/// Performance comparison between different approaches for generic operations.
/// Based on https://stackoverflow.com/a/8122675/710069
/// </summary>
[SimpleJob]
[MemoryDiagnoser]
public class DynamicVsGenericBenchmarks
{
private const int Iterations = 1000;
private readonly ulong[] _values;

public DynamicVsGenericBenchmarks()
{
_values = new ulong[Iterations];
var random = new System.Random(42);
for (int i = 0; i < Iterations; i++)
{
_values[i] = (ulong)random.Next(1, 100);
}
}

[GlobalSetup]
public void Setup()
{
// Warm up expression compilation
ExpressionAdd(1UL, 2UL);
ExpressionEquals(1UL, 2UL);
}

[Benchmark(Description = "INumber Addition")]
public ulong INumberAddition()
{
ulong sum = 0;
for (int i = 0; i < Iterations - 1; i++)
{
sum += INumberAdd(_values[i], _values[i + 1]);
}
return sum;
}

[Benchmark(Description = "Dynamic Addition")]
public ulong DynamicAddition()
{
ulong sum = 0;
for (int i = 0; i < Iterations - 1; i++)
{
sum += DynamicAdd(_values[i], _values[i + 1]);
}
return sum;
}

[Benchmark(Description = "Expression Addition")]
public ulong ExpressionAddition()
{
ulong sum = 0;
for (int i = 0; i < Iterations - 1; i++)
{
sum += ExpressionAdd(_values[i], _values[i + 1]);
}
return sum;
}

[Benchmark(Description = "IEqualityOperators Equality")]
public int IEqualityOperatorsEquality()
{
int equalCount = 0;
for (int i = 0; i < Iterations - 1; i++)
{
if (IEqualityOperatorsEquals(_values[i], _values[i + 1]))
equalCount++;
}
return equalCount;
}

[Benchmark(Description = "EqualityComparer Equality")]
public int EqualityComparerEquality()
{
int equalCount = 0;
for (int i = 0; i < Iterations - 1; i++)
{
if (EqualityComparerEquals(_values[i], _values[i + 1]))
equalCount++;
}
return equalCount;
}

[Benchmark(Description = "Dynamic Equality")]
public int DynamicEquality()
{
int equalCount = 0;
for (int i = 0; i < Iterations - 1; i++)
{
if (DynamicEquals(_values[i], _values[i + 1]))
equalCount++;
}
return equalCount;
}

[Benchmark(Description = "Expression Equality")]
public int ExpressionEquality()
{
int equalCount = 0;
for (int i = 0; i < Iterations - 1; i++)
{
if (ExpressionEquals(_values[i], _values[i + 1]))
equalCount++;
}
return equalCount;
}

// Implementation methods

// INumber approach (NET 7+ - best performance)
private static T INumberAdd<T>(T a, T b) where T : INumber<T>
{
return a + b;
}

// Dynamic approach (slowest)
private static T DynamicAdd<T>(T a, T b)
{
dynamic dynamicA = a;
dynamic dynamicB = b;
dynamic result = dynamicA + dynamicB;
return (T)Convert.ChangeType(result, typeof(T));
}

// Expression-based approach (moderate performance)
private static readonly ConcurrentDictionary<Type, object> _addFunctions =
new ConcurrentDictionary<Type, object>();

private static T ExpressionAdd<T>(T a, T b)
{
var addFunc = (Func<T, T, T>)_addFunctions.GetOrAdd(typeof(T), type =>
{
var paramA = Expression.Parameter(type, "a");
var paramB = Expression.Parameter(type, "b");

Expression left = paramA;
Expression right = paramB;

// For byte and other smaller types, convert to int for arithmetic
if (type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort))
{
left = Expression.Convert(paramA, typeof(int));
right = Expression.Convert(paramB, typeof(int));
}

Expression body = Expression.Add(left, right);

// Convert back if needed
if (type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort))
{
body = Expression.Convert(body, type);
}

return Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
});

return addFunc(a, b);
}

// IEqualityOperators approach (NET 7+ - best performance)
private static bool IEqualityOperatorsEquals<T>(T a, T b) where T : IEqualityOperators<T, T, bool>
{
return a == b;
}

// EqualityComparer approach (good performance)
private static bool EqualityComparerEquals<T>(T a, T b)
{
return EqualityComparer<T>.Default.Equals(a, b);
}

// Dynamic approach (slowest)
private static bool DynamicEquals<T>(T a, T b)
{
dynamic dynamicA = a;
dynamic dynamicB = b;
return (bool)(dynamicA == dynamicB);
}

// Expression-based approach (moderate performance)
private static readonly ConcurrentDictionary<Type, object> _equalityFunctions =
new ConcurrentDictionary<Type, object>();

private static bool ExpressionEquals<T>(T a, T b)
{
var equalityFunc = (Func<T, T, bool>)_equalityFunctions.GetOrAdd(typeof(T), type =>
{
var paramA = Expression.Parameter(type, "a");
var paramB = Expression.Parameter(type, "b");
var body = Expression.Equal(paramA, paramB);
return Expression.Lambda<Func<T, T, bool>>(body, paramA, paramB).Compile();
});

return equalityFunc(a, b);
}
}
}
1 change: 1 addition & 0 deletions csharp/Platform.Data.Doublets.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ static void Main()
{
BenchmarkRunner.Run<CountBenchmarks>();
BenchmarkRunner.Run<LinkStructBenchmarks>();
BenchmarkRunner.Run<DynamicVsGenericBenchmarks>();
// BenchmarkRunner.Run<MemoryBenchmarks>();
}
}
Expand Down
121 changes: 121 additions & 0 deletions csharp/Platform.Data.Doublets.Tests/ComparisonTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
using System;
using System.Numerics;
using System.Linq.Expressions;
using Xunit;
using Platform.Converters;

namespace Platform.Data.Doublets.Tests
{
public static class ComparisonTests
{
[Fact]
public static void GenericAdditionTest()
{
// Test INumber approach (NET 7+) - recommended approach
TestINumberAddition<byte>();
TestINumberAddition<ushort>();
TestINumberAddition<uint>();
TestINumberAddition<ulong>();

// Test dynamic approach - for comparison
TestDynamicAddition<byte>();
TestDynamicAddition<ushort>();
TestDynamicAddition<uint>();
TestDynamicAddition<ulong>();

// Test expression-based approach
TestExpressionAddition<byte>();
TestExpressionAddition<ushort>();
TestExpressionAddition<uint>();
TestExpressionAddition<ulong>();
}

private static void TestINumberAddition<T>() where T : INumber<T>
{
var a = T.One;
var b = T.One;
var result = INumberAdd(a, b);
var expected = T.One + T.One;
Assert.Equal(expected, result);
}

private static void TestDynamicAddition<T>()
{
var a = GetOne<T>();
var b = GetOne<T>();
var result = DynamicAdd(a, b);
var expected = GetTwo<T>();
Assert.Equal(expected, result);
}

private static void TestExpressionAddition<T>()
{
var a = GetOne<T>();
var b = GetOne<T>();
var result = ExpressionAdd(a, b);
var expected = GetTwo<T>();
Assert.Equal(expected, result);
}

// INumber approach (NET 7+ - best performance)
private static T INumberAdd<T>(T a, T b) where T : INumber<T>
{
return a + b;
}

// Dynamic approach (slowest)
private static T DynamicAdd<T>(T a, T b)
{
dynamic dynamicA = a;
dynamic dynamicB = b;
dynamic result = dynamicA + dynamicB;
return (T)Convert.ChangeType(result, typeof(T));
}

// Expression-based approach (moderate performance)
private static readonly System.Collections.Concurrent.ConcurrentDictionary<Type, object> _addFunctions =
new System.Collections.Concurrent.ConcurrentDictionary<Type, object>();

private static T ExpressionAdd<T>(T a, T b)
{
var addFunc = (Func<T, T, T>)_addFunctions.GetOrAdd(typeof(T), type =>
{
var paramA = Expression.Parameter(type, "a");
var paramB = Expression.Parameter(type, "b");

Expression left = paramA;
Expression right = paramB;

// For byte and other smaller types, convert to int for arithmetic
if (type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort))
{
left = Expression.Convert(paramA, typeof(int));
right = Expression.Convert(paramB, typeof(int));
}

Expression body = Expression.Add(left, right);

// Convert back if needed
if (type == typeof(byte) || type == typeof(sbyte) || type == typeof(short) || type == typeof(ushort))
{
body = Expression.Convert(body, type);
}

return Expression.Lambda<Func<T, T, T>>(body, paramA, paramB).Compile();
});

return addFunc(a, b);
}

// Helper methods to get typed values without INumber constraint
private static T GetOne<T>()
{
return (T)Convert.ChangeType(1, typeof(T));
}

private static T GetTwo<T>()
{
return (T)Convert.ChangeType(2, typeof(T));
}
}
}
Loading
Loading