Skip to content

Commit

Permalink
Improving the Serializer benchmarks (#343)
Browse files Browse the repository at this point in the history
* Improving the Serializer benchmark by adding all the serializers and cleaning up the code and config for it. Also added tests for serializing arrays to check for memory pressure on buffers.
* Reverting NuGet updates
* Fixing Job config
* Minor usings cleanup

---------

Co-authored-by: Jody Donetti <[email protected]>
Co-authored-by: Stefán Jökull Sigurðarson <[email protected]>
  • Loading branch information
stebet and jodydonetti authored Dec 13, 2024
1 parent 66869e6 commit b955c9a
Show file tree
Hide file tree
Showing 9 changed files with 126 additions and 89 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,144 +5,132 @@
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Columns;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Toolchains.InProcess.Emit;
using MemoryPack;
using MessagePack;
using Microsoft.IO;
using ZiggyCreatures.Caching.Fusion.Serialization;
using ZiggyCreatures.Caching.Fusion.Serialization.CysharpMemoryPack;
using ZiggyCreatures.Caching.Fusion.Serialization.NeueccMessagePack;
using ZiggyCreatures.Caching.Fusion.Serialization.NewtonsoftJson;
using ZiggyCreatures.Caching.Fusion.Serialization.ProtoBufNet;
using ZiggyCreatures.Caching.Fusion.Serialization.ServiceStackJson;
using ZiggyCreatures.Caching.Fusion.Serialization.SystemTextJson;

namespace ZiggyCreatures.Caching.Fusion.Benchmarks;

public abstract class AbstractSerializersBenchmark
[DataContract]
[MessagePackObject]
[MemoryPackable]
public partial class SampleModel
{
protected static Random _MyRandom = new Random(2110);

[DataContract]
protected class SampleModel
private static readonly Random _MyRandom = new(2110);

[DataMember(Order = 1)]
[Key(0)]
public string? Name { get; set; }
[DataMember(Order = 2)]
[Key(1)]
public int Age { get; set; }
[DataMember(Order = 3)]
[Key(2)]
public DateTime Date { get; set; }
[DataMember(Order = 4)]
[Key(3)]
public List<int> FavoriteNumbers { get; set; } = [];

public static SampleModel GenerateRandom()
{
[DataMember(Order = 1)]
public string? Name { get; set; }
[DataMember(Order = 2)]
public int Age { get; set; }
[DataMember(Order = 3)]
public DateTime Date { get; set; }
[DataMember(Order = 4)]
public List<int> FavoriteNumbers { get; set; } = [];

public static SampleModel GenerateRandom()
var model = new SampleModel
{
Name = Guid.NewGuid().ToString("N"),
Age = _MyRandom.Next(1, 100),
Date = DateTime.UtcNow,
};
for (int i = 0; i < 10; i++)
{
var model = new SampleModel
{
Name = Guid.NewGuid().ToString("N"),
Age = _MyRandom.Next(1, 100),
Date = DateTime.UtcNow,
};
for (int i = 0; i < 10; i++)
{
model.FavoriteNumbers.Add(_MyRandom.Next(1, 1000));
}
return model;
model.FavoriteNumbers.Add(_MyRandom.Next(1, 1000));
}
return model;
}
}

protected class Config : ManualConfig
[Config(typeof(Config))]
public class SerializersBenchmark
{
public class Config : ManualConfig
{
public Config()
{
AddColumn(StatisticColumn.P95);
AddDiagnoser(MemoryDiagnoser.Default);
AddLogicalGroupRules(BenchmarkLogicalGroupRule.ByMethod);
AddJob(Job.Default.WithToolchain(InProcessEmitToolchain.Instance));
WithOrderer(new DefaultOrderer(summaryOrderPolicy: SummaryOrderPolicy.FastestToSlowest));
WithSummaryStyle(BenchmarkDotNet.Reports.SummaryStyle.Default.WithMaxParameterColumnWidth(50));
}
}

protected IFusionCacheSerializer _Normal = null!;
protected IFusionCacheSerializer _Recyclable = null!;
[ParamsSource(nameof(GetSerializers))]
public IFusionCacheSerializer Serializer = null!;
protected List<SampleModel> _Models = [];
protected byte[] _Blob = null!;

[Params(1, 100, 1_000)]
public int Size;

[GlobalSetup]
public void Setup()
{
for (int i = 0; i < Size; i++)
for (int i = 0; i < 1000; i++)
{
_Models.Add(SampleModel.GenerateRandom());
}
_Blob = _Normal.Serialize(_Models);
}

[Benchmark(Baseline = true)]
public void Serialize_Normal()
{
_Normal.Serialize(_Models);
_Blob = Serializer.Serialize(_Models);
}

[Benchmark]
public void Serialize_Recyclable()
public void Serialize()
{
_Recyclable.Serialize(_Models);
Serializer.Serialize(_Models);
}

[Benchmark]
public void Deserialize_Normal()
public void Deserialize()
{
_Normal.Deserialize<List<SampleModel>>(_Blob);
Serializer.Deserialize<List<SampleModel>>(_Blob);
}

[Benchmark]
public void Deserialize_Recyclable()
public async Task SerializeAsync()
{
_Recyclable.Deserialize<List<SampleModel>>(_Blob);
await Serializer.SerializeAsync(_Models).ConfigureAwait(false);
}

[Benchmark]
public async Task SerializeAsync_Normal()
public async Task DeserializeAsync()
{
await _Normal.SerializeAsync(_Models).ConfigureAwait(false);
await Serializer.DeserializeAsync<List<SampleModel>>(_Blob).ConfigureAwait(false);
}

[Benchmark]
public async Task SerializeAsync_Recyclable()
{
await _Recyclable.SerializeAsync(_Models).ConfigureAwait(false);
}

[Benchmark]
public async Task DeserializeAsync_Normal()
public static IEnumerable<IFusionCacheSerializer> GetSerializers()
{
await _Normal.DeserializeAsync<List<SampleModel>>(_Blob).ConfigureAwait(false);
}

[Benchmark]
public async Task DeserializeAsync_Recyclable()
{
await _Recyclable.DeserializeAsync<List<SampleModel>>(_Blob).ConfigureAwait(false);
}
}

[MemoryDiagnoser]
[Config(typeof(Config))]
public class SystemTextJsonSerializerBenchmark
: AbstractSerializersBenchmark
{
public SystemTextJsonSerializerBenchmark()
{
_Normal = new FusionCacheSystemTextJsonSerializer();
_Recyclable = new FusionCacheSystemTextJsonSerializer(new FusionCacheSystemTextJsonSerializer.Options
yield return new FusionCacheCysharpMemoryPackSerializer();
yield return new FusionCacheNeueccMessagePackSerializer();
yield return new FusionCacheNewtonsoftJsonSerializer();
yield return new FusionCacheProtoBufNetSerializer();
yield return new FusionCacheProtoBufNetSerializer(new FusionCacheProtoBufNetSerializer.Options
{
StreamManager = new RecyclableMemoryStreamManager()
});
}
}

[MemoryDiagnoser]
[Config(typeof(Config))]
public class ProtobufSerializerBenchmark
: AbstractSerializersBenchmark
{
public ProtobufSerializerBenchmark()
{
_Normal = new FusionCacheProtoBufNetSerializer();
_Recyclable = new FusionCacheProtoBufNetSerializer(new FusionCacheProtoBufNetSerializer.Options
yield return new FusionCacheServiceStackJsonSerializer();
yield return new FusionCacheServiceStackJsonSerializer(new FusionCacheServiceStackJsonSerializer.Options
{
StreamManager = new RecyclableMemoryStreamManager()
});
yield return new FusionCacheSystemTextJsonSerializer();
yield return new FusionCacheSystemTextJsonSerializer(new FusionCacheSystemTextJsonSerializer.Options
{
StreamManager = new RecyclableMemoryStreamManager()
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,15 @@
<PackageReference Include="EasyCaching.InMemory" Version="1.9.2" />
<PackageReference Include="FastCache.Cached" Version="1.8.2" />
<PackageReference Include="LazyCache" Version="2.4.0" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.9.24507.7" />
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.0.0-preview.9.24556.5" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack\ZiggyCreatures.FusionCache.Serialization.CysharpMemoryPack.csproj" />
<ProjectReference Include="..\..\src\ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack\ZiggyCreatures.FusionCache.Serialization.NeueccMessagePack.csproj" />
<ProjectReference Include="..\..\src\ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson\ZiggyCreatures.FusionCache.Serialization.NewtonsoftJson.csproj" />
<ProjectReference Include="..\..\src\ZiggyCreatures.FusionCache.Serialization.ProtoBufNet\ZiggyCreatures.FusionCache.Serialization.ProtoBufNet.csproj" />
<ProjectReference Include="..\..\src\ZiggyCreatures.FusionCache.Serialization.ServiceStackJson\ZiggyCreatures.FusionCache.Serialization.ServiceStackJson.csproj" />
<ProjectReference Include="..\..\src\ZiggyCreatures.FusionCache.Serialization.SystemTextJson\ZiggyCreatures.FusionCache.Serialization.SystemTextJson.csproj" />
<ProjectReference Include="..\..\src\ZiggyCreatures.FusionCache\ZiggyCreatures.FusionCache.csproj" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,7 @@ public ValueTask<byte[]> SerializeAsync<T>(T? obj, CancellationToken token = def
{
return new ValueTask<T?>(Deserialize<T>(data));
}

/// <inheritdoc />
public override string ToString() => $"{GetType().Name}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,7 @@ public ValueTask<byte[]> SerializeAsync<T>(T? obj, CancellationToken token = def
// PER @neuecc 'S SUGGESTION: AVOID AWAITING ON A MEMORY STREAM
return new ValueTask<T?>(Deserialize<T>(data));
}

/// <inheritdoc />
public override string ToString() => $"{GetType().Name}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,7 @@ public ValueTask<byte[]> SerializeAsync<T>(T? obj, CancellationToken token = def
{
return new ValueTask<T?>(Deserialize<T>(data));
}

/// <inheritdoc />
public override string ToString() => $"{GetType().Name}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,7 @@ public ValueTask<byte[]> SerializeAsync<T>(T? obj, CancellationToken token = def
{
return new ValueTask<T?>(Deserialize<T>(data));
}

/// <inheritdoc />
public override string ToString() => $"{(_streamManager != null ? "Recyclable" : "")}{GetType().Name}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,7 @@ public ValueTask<byte[]> SerializeAsync<T>(T? obj, CancellationToken token = def
//using var stream = new MemoryStream(data);
//return await JsonSerializer.DeserializeFromStreamAsync<T?>(stream);
}

/// <inheritdoc />
public override string ToString() => $"{(_streamManager != null ? "Recyclable" : "")}{GetType().Name}";
}
Original file line number Diff line number Diff line change
Expand Up @@ -86,4 +86,7 @@ public async ValueTask<byte[]> SerializeAsync<T>(T? obj, CancellationToken token
using var stream = GetMemoryStream(data);
return await JsonSerializer.DeserializeAsync<T>(stream, _serializerOptions, token);
}

/// <inheritdoc />
public override string ToString() => $"{(_streamManager != null ? "Recyclable" : "")}{GetType().Name}";
}
30 changes: 30 additions & 0 deletions tests/ZiggyCreatures.FusionCache.Tests/SerializationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,36 @@ public void LoopSucceedsWithComplexTypes(SerializerType serializerType)
Assert.Equal(data, looped);
}

[Theory]
[ClassData(typeof(SerializerTypesClassData))]
public async Task LoopSucceedsWitComplexTypesArrayAsync(SerializerType serializerType)
{
var data = new ComplexType[1024 * 1024];
for(int i = 0; i < data.Length; i++)
{
data[i] = ComplexType.CreateSample();
}

var serializer = TestsUtils.GetSerializer(serializerType);
var looped = await LoopDeLoopAsync(serializer, data);
Assert.Equal(data, looped);
}

[Theory]
[ClassData(typeof(SerializerTypesClassData))]
public void LoopSucceedsWithComplexTypesArray(SerializerType serializerType)
{
var data = new ComplexType[1024 * 1024];
for (int i = 0; i < data.Length; i++)
{
data[i] = ComplexType.CreateSample();
}

var serializer = TestsUtils.GetSerializer(serializerType);
var looped = LoopDeLoop(serializer, data);
Assert.Equal(data, looped);
}

[Theory]
[ClassData(typeof(SerializerTypesClassData))]
public async Task LoopDoesNotFailWithNullAsync(SerializerType serializerType)
Expand Down

0 comments on commit b955c9a

Please sign in to comment.