-
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat: Implement MessagePack serialization with binary optimization
- Loading branch information
Showing
14 changed files
with
788 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
12 changes: 12 additions & 0 deletions
12
benchmark/UUID.Serialization.MessagePack.Benchmarks/Program.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
using BenchmarkDotNet.Running; | ||
|
||
namespace UUIDSerializationMessagePackBenchmarks | ||
{ | ||
internal class Program | ||
{ | ||
static void Main(string[] args) | ||
{ | ||
BenchmarkRunner.Run<UUIDFormatterBenchmarks>(); | ||
} | ||
} | ||
} |
26 changes: 26 additions & 0 deletions
26
...UID.Serialization.MessagePack.Benchmarks/UUID.Serialization.MessagePack.Benchmarks.csproj
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<Project Sdk="Microsoft.NET.Sdk"> | ||
|
||
<PropertyGroup> | ||
<Nullable>enable</Nullable> | ||
<OutputType>Exe</OutputType> | ||
<DebugType>pdbonly</DebugType> | ||
<DebugSymbols>true</DebugSymbols> | ||
<LangVersion>preview</LangVersion> | ||
<AnalysisLevel>preview</AnalysisLevel> | ||
<ImplicitUsings>enable</ImplicitUsings> | ||
<PlatformTarget>AnyCPU</PlatformTarget> | ||
<TargetFramework>net9.0</TargetFramework> | ||
<RootNamespace>UUIDSerializationMessagePackBenchmarks</RootNamespace> | ||
</PropertyGroup> | ||
|
||
<ItemGroup> | ||
<ProjectReference Include="..\..\src\UUID\UUID.csproj" /> | ||
<ProjectReference Include="..\..\src\UUID.Serialization.MessagePack\UUID.Serialization.MessagePack.csproj" /> | ||
</ItemGroup> | ||
|
||
<ItemGroup> | ||
<PackageReference Include="MessagePack" Version="3.1.1" /> | ||
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" /> | ||
</ItemGroup> | ||
|
||
</Project> |
117 changes: 117 additions & 0 deletions
117
benchmark/UUID.Serialization.MessagePack.Benchmarks/UUIDFormatterBenchmarks.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
using BenchmarkDotNet.Attributes; | ||
using BenchmarkDotNet.Jobs; | ||
using BenchmarkDotNet.Order; | ||
using MessagePack; | ||
using MessagePack.Resolvers; | ||
|
||
namespace UUIDSerializationMessagePackBenchmarks | ||
{ | ||
[RankColumn] | ||
[MemoryDiagnoser] | ||
[Orderer(SummaryOrderPolicy.FastestToSlowest)] | ||
[SimpleJob(RuntimeMoniker.Net90, launchCount: 1, warmupCount: 2, iterationCount: 3)] | ||
public class UUIDFormatterBenchmarks | ||
{ | ||
private readonly MessagePackSerializerOptions _options; | ||
private readonly UUID _uuid; | ||
private readonly byte[] _serializedUuid; | ||
private readonly List<UUID> _uuidList; | ||
private readonly byte[] _serializedUuidList; | ||
private readonly Dictionary<UUID, string> _uuidDictionary; | ||
private readonly byte[] _serializedUuidDictionary; | ||
private readonly TestModel _model; | ||
private readonly byte[] _serializedModel; | ||
|
||
public UUIDFormatterBenchmarks() | ||
{ | ||
_options = MessagePackSerializerOptions.Standard | ||
.WithResolver(CompositeResolver.Create( | ||
UUIDResolver.Instance, | ||
StandardResolver.Instance | ||
)); | ||
|
||
_uuid = new UUID(); | ||
_serializedUuid = MessagePackSerializer.Serialize(_uuid, _options); | ||
|
||
_uuidList = new List<UUID> { new(), new(), new() }; | ||
_serializedUuidList = MessagePackSerializer.Serialize(_uuidList, _options); | ||
|
||
_uuidDictionary = new Dictionary<UUID, string> | ||
{ | ||
{ new UUID(), "Value1" }, | ||
{ new UUID(), "Value2" }, | ||
{ new UUID(), "Value3" } | ||
}; | ||
_serializedUuidDictionary = MessagePackSerializer.Serialize(_uuidDictionary, _options); | ||
|
||
_model = new TestModel | ||
{ | ||
Id = new UUID(), | ||
Name = "Test", | ||
Items = new List<UUID> { new(), new() } | ||
}; | ||
_serializedModel = MessagePackSerializer.Serialize(_model, _options); | ||
} | ||
|
||
[Benchmark] | ||
public byte[] Serialize_SingleUUID() | ||
{ | ||
return MessagePackSerializer.Serialize(_uuid, _options); | ||
} | ||
|
||
[Benchmark] | ||
public UUID Deserialize_SingleUUID() | ||
{ | ||
return MessagePackSerializer.Deserialize<UUID>(_serializedUuid, _options); | ||
} | ||
|
||
[Benchmark] | ||
public byte[] Serialize_UUIDList() | ||
{ | ||
return MessagePackSerializer.Serialize(_uuidList, _options); | ||
} | ||
|
||
[Benchmark] | ||
public List<UUID> Deserialize_UUIDList() | ||
{ | ||
return MessagePackSerializer.Deserialize<List<UUID>>(_serializedUuidList, _options); | ||
} | ||
|
||
[Benchmark] | ||
public byte[] Serialize_UUIDDictionary() | ||
{ | ||
return MessagePackSerializer.Serialize(_uuidDictionary, _options); | ||
} | ||
|
||
[Benchmark] | ||
public Dictionary<UUID, string> Deserialize_UUIDDictionary() | ||
{ | ||
return MessagePackSerializer.Deserialize<Dictionary<UUID, string>>(_serializedUuidDictionary, _options); | ||
} | ||
|
||
[Benchmark] | ||
public byte[] Serialize_ComplexModel() | ||
{ | ||
return MessagePackSerializer.Serialize(_model, _options); | ||
} | ||
|
||
[Benchmark] | ||
public TestModel Deserialize_ComplexModel() | ||
{ | ||
return MessagePackSerializer.Deserialize<TestModel>(_serializedModel, _options); | ||
} | ||
} | ||
|
||
[MessagePackObject] | ||
public class TestModel | ||
{ | ||
[Key(0)] | ||
public UUID Id { get; set; } | ||
|
||
[Key(1)] | ||
public string Name { get; set; } = ""; | ||
|
||
[Key(2)] | ||
public List<UUID> Items { get; set; } = new(); | ||
} | ||
} |
88 changes: 88 additions & 0 deletions
88
src/UUID.Serialization.MessagePack/Formatters/UUIDFormatter.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
using MessagePack; | ||
using MessagePack.Formatters; | ||
using System.Buffers; | ||
|
||
namespace System | ||
{ | ||
/// <summary> | ||
/// MessagePack formatter for UUID type. | ||
/// Handles serialization and deserialization of UUID values in MessagePack format. | ||
/// </summary> | ||
/// <remarks> | ||
/// This formatter serializes UUID as a 16-byte binary array where: | ||
/// - First 8 bytes contain the timestamp | ||
/// - Last 8 bytes contain the random value | ||
/// | ||
/// The formatter supports both modern (.NET 6+) and legacy (.NET Framework) serialization methods | ||
/// through conditional compilation. | ||
/// </remarks> | ||
public class UUIDFormatter : IMessagePackFormatter<UUID> | ||
{ | ||
/// <summary> | ||
/// Deserializes a UUID from MessagePack format. | ||
/// </summary> | ||
/// <param name="reader">The MessagePack reader containing the serialized data.</param> | ||
/// <param name="options">Serializer options for customizing the deserialization process.</param> | ||
/// <returns>A deserialized UUID instance.</returns> | ||
/// <exception cref="MessagePackSerializationException"> | ||
/// Thrown when: | ||
/// - The input is null | ||
/// - The input length is not exactly 16 bytes | ||
/// - The MessagePack format is invalid | ||
/// </exception> | ||
/// <remarks> | ||
/// The deserialization process expects a binary format with exactly 16 bytes. | ||
/// The bytes are interpreted as two 8-byte segments for timestamp and random values. | ||
/// </remarks> | ||
public UUID Deserialize(ref MessagePackReader reader, MessagePackSerializerOptions options) | ||
{ | ||
ReadOnlySequence<byte>? bin = reader.ReadBytes(); | ||
|
||
if (bin == null) | ||
{ | ||
throw new MessagePackSerializationException($"Unexpected msgpack code {MessagePackCode.Nil} ({MessagePackCode.ToFormatName(MessagePackCode.Nil)}) encountered."); | ||
} | ||
|
||
byte[] bytes = bin.Value.ToArray(); | ||
if (bytes.Length != 16) | ||
{ | ||
throw new MessagePackSerializationException($"UUID must be exactly 16 bytes long, but was {bytes.Length} bytes."); | ||
} | ||
|
||
ulong timestamp = BitConverter.ToUInt64(bytes, 0); | ||
ulong random = BitConverter.ToUInt64(bytes, 8); | ||
|
||
return new UUID(timestamp, random); | ||
} | ||
|
||
/// <summary> | ||
/// Serializes a UUID to MessagePack format. | ||
/// </summary> | ||
/// <param name="writer">The MessagePack writer to write the serialized data to.</param> | ||
/// <param name="value">The UUID value to serialize.</param> | ||
/// <param name="options">Serializer options for customizing the serialization process.</param> | ||
/// <remarks> | ||
/// The serialization process writes the UUID as a 16-byte binary array. | ||
/// Uses different approaches based on the target framework: | ||
/// - For .NET 6+ and .NET Standard 2.1: Uses Span-based BitConverter methods | ||
/// - For other frameworks: Uses traditional byte array methods with Buffer.BlockCopy | ||
/// </remarks> | ||
public void Serialize(ref MessagePackWriter writer, UUID value, MessagePackSerializerOptions options) | ||
{ | ||
const int Length = 16; | ||
byte[] bytes = new byte[Length]; | ||
|
||
#if NETCOREAPP || NETSTANDARD2_1 | ||
BitConverter.TryWriteBytes(bytes.AsSpan(0), value.Timestamp); | ||
BitConverter.TryWriteBytes(bytes.AsSpan(8), value.Random); | ||
#else | ||
byte[] timestampBytes = BitConverter.GetBytes(value.Timestamp); | ||
byte[] randomBytes = BitConverter.GetBytes(value.Random); | ||
Buffer.BlockCopy(timestampBytes, 0, bytes, 0, 8); | ||
Buffer.BlockCopy(randomBytes, 0, bytes, 8, 8); | ||
#endif | ||
|
||
writer.Write(bytes); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# UUID.Serialization.MessagePack | ||
|
||
A MessagePack serialization provider for the UUID library. | ||
|
||
## Installation | ||
|
||
```bash | ||
dotnet add package UUID.Serialization.MessagePack | ||
``` | ||
|
||
## Usage | ||
|
||
### Basic Usage | ||
|
||
```csharp | ||
using MessagePack; | ||
using MessagePack.Resolvers; | ||
|
||
// Register the resolver globally | ||
var options = MessagePackSerializerOptions.Standard | ||
.WithResolver(CompositeResolver.Create( | ||
UUIDResolver.Instance, | ||
StandardResolver.Instance | ||
)); | ||
|
||
// Serialize | ||
var uuid = new UUID(); | ||
byte[] bytes = MessagePackSerializer.Serialize(uuid, options); | ||
|
||
// Deserialize | ||
UUID deserializedUuid = MessagePackSerializer.Deserialize<UUID>(bytes, options); | ||
``` | ||
|
||
### With Models | ||
|
||
```csharp | ||
public class UserModel | ||
{ | ||
[Key(0)] | ||
public UUID Id { get; set; } | ||
|
||
[Key(1)] | ||
public string Name { get; set; } | ||
} | ||
|
||
// Register resolver | ||
var options = MessagePackSerializerOptions.Standard | ||
.WithResolver(CompositeResolver.Create( | ||
UUIDResolver.Instance, | ||
StandardResolver.Instance | ||
)); | ||
|
||
// Serialize model | ||
var user = new UserModel | ||
{ | ||
Id = new UUID(), | ||
Name = "John Doe" | ||
}; | ||
byte[] bytes = MessagePackSerializer.Serialize(user, options); | ||
|
||
// Deserialize model | ||
UserModel deserializedUser = MessagePackSerializer.Deserialize<UserModel>(bytes, options); | ||
``` | ||
|
||
## Features | ||
|
||
- Support for null handling | ||
- Thread-safe implementation | ||
- Comprehensive error handling | ||
- Efficient binary serialization | ||
- Full documentation with XML comments | ||
- Seamless integration with MessagePack | ||
- Custom error messages for better debugging | ||
|
||
## Error Handling | ||
|
||
The formatter provides detailed error messages for common scenarios: | ||
|
||
- Null values: "Cannot convert null value to UUID" | ||
- Invalid byte length: "UUID must be exactly 16 bytes long" | ||
- Incorrect MessagePack types: Shows expected vs actual type | ||
- Invalid binary format: Includes details about the attempted deserialization | ||
|
||
## Requirements | ||
|
||
- UUID library | ||
- MessagePack 2.5.129 or later | ||
- Supports .NET 6.0+, .NET Standard 2.0+, and .NET Framework 4.8+ | ||
|
||
## Contributing | ||
|
||
Contributions are welcome! Please feel free to submit a Pull Request. | ||
|
||
## License | ||
|
||
This project is licensed under the terms of the LICENSE file included in the root directory. |
Oops, something went wrong.