Skip to content

Commit b470e94

Browse files
authored
Merge pull request #70 from Cysharp/pooling-formatter
Deserialize array from ArrayPool
2 parents 628dc3f + eff7833 commit b470e94

File tree

10 files changed

+418
-47
lines changed

10 files changed

+418
-47
lines changed

README.md

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -613,7 +613,7 @@ public abstract class MemoryPackCustomFormatterAttribute<T> : Attribute
613613
}
614614
```
615615

616-
In built-in attribtues, `Utf8StringFormatterAttribute`, `Utf16StringFormatterAttribute`, `InternStringFormatterAttribute`, `OrdinalIgnoreCaseStringDictionaryFormatterAttribtue<TValue>`, `BitPackFormatterAttribtue`, `BrotliFormatter`, `BrotliStringFormatter`, `BrotliFormatter<T>` exsits.
616+
In built-in attribtues, `Utf8StringFormatterAttribute`, `Utf16StringFormatterAttribute`, `InternStringFormatterAttribute`, `OrdinalIgnoreCaseStringDictionaryFormatterAttribtue<TValue>`, `BitPackFormatterAttribtue`, `BrotliFormatter`, `BrotliStringFormatter`, `BrotliFormatter<T>`, `MemoryPoolFormatter<T>`, `ReadOnlyMemoryPoolFormatter<T>` exsits.
617617

618618
```csharp
619619
[MemoryPackable]
@@ -699,6 +699,80 @@ public partial class Sample
699699
}
700700
```
701701

702+
Deserialize array pooling
703+
---
704+
For deserializing large array(any `T`), MemoryPack offers multiple efficient pooling methods. The most effective way is to use the [#Overwrite](#overwrite) function. In particular, with `List<T>`, always reuse.
705+
706+
```csharp
707+
[MemoryPackable]
708+
public partial class ListBytesSample
709+
{
710+
public int Id { get; set; }
711+
public List<byte> Payload { get; set; }
712+
}
713+
714+
// ----
715+
716+
// List<byte> is reused, no allocation in deserialize.
717+
MemoryPackSerializer.Deserialize<ListBytesSample>(bin, ref reuseObject);
718+
719+
// for efficient operation, you can get Span<T> by CollectionsMarshal
720+
var span = CollectionsMarshal.AsSpan(value.Payload);
721+
```
722+
723+
A convinient way is to deserialize to an ArrayPool at deserialization time. MemoryPack provides `MemoryPoolFormatter<T>` and `ReadOnlyMemoryPoolFormatter<T>`.
724+
725+
```csharp
726+
[MemoryPackable]
727+
public partial class PoolModelSample : IDisposable
728+
{
729+
public int Id { get; }
730+
731+
[MemoryPoolFormatter<byte>]
732+
public Memory<byte> Payload { get; private set; }
733+
734+
public PoolModelSample(int id, Memory<byte> payload)
735+
{
736+
Id = id;
737+
Payload = payload;
738+
}
739+
740+
// You must write the return code yourself, here is snippet.
741+
742+
bool usePool;
743+
744+
[MemoryPackOnDeserialized]
745+
void OnDeserialized()
746+
{
747+
usePool = true;
748+
}
749+
750+
public void Dispose()
751+
{
752+
if (!usePool) return;
753+
754+
Return(Payload); Payload = default;
755+
}
756+
757+
static void Return<T>(Memory<T> memory) => Return((ReadOnlyMemory<T>)memory);
758+
759+
static void Return<T>(ReadOnlyMemory<T> memory)
760+
{
761+
if (MemoryMarshal.TryGetArray(memory, out var segment) && segment.Array is { Length: > 0 })
762+
{
763+
ArrayPool<T>.Shared.Return(segment.Array, clearArray: RuntimeHelpers.IsReferenceOrContainsReferences<T>());
764+
}
765+
}
766+
}
767+
768+
// ---
769+
770+
using(var value = MemoryPackSerializer.Deserialize<PoolModelSample>(bin))
771+
{
772+
// do anything...
773+
} // return to ArrayPool
774+
```
775+
702776
Performance
703777
---
704778
TODO for describe details, stay tuned.

sandbox/SandboxConsoleApp/ForReadMe.cs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
using MemoryPack;
22
using System;
3+
using System.Buffers;
34
using System.Collections.Generic;
45
using System.Linq;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
58
using System.Text;
69
using System.Threading.Tasks;
710
using static System.Runtime.InteropServices.JavaScript.JSType;
@@ -158,3 +161,44 @@ public partial class MyDictContainer
158161

159162

160163
}
164+
165+
166+
[MemoryPackable]
167+
public partial class PoolModelSample : IDisposable
168+
{
169+
public int Id { get; }
170+
171+
[MemoryPoolFormatter<byte>]
172+
public Memory<byte> Payload { get; private set; }
173+
174+
public PoolModelSample(int id, Memory<byte> payload)
175+
{
176+
Id = id;
177+
Payload = payload;
178+
}
179+
180+
bool usePool;
181+
182+
[MemoryPackOnDeserialized]
183+
void OnDeserialized()
184+
{
185+
usePool = true;
186+
}
187+
188+
public void Dispose()
189+
{
190+
if (!usePool) return;
191+
192+
Return(Payload); Payload = default;
193+
}
194+
195+
static void Return<T>(Memory<T> memory) => Return((ReadOnlyMemory<T>)memory);
196+
197+
static void Return<T>(ReadOnlyMemory<T> memory)
198+
{
199+
if (MemoryMarshal.TryGetArray(memory, out var segment) && segment.Array is { Length: > 0 })
200+
{
201+
ArrayPool<T>.Shared.Return(segment.Array, clearArray: RuntimeHelpers.IsReferenceOrContainsReferences<T>());
202+
}
203+
}
204+
}

sandbox/SandboxConsoleApp/Program.cs

Lines changed: 19 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
#pragma warning disable CS8600
22
#pragma warning disable CS0169
3+
#pragma warning disable CS8602
4+
#pragma warning disable CS8618
35

46
using MemoryPack;
57
using MemoryPack.Compression;
@@ -26,63 +28,40 @@
2628
using System.Xml.Linq;
2729

2830

29-
var str1 = File.ReadAllText(@"Hypertext Transfer Protocol - Wikipedia.html");
30-
var str2 = File.ReadAllText(@"Hypertext Transfer Protocol - Wikipedia_JPN.html");
31-
var str3 = File.ReadAllText(@"Hypertext Transfer Protocol - Wikipedia _JPN_TextOnly.txt");
32-
33-
34-
foreach (var item in new[] { str1, str2, str3 })
35-
{
36-
if (item == str1) Console.WriteLine("English");
37-
if (item == str2) Console.WriteLine("Japanese");
38-
if (item == str3) Console.WriteLine("Jpn Text Only");
39-
40-
var utf16 = MemoryMarshal.AsBytes(item.AsSpan()).ToArray();
41-
var utf8 = Encoding.UTF8.GetBytes(item);
42-
43-
Console.WriteLine($"UTF16: {HumanReadable(utf16.Length)}");
44-
Console.WriteLine($"UTF8: {HumanReadable(utf8.Length)}");
45-
Console.WriteLine($"Compress UTF16: {HumanReadable(Compress(utf16).Length)}");
46-
Console.WriteLine($"Compress UTF8: {HumanReadable(Compress(utf8).Length)}");
47-
}
48-
49-
50-
31+
var value = new ListBytesSample();
5132

33+
var bin = MemoryPackSerializer.Serialize(value);
34+
MemoryPackSerializer.Deserialize<ListBytesSample>(bin, ref value);
5235

36+
// for efficient operation, you can get Span<T> by CollectionsMarshal
5337

54-
// ---
38+
var span = CollectionsMarshal.AsSpan(value.Payload);
5539

56-
static byte[] Compress(ReadOnlySpan<byte> source)
40+
[MemoryPackable]
41+
public partial class ListBytesSample
5742
{
58-
using var encoder = new BrotliEncoder(1, 22);
59-
var maxLength = BrotliEncoder.GetMaxCompressedLength(source.Length);
60-
var dest = new byte[maxLength];
61-
var status = encoder.Compress(source, dest, out var consumed, out var written, true);
62-
if (status != OperationStatus.Done)
63-
{
64-
throw new Exception("DAME");
65-
}
66-
return dest.AsSpan(written).ToArray();
43+
public int Id { get; set; }
44+
public List<byte> Payload { get; set; }
6745
}
6846

69-
string HumanReadable(int length)
47+
[MemoryPackable]
48+
public partial class IntClass2
7049
{
71-
if (length < 1024) return $"{length} bytes";
72-
if (length < 1024 * 1024) return $"{length / 1024} KB";
73-
if (length < 1024 * 1024 * 1024) return $"{length / 1024 / 1024} MB";
74-
return $"{length / 1024 / 1024 / 1024} GB";
50+
public int Value { get; set; }
7551
}
7652

53+
54+
7755
[MemoryPackable]
78-
public partial class IntClass2
56+
public partial struct BrotliValue<T>
7957
{
80-
public int Value { get; set; }
58+
public long Value { get; set; }
8159
}
8260

8361

8462

8563

64+
8665
//var arrayBufferWriter = new ArrayBufferWriter<byte>();
8766

8867

sandbox/SandboxWebApp/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using MemoryPack;
22
using MemoryPack.AspNetCoreMvcFormatter;
3+
using System.Diagnostics;
34

45
var builder = WebApplication.CreateBuilder(args);
56

src/MemoryPack.Core/CustomFormatterAttributes.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,4 +102,24 @@ public override BrotliStringFormatter GetFormatter()
102102
}
103103
}
104104

105+
public sealed class MemoryPoolFormatterAttribute<T> : MemoryPackCustomFormatterAttribute<MemoryPoolFormatter<T>, Memory<T?>>
106+
{
107+
static readonly MemoryPoolFormatter<T> formatter = new MemoryPoolFormatter<T>();
108+
109+
public override MemoryPoolFormatter<T> GetFormatter()
110+
{
111+
return formatter;
112+
}
113+
}
114+
115+
public sealed class ReadOnlyMemoryPoolFormatterAttribute<T> : MemoryPackCustomFormatterAttribute<ReadOnlyMemoryPoolFormatter<T>, ReadOnlyMemory<T?>>
116+
{
117+
static readonly ReadOnlyMemoryPoolFormatter<T> formatter = new ReadOnlyMemoryPoolFormatter<T>();
118+
119+
public override ReadOnlyMemoryPoolFormatter<T> GetFormatter()
120+
{
121+
return formatter;
122+
}
123+
}
124+
105125
#endif

src/MemoryPack.Core/Formatters/ArrayFormatters.cs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,66 @@ public override void Deserialize(ref MemoryPackReader reader, scoped ref ReadOnl
154154
value = (array == null) ? default : new ReadOnlySequence<T?>(array);
155155
}
156156
}
157+
158+
[Preserve]
159+
public sealed class MemoryPoolFormatter<T> : MemoryPackFormatter<Memory<T?>>
160+
{
161+
[Preserve]
162+
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref Memory<T?> value)
163+
{
164+
writer.WriteSpan(value.Span);
165+
}
166+
167+
[Preserve]
168+
public override void Deserialize(ref MemoryPackReader reader, scoped ref Memory<T?> value)
169+
{
170+
if (!reader.TryReadCollectionHeader(out var length))
171+
{
172+
value = null;
173+
return;
174+
}
175+
176+
if (length == 0)
177+
{
178+
value = Memory<T?>.Empty;
179+
return;
180+
}
181+
182+
var memory = ArrayPool<T?>.Shared.Rent(length).AsMemory(0, length);
183+
var span = memory.Span;
184+
reader.ReadSpanWithoutReadLengthHeader(length, ref span);
185+
value = memory;
186+
}
187+
}
188+
189+
[Preserve]
190+
public sealed class ReadOnlyMemoryPoolFormatter<T> : MemoryPackFormatter<ReadOnlyMemory<T?>>
191+
{
192+
[Preserve]
193+
public override void Serialize<TBufferWriter>(ref MemoryPackWriter<TBufferWriter> writer, scoped ref ReadOnlyMemory<T?> value)
194+
{
195+
writer.WriteSpan(value.Span);
196+
}
197+
198+
[Preserve]
199+
public override void Deserialize(ref MemoryPackReader reader, scoped ref ReadOnlyMemory<T?> value)
200+
{
201+
if (!reader.TryReadCollectionHeader(out var length))
202+
{
203+
value = null;
204+
return;
205+
}
206+
207+
if (length == 0)
208+
{
209+
value = Memory<T?>.Empty;
210+
return;
211+
}
212+
213+
var memory = ArrayPool<T?>.Shared.Rent(length).AsMemory(0, length);
214+
var span = memory.Span;
215+
reader.ReadSpanWithoutReadLengthHeader(length, ref span);
216+
value = memory;
217+
}
218+
}
157219
}

src/MemoryPack.Unity/Assets/Plugins/MemoryPack/Runtime/MemoryPack.Core/CustomFormatterAttributes.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,26 @@ public override BrotliStringFormatter GetFormatter()
111111
}
112112
}
113113

114+
public sealed class MemoryPoolFormatterAttribute<T> : MemoryPackCustomFormatterAttribute<MemoryPoolFormatter<T>, Memory<T?>>
115+
{
116+
static readonly MemoryPoolFormatter<T> formatter = new MemoryPoolFormatter<T>();
117+
118+
public override MemoryPoolFormatter<T> GetFormatter()
119+
{
120+
return formatter;
121+
}
122+
}
123+
124+
public sealed class ReadOnlyMemoryPoolFormatterAttribute<T> : MemoryPackCustomFormatterAttribute<ReadOnlyMemoryPoolFormatter<T>, ReadOnlyMemory<T?>>
125+
{
126+
static readonly ReadOnlyMemoryPoolFormatter<T> formatter = new ReadOnlyMemoryPoolFormatter<T>();
127+
128+
public override ReadOnlyMemoryPoolFormatter<T> GetFormatter()
129+
{
130+
return formatter;
131+
}
132+
}
133+
114134
#endif
115135

116136
}

0 commit comments

Comments
 (0)