Skip to content

Commit

Permalink
add benchmark results
Browse files Browse the repository at this point in the history
  • Loading branch information
lofcz committed Jan 9, 2025
1 parent a544676 commit 75a997a
Show file tree
Hide file tree
Showing 8 changed files with 217 additions and 18 deletions.
85 changes: 85 additions & 0 deletions FastCloner.Benchmark/BenchArray2d.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using BenchmarkDotNet.Attributes;
using Force.DeepCloner;

namespace FastCloner.Benchmark;

[RankColumn]
[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)]
[MemoryDiagnoser]
public class Bench2DArray
{
private int[,] testData;
private const int SIZE = 1000;

[GlobalSetup]
public void Setup()
{
testData = new int[SIZE, SIZE];

for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < SIZE; j++)
{
testData[i, j] = i * j;
}
}
}

[Benchmark(Baseline = true)]
public object? FastCloner()
{
return global::FastCloner.FastCloner.DeepClone(testData);
}

[Benchmark]
public object? DeepCopier()
{
return global::DeepCopier.Copier.Copy(testData);
}

[Benchmark]
public object? DeepCopy()
{
return global::DeepCopy.DeepCopier.Copy(testData);
}

[Benchmark]
public object DeepCopyExpression()
{
return global::DeepCopy.ObjectCloner.Clone(testData);
}

[Benchmark]
public object? FastDeepCloner()
{
return global::FastDeepCloner.DeepCloner.Clone(testData);
}

[Benchmark]
public object? DeepCloner()
{
return testData.DeepClone();
}

[Benchmark]
public object? ArrayCopy()
{
int[,] clone = new int[SIZE, SIZE];
Array.Copy(testData, clone, testData.Length);
return clone;
}

[Benchmark]
public object? ManualCopy()
{
int[,] clone = new int[SIZE, SIZE];
for (int i = 0; i < SIZE; i++)
{
for (int j = 0; j < SIZE; j++)
{
clone[i, j] = testData[i, j];
}
}
return clone;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@ namespace FastCloner.Benchmark;
[RankColumn]
[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)]
[MemoryDiagnoser]
public class DictionaryBenchmark
public class BenchDictionary
{
private Dictionary<ComplexKey, string> testData;
private Dictionary<SimpleKey, string> testData;

[GlobalSetup]
public void Setup()
{
testData = new Dictionary<ComplexKey, string>();
testData = new Dictionary<SimpleKey, string>();

for (int i = 0; i < 1000; i++)
{
ComplexKey key = new ComplexKey { Id = i, Name = $"Key{i}" };
SimpleKey key = new SimpleKey { Id = i, Name = $"Key{i}" };
testData.Add(key, $"Value{i}");
}
}
Expand Down Expand Up @@ -59,19 +59,8 @@ public object DeepCopyExpression()
}
}

