From 96b31ced094d2c7eb36a5022d2a88eef1e7bf65b Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 10:39:02 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #68 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/linksplatform/Data.Doublets/issues/68 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..3b0d3c3e6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/linksplatform/Data.Doublets/issues/68 +Your prepared branch: issue-68-4e596e00 +Your prepared working directory: /tmp/gh-issue-solver-1757835486388 + +Proceed. \ No newline at end of file From bc1dbb7d2d6842911d7d666d3cee4eaa767076a8 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 10:50:08 +0300 Subject: [PATCH 2/3] Add performance comparison tests and benchmarks for dynamic vs generic operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements performance comparison between different approaches for generic operations based on https://stackoverflow.com/a/8122675/710069: - INumber approach (NET 7+ - best performance) - Expression-based approach (moderate performance) - Dynamic approach (slowest performance) - EqualityComparer approach (good performance) Added files: - ComparisonTests.cs: Unit tests comparing different generic addition approaches - EqualityTests.cs: Unit tests comparing different generic equality approaches - DynamicVsGenericBenchmarks.cs: Performance benchmarks using BenchmarkDotNet Updated Program.cs to include the new benchmark suite. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../DynamicVsGenericBenchmarks.cs | 211 ++++++++++++++++++ .../Program.cs | 1 + .../ComparisonTests.cs | 121 ++++++++++ .../EqualityTests.cs | 127 +++++++++++ 4 files changed, 460 insertions(+) create mode 100644 csharp/Platform.Data.Doublets.Benchmarks/DynamicVsGenericBenchmarks.cs create mode 100644 csharp/Platform.Data.Doublets.Tests/ComparisonTests.cs create mode 100644 csharp/Platform.Data.Doublets.Tests/EqualityTests.cs diff --git a/csharp/Platform.Data.Doublets.Benchmarks/DynamicVsGenericBenchmarks.cs b/csharp/Platform.Data.Doublets.Benchmarks/DynamicVsGenericBenchmarks.cs new file mode 100644 index 000000000..333c597d1 --- /dev/null +++ b/csharp/Platform.Data.Doublets.Benchmarks/DynamicVsGenericBenchmarks.cs @@ -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 +{ + /// + /// Performance comparison between different approaches for generic operations. + /// Based on https://stackoverflow.com/a/8122675/710069 + /// + [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 a, T b) where T : INumber + { + return a + b; + } + + // Dynamic approach (slowest) + private static T DynamicAdd(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 _addFunctions = + new ConcurrentDictionary(); + + private static T ExpressionAdd(T a, T b) + { + var addFunc = (Func)_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>(body, paramA, paramB).Compile(); + }); + + return addFunc(a, b); + } + + // IEqualityOperators approach (NET 7+ - best performance) + private static bool IEqualityOperatorsEquals(T a, T b) where T : IEqualityOperators + { + return a == b; + } + + // EqualityComparer approach (good performance) + private static bool EqualityComparerEquals(T a, T b) + { + return EqualityComparer.Default.Equals(a, b); + } + + // Dynamic approach (slowest) + private static bool DynamicEquals(T a, T b) + { + dynamic dynamicA = a; + dynamic dynamicB = b; + return (bool)(dynamicA == dynamicB); + } + + // Expression-based approach (moderate performance) + private static readonly ConcurrentDictionary _equalityFunctions = + new ConcurrentDictionary(); + + private static bool ExpressionEquals(T a, T b) + { + var equalityFunc = (Func)_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>(body, paramA, paramB).Compile(); + }); + + return equalityFunc(a, b); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets.Benchmarks/Program.cs b/csharp/Platform.Data.Doublets.Benchmarks/Program.cs index f987151c6..21397aa67 100644 --- a/csharp/Platform.Data.Doublets.Benchmarks/Program.cs +++ b/csharp/Platform.Data.Doublets.Benchmarks/Program.cs @@ -8,6 +8,7 @@ static void Main() { BenchmarkRunner.Run(); BenchmarkRunner.Run(); + BenchmarkRunner.Run(); // BenchmarkRunner.Run(); } } diff --git a/csharp/Platform.Data.Doublets.Tests/ComparisonTests.cs b/csharp/Platform.Data.Doublets.Tests/ComparisonTests.cs new file mode 100644 index 000000000..b0bca6f06 --- /dev/null +++ b/csharp/Platform.Data.Doublets.Tests/ComparisonTests.cs @@ -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(); + TestINumberAddition(); + TestINumberAddition(); + TestINumberAddition(); + + // Test dynamic approach - for comparison + TestDynamicAddition(); + TestDynamicAddition(); + TestDynamicAddition(); + TestDynamicAddition(); + + // Test expression-based approach + TestExpressionAddition(); + TestExpressionAddition(); + TestExpressionAddition(); + TestExpressionAddition(); + } + + private static void TestINumberAddition() where T : INumber + { + 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() + { + var a = GetOne(); + var b = GetOne(); + var result = DynamicAdd(a, b); + var expected = GetTwo(); + Assert.Equal(expected, result); + } + + private static void TestExpressionAddition() + { + var a = GetOne(); + var b = GetOne(); + var result = ExpressionAdd(a, b); + var expected = GetTwo(); + Assert.Equal(expected, result); + } + + // INumber approach (NET 7+ - best performance) + private static T INumberAdd(T a, T b) where T : INumber + { + return a + b; + } + + // Dynamic approach (slowest) + private static T DynamicAdd(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 _addFunctions = + new System.Collections.Concurrent.ConcurrentDictionary(); + + private static T ExpressionAdd(T a, T b) + { + var addFunc = (Func)_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>(body, paramA, paramB).Compile(); + }); + + return addFunc(a, b); + } + + // Helper methods to get typed values without INumber constraint + private static T GetOne() + { + return (T)Convert.ChangeType(1, typeof(T)); + } + + private static T GetTwo() + { + return (T)Convert.ChangeType(2, typeof(T)); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Data.Doublets.Tests/EqualityTests.cs b/csharp/Platform.Data.Doublets.Tests/EqualityTests.cs new file mode 100644 index 000000000..1238f662c --- /dev/null +++ b/csharp/Platform.Data.Doublets.Tests/EqualityTests.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Linq.Expressions; +using Xunit; + +namespace Platform.Data.Doublets.Tests +{ + public static class EqualityTests + { + [Fact] + public static void GenericEqualityTest() + { + // Test IEqualityOperators approach (NET 7+) - recommended approach + TestIEqualityOperatorsComparison(); + TestIEqualityOperatorsComparison(); + TestIEqualityOperatorsComparison(); + TestIEqualityOperatorsComparison(); + + // Test EqualityComparer approach + TestEqualityComparerComparison(); + TestEqualityComparerComparison(); + TestEqualityComparerComparison(); + TestEqualityComparerComparison(); + + // Test dynamic approach - for comparison + TestDynamicComparison(); + TestDynamicComparison(); + TestDynamicComparison(); + TestDynamicComparison(); + + // Test expression-based approach + TestExpressionComparison(); + TestExpressionComparison(); + TestExpressionComparison(); + TestExpressionComparison(); + } + + private static void TestIEqualityOperatorsComparison() where T : IEqualityOperators + { + var a = GetOne(); + var b = GetOne(); + var c = GetTwo(); + + Assert.True(IEqualityOperatorsEquals(a, b)); + Assert.False(IEqualityOperatorsEquals(a, c)); + } + + private static void TestEqualityComparerComparison() + { + var a = GetOne(); + var b = GetOne(); + var c = GetTwo(); + + Assert.True(EqualityComparerEquals(a, b)); + Assert.False(EqualityComparerEquals(a, c)); + } + + private static void TestDynamicComparison() + { + var a = GetOne(); + var b = GetOne(); + var c = GetTwo(); + + Assert.True(DynamicEquals(a, b)); + Assert.False(DynamicEquals(a, c)); + } + + private static void TestExpressionComparison() + { + var a = GetOne(); + var b = GetOne(); + var c = GetTwo(); + + Assert.True(ExpressionEquals(a, b)); + Assert.False(ExpressionEquals(a, c)); + } + + // IEqualityOperators approach (NET 7+ - best performance) + private static bool IEqualityOperatorsEquals(T a, T b) where T : IEqualityOperators + { + return a == b; + } + + // EqualityComparer approach (good performance) + private static bool EqualityComparerEquals(T a, T b) + { + return EqualityComparer.Default.Equals(a, b); + } + + // Dynamic approach (slowest) + private static bool DynamicEquals(T a, T b) + { + dynamic dynamicA = a; + dynamic dynamicB = b; + return (bool)(dynamicA == dynamicB); + } + + // Expression-based approach (moderate performance) + private static readonly System.Collections.Concurrent.ConcurrentDictionary _equalityFunctions = + new System.Collections.Concurrent.ConcurrentDictionary(); + + private static bool ExpressionEquals(T a, T b) + { + var equalityFunc = (Func)_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>(body, paramA, paramB).Compile(); + }); + + return equalityFunc(a, b); + } + + // Helper methods to get typed values + private static T GetOne() + { + return (T)Convert.ChangeType(1, typeof(T)); + } + + private static T GetTwo() + { + return (T)Convert.ChangeType(2, typeof(T)); + } + } +} \ No newline at end of file From 4395f648ef684973fe849fcdd1bef1cc9f35ea60 Mon Sep 17 00:00:00 2001 From: konard Date: Sun, 14 Sep 2025 10:51:21 +0300 Subject: [PATCH 3/3] Remove CLAUDE.md - Claude command completed --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 3b0d3c3e6..000000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/linksplatform/Data.Doublets/issues/68 -Your prepared branch: issue-68-4e596e00 -Your prepared working directory: /tmp/gh-issue-solver-1757835486388 - -Proceed. \ No newline at end of file