public class ComplexKey
public class SimpleKey
{
public int Id { get; set; }
public string Name { get; set; }

public override bool Equals(object obj)
{
if (obj is not ComplexKey other) return false;
return Id == other.Id && Name == other.Name;
}

public override int GetHashCode()
{
return HashCode.Combine(Id, Name);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace FastCloner.Benchmark;
[RankColumn]
[Orderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest)]
[MemoryDiagnoser]
public class Minimal
public class BenchMinimal
{
private TestObject testData;

Expand Down
70 changes: 70 additions & 0 deletions FastCloner.Benchmark/FeatureDictionary.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using Force.DeepCloner;

namespace FastCloner.Benchmark;

using System;
using System.Collections.Generic;

public class FeatureDictionary
{
public void ValidateHashCodesAfterCloning()
{
// Arrange
Dictionary<SimpleKey, string> originalDict = new Dictionary<SimpleKey, string>();
SimpleKey key1 = new SimpleKey { Id = 1, Name = "Test1" };
originalDict.Add(key1, "Value1");

// Act - test všech implementací
TestCloner("FastCloner", () => TryClone(() => global::FastCloner.FastCloner.DeepClone(originalDict)));
TestCloner("DeepCopier", () => TryClone(() => global::DeepCopier.Copier.Copy(originalDict)));
TestCloner("DeepCopy", () => TryClone(() => global::DeepCopy.DeepCopier.Copy(originalDict)));
TestCloner("DeepCopyExpression", () => TryClone(() => global::DeepCopy.ObjectCloner.Clone(originalDict)));
TestCloner("FastDeepCloner", () => TryClone(() => global::FastDeepCloner.DeepCloner.Clone(originalDict)));
TestCloner("DeepCloner", () => TryClone(() => originalDict.DeepClone()));
}

private static void TestCloner(string clonerName, Func<(bool success, Dictionary<SimpleKey, string> clonedDict)> cloneFunction)
{
try
{
// Act
(bool success, Dictionary<SimpleKey, string> clonedDict) = cloneFunction();

if (!success)
{
Console.WriteLine($"{clonerName}: ❌");
return;
}

// Arrange
KeyValuePair<SimpleKey, string> searchKey = clonedDict.First();

// Assert
if (clonedDict.TryGetValue(searchKey.Key, out string? value) && value == "Value1")
{
Console.WriteLine($"{clonerName}: ✅");
}
else
{
Console.WriteLine($"{clonerName}: ❌");
}
}
catch (Exception ex)
{
Console.WriteLine($"{clonerName}: ❌ (Exception: {ex.Message})");
}
}

private (bool success, Dictionary<SimpleKey, string>? clonedDict) TryClone(Func<Dictionary<SimpleKey, string>> cloneFunction)
{
try
{
Dictionary<SimpleKey, string> clonedDict = cloneFunction();
return (true, clonedDict);
}
catch
{
return (false, null);
}
}
}
9 changes: 9 additions & 0 deletions FastCloner.Benchmark/FeatureValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace FastCloner.Benchmark;

public class FeatureValidator
{
public static void ValidateDictionary()
{
new FeatureDictionary().ValidateHashCodesAfterCloning();
}
}
2 changes: 1 addition & 1 deletion FastCloner.Benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ static void Main(string[] args)
ManualConfig config = DefaultConfig.Instance
.WithOptions(ConfigOptions.DisableOptimizationsValidator);

Summary summary = BenchmarkRunner.Run<Minimal>(config);
Summary summary = BenchmarkRunner.Run<Bench2DArray>(config);
Console.WriteLine(summary);
}
}
42 changes: 42 additions & 0 deletions FastCloner.Benchmark/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Benchmark results

FastCloner aims to _work correctly_ in the first place. It's currently reflection based, with opt-in source generator for increased performance. The benchmarking is made harder by the fact that many competing libraries behave incorrectly for scenarios that would be interested to bechmark. For example, only a few libraries deep clone dictionaries correctly. Broadly speaking, FastCloner in reflection mode is _fast_ in the reflection category, lags behind IL generation (when it works) and source generators.

We alocate some extra memory, but this is mostly one-time price for various lookup tables.

### Simple Dictionary:

| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Gen1 | Allocated | Alloc Ratio |
|----------------------|---------:|--------:|--------:|------:|--------:|-----:|--------:|--------:|----------:|------------:|
| DeepCloner** | 111.8 us | 1.55 us | 1.45 us | 0.58 | 0.03 | 1 | 26.7334 | 8.7891 | 164.51 KB | 0.46 |
| FastDeepCloner | 117.4 us | 1.36 us | 1.20 us | 0.61 | 0.03 | 2 | 16.3574 | 4.0283 | 100.79 KB | 0.28 |
| ⭐ FastCloner | 191.9 us | 3.81 us | 9.13 us | 1.00 | 0.07 | 3 | 58.5938 | 1.9531 | 359.94 KB | 1.00 |
| DeepCopy** | 211.9 us | 3.53 us | 3.30 us | 1.11 | 0.05 | 4 | 50.2930 | 24.9023 | 310.47 KB | 0.86 |
| DeepCopyExpression** | 241.5 us | 4.69 us | 7.02 us | 1.26 | 0.07 | 5 | 38.3301 | 9.5215 | 235.23 KB | 0.65 |
| DeepCopier (crashes) | NA | NA | NA | ? | ? | ? | NA | NA | NA | ? |

** clones items incorrectly, unless hash code is overriden.

### Minimal:

| Method | Mean | Error | StdDev | Ratio | RatioSD | Rank | Gen0 | Allocated | Alloc Ratio |
|--------------------|------------:|----------:|----------:|------:|--------:|-----:|-------:|----------:|------------:|
| DeepCopier | 23.48 ns | 0.525 ns | 0.998 ns | 0.15 | 0.01 | 1 | 0.0115 | 72 B | 0.20 |
| DeepCopy | 41.55 ns | 0.751 ns | 0.702 ns | 0.27 | 0.01 | 2 | 0.0114 | 72 B | 0.20 |
| DeepCloner | 134.54 ns | 2.654 ns | 3.057 ns | 0.87 | 0.03 | 3 | 0.0370 | 232 B | 0.64 |
| ⭐ FastCloner | 155.21 ns | 3.007 ns | 4.215 ns | 1.00 | 0.04 | 4 | 0.0572 | 360 B | 1.00 |
| DeepCopyExpression | 169.37 ns | 3.269 ns | 3.498 ns | 1.09 | 0.04 | 5 | 0.0393 | 248 B | 0.69 |
| FastDeepCloner | 2,210.93 ns | 43.556 ns | 58.146 ns | 14.26 | 0.53 | 6 | 0.2060 | 1296 B | 3.60 |

### Array 2D Big

| Method | Mean | Error | StdDev | Median | Ratio | RatioSD | Rank | Gen0 | Gen1 | Gen2 | Allocated | Alloc Ratio |
|----------------------|-----------:|---------:|----------:|-----------:|------:|--------:|-----:|---------:|---------:|---------:|----------:|------------:|
| DeepCopyExpression | 657.1 us | 15.29 us | 44.11 us | 652.0 us | 0.78 | 0.06 | 1 | 395.5078 | 394.5313 | 394.5313 | 3.81 MB | 1.00 |
| FastDeepCloner | 663.9 us | 19.89 us | 57.38 us | 646.3 us | 0.79 | 0.07 | 1 | 399.4141 | 398.4375 | 398.4375 | 3.82 MB | 1.00 |
| ArrayCopy | 784.2 us | 14.09 us | 18.81 us | 783.6 us | 0.93 | 0.04 | 2 | 281.2500 | 281.2500 | 281.2500 | 3.81 MB | 1.00 |
| DeepCloner | 819.2 us | 13.67 us | 16.27 us | 820.5 us | 0.97 | 0.04 | 2 | 296.8750 | 296.8750 | 296.8750 | 3.81 MB | 1.00 |
| DeepCopy | 832.6 us | 16.88 us | 48.15 us | 822.2 us | 0.99 | 0.07 | 2 | 273.4375 | 273.4375 | 273.4375 | 3.81 MB | 1.00 |
| ⭐ FastCloner | 842.9 us | 16.79 us | 35.05 us | 833.3 us | 1.00 | 0.06 | 2 | 328.1250 | 328.1250 | 328.1250 | 3.82 MB | 1.00 |
| ManualCopy | 2,574.0 us | 50.50 us | 115.02 us | 2,555.7 us | 3.06 | 0.18 | 3 | 390.6250 | 390.6250 | 390.6250 | 3.81 MB | 1.00 |
| DeepCopier (crashes) | NA | NA | NA | NA | ? | ? | ? | NA | NA | NA | NA | ? |
4 changes: 4 additions & 0 deletions FastCloner.SourceGenerator/FastClonerIncrementalGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ public class FastClonerIncrementalGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
#if !ENABLE_SOURCEGEN
return;
#endif

IncrementalValuesProvider<(TypeDeclarationSyntax Syntax, SemanticModel SemanticModel)> typesToProcess =

Check warning on line 19 in FastCloner.SourceGenerator/FastClonerIncrementalGenerator.cs

View workflow job for this annotation

GitHub Actions / build-and-release

Unreachable code detected
context.SyntaxProvider
.CreateSyntaxProvider(
Expand Down

0 comments on commit 75a997a

Please sign in to comment.