Skip to content

Commit 059ff6f

Browse files
authored
Merge pull request MessagePack-CSharp#27 from AArnott/fix9
Add support for IBufferWriter<byte>
2 parents 82b6cda + a849607 commit 059ff6f

28 files changed

+1735
-2603
lines changed

sandbox/DynamicCodeDumper/DynamicCodeDumper.csproj

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
<Compile Include="..\..\src\MessagePack\FloatBits.cs">
1111
<Link>Code\FloatBits.cs</Link>
1212
</Compile>
13+
<Compile Include="..\..\src\MessagePack\BufferWriter.cs">
14+
<Link>Code\BufferWriter.cs</Link>
15+
</Compile>
1316
<Compile Include="..\..\src\MessagePack\Formatters\IMessagePackFormatter.cs">
1417
<Link>Code\IMessagePackFormatter.cs</Link>
1518
</Compile>
@@ -58,9 +61,6 @@
5861
<Compile Include="..\..\src\MessagePack\Internal\UnsafeMemory.Low.cs">
5962
<Link>Code\UnsafeMemory.Low.cs</Link>
6063
</Compile>
61-
<Compile Include="..\..\src\MessagePack\MessagePackBinary.cs">
62-
<Link>Code\MessagePackBinary.cs</Link>
63-
</Compile>
6464
<Compile Include="..\..\src\MessagePack\ExtensionHeader.cs">
6565
<Link>Code\ExtensionHeader.cs</Link>
6666
</Compile>

sandbox/DynamicCodeDumper/Program.cs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,12 @@ static void Main(string[] args)
6060

6161
//DynamicContractlessObjectResolver.Instance.GetFormatter<EntityBase>();
6262

63-
var sequenceWriter = new MessagePackWriter();
64-
f.Serialize(ref sequenceWriter, new MyClass { MyProperty1 = 100, MyProperty2 = "foo" }, null);
65-
sequenceWriter.Flush();
63+
using (var sequence = new Sequence<byte>())
64+
{
65+
var sequenceWriter = new MessagePackWriter(sequence);
66+
f.Serialize(ref sequenceWriter, new MyClass { MyProperty1 = 100, MyProperty2 = "foo" }, null);
67+
sequenceWriter.Flush();
68+
}
6669
}
6770
catch (Exception ex)
6871
{

sandbox/PerfBenchmarkDotNet/MessagePackWriterBenchmark.cs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ namespace PerfBenchmarkDotNet
1212
public class MessagePackWriterBenchmark
1313
{
1414
private const int RepsOverArray = 300 * 1024;
15+
private readonly Sequence<byte> sequence = new Sequence<byte>();
1516

1617
private readonly int[] values = new int[newmsgpack::MessagePack.MessagePackCode.MaxFixInt];
1718
private readonly byte[] byteValues = new byte[newmsgpack::MessagePack.MessagePackCode.MaxFixInt];
@@ -34,11 +35,17 @@ public void GlobalSetup()
3435
}
3536
}
3637

38+
[IterationSetup]
39+
public void IterationSetup() => this.sequence.GetSpan(bytes.Length);
40+
41+
[IterationCleanup]
42+
public void IterationCleanup() => this.sequence.Reset();
43+
3744
[Benchmark(OperationsPerInvoke = RepsOverArray * newmsgpack::MessagePack.MessagePackCode.MaxFixInt)]
3845
[BenchmarkCategory("2.0")]
3946
public void Write_Byte()
4047
{
41-
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.bytes);
48+
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.sequence);
4249
for (int j = 0; j < RepsOverArray; j++)
4350
{
4451
for (int i = 0; i < byteValues.Length; i++)
@@ -66,7 +73,7 @@ public void WriteByte()
6673
[BenchmarkCategory("2.0")]
6774
public void Write_UInt32()
6875
{
69-
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.bytes);
76+
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.sequence);
7077
for (int j = 0; j < RepsOverArray; j++)
7178
{
7279
for (int i = 0; i < values.Length; i++)
@@ -80,7 +87,7 @@ public void Write_UInt32()
8087
[BenchmarkCategory("2.0")]
8188
public void Write_Int32()
8289
{
83-
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.bytes);
90+
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.sequence);
8491
for (int j = 0; j < RepsOverArray; j++)
8592
{
8693
for (int i = 0; i < values.Length; i++)
@@ -110,12 +117,14 @@ public void Write_String()
110117
{
111118
for (int j = 0; j < 5; j++)
112119
{
113-
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.bytes);
120+
var writer = new newmsgpack::MessagePack.MessagePackWriter(this.sequence);
114121
for (int i = 0; i < 1000000; i++)
115122
{
116123
writer.Write("Hello!");
117124
}
118125
writer.Flush();
126+
this.sequence.Reset();
127+
this.sequence.GetSpan(bytes.Length);
119128
}
120129
}
121130

sandbox/PerfBenchmarkDotNet/PerfBenchmarkDotNet.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
44
<TargetFrameworks>net47;netcoreapp2.0</TargetFrameworks>
5+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
56
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
67
</PropertyGroup>
78
<ItemGroup>

sandbox/PerfBenchmarkDotNet/Program.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ static void Main(string[] args)
5353
typeof(ImproveStringKeySerializeBenchmark),
5454
typeof(MessagePackReaderBenchmark),
5555
typeof(MessagePackWriterBenchmark),
56+
typeof(SpanBenchmarks),
5657
});
5758

5859
// args = new[] { "0" };
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
using BenchmarkDotNet.Attributes;
4+
5+
namespace PerfBenchmarkDotNet
6+
{
7+
public class SpanBenchmarks
8+
{
9+
private const string SomeString = "Hi there";
10+
private static readonly byte[] byteArray = new byte[1];
11+
12+
[Benchmark]
13+
public unsafe void PinString()
14+
{
15+
fixed (char* pChars = SomeString)
16+
{
17+
char ch = pChars[0];
18+
}
19+
}
20+
21+
[Benchmark]
22+
public unsafe void GetSpanFromString()
23+
{
24+
char ch = SomeString.AsSpan()[0];
25+
}
26+
27+
[Benchmark]
28+
public unsafe void PinArray()
29+
{
30+
fixed (byte* pBytes = byteArray)
31+
{
32+
pBytes[0] = 7;
33+
}
34+
}
35+
36+
[Benchmark]
37+
public unsafe void PinArray_Indexer()
38+
{
39+
fixed (byte* pBytes = &byteArray[0])
40+
{
41+
pBytes[0] = 7;
42+
}
43+
}
44+
45+
[Benchmark]
46+
public unsafe void PinSpan_GetReference()
47+
{
48+
Span<byte> span = byteArray;
49+
fixed (byte* pBytes = &MemoryMarshal.GetReference(span))
50+
{
51+
pBytes[0] = 7;
52+
}
53+
}
54+
55+
[Benchmark]
56+
public void PinSpan()
57+
{
58+
PinSpan_Helper(byteArray);
59+
}
60+
61+
private static unsafe void PinSpan_Helper(Span<byte> bytes)
62+
{
63+
fixed (byte* pBytes = bytes)
64+
{
65+
pBytes[0] = 7;
66+
}
67+
}
68+
69+
[Benchmark]
70+
public void PinSpan_Indexer()
71+
{
72+
PinSpan_Indexer_Helper(byteArray);
73+
}
74+
75+
private static unsafe void PinSpan_Indexer_Helper(Span<byte> bytes)
76+
{
77+
fixed (byte* pBytes = &bytes[0])
78+
{
79+
pBytes[0] = 8;
80+
}
81+
}
82+
}
83+
}

src/MessagePack/BufferWriter.cs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
// Copyright (c) .NET Foundation. All rights reserved.
2+
// Copyright (c) Andrew Arnott. All rights reserved.
3+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
4+
5+
using System;
6+
using System.Buffers;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
10+
namespace MessagePack
11+
{
12+
/// <summary>
13+
/// A fast access struct that wraps <see cref="IBufferWriter{T}"/>.
14+
/// </summary>
15+
internal ref struct BufferWriter
16+
{
17+
/// <summary>
18+
/// The underlying <see cref="IBufferWriter{T}"/>.
19+
/// </summary>
20+
private IBufferWriter<byte> _output;
21+
22+
/// <summary>
23+
/// The result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)"/>, less any bytes already "consumed" with <see cref="Advance(int)"/>.
24+
/// Backing field for the <see cref="Span"/> property.
25+
/// </summary>
26+
private Span<byte> _span;
27+
28+
/// <summary>
29+
/// The result of the last call to <see cref="IBufferWriter{T}.GetMemory(int)"/>, less any bytes already "consumed" with <see cref="Advance(int)"/>.
30+
/// </summary>
31+
private ArraySegment<byte> _segment;
32+
33+
/// <summary>
34+
/// The number of uncommitted bytes (all the calls to <see cref="Advance(int)"/> since the last call to <see cref="Commit"/>).
35+
/// </summary>
36+
private int _buffered;
37+
38+
/// <summary>
39+
/// The total number of bytes written with this writer.
40+
/// Backing field for the <see cref="BytesCommitted"/> property.
41+
/// </summary>
42+
private long _bytesCommitted;
43+
44+
/// <summary>
45+
/// Initializes a new instance of the <see cref="BufferWriter"/> struct.
46+
/// </summary>
47+
/// <param name="output">The <see cref="IBufferWriter{T}"/> to be wrapped.</param>
48+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
49+
public BufferWriter(IBufferWriter<byte> output)
50+
{
51+
_buffered = 0;
52+
_bytesCommitted = 0;
53+
_output = output;
54+
55+
var memory = _output.GetMemory();
56+
MemoryMarshal.TryGetArray(memory, out _segment);
57+
_span = memory.Span;
58+
}
59+
60+
/// <summary>
61+
/// Gets the result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)"/>.
62+
/// </summary>
63+
public Span<byte> Span => _span;
64+
65+
/// <summary>
66+
/// Gets the total number of bytes written with this writer.
67+
/// </summary>
68+
public long BytesCommitted => _bytesCommitted;
69+
70+
/// <summary>
71+
/// Gets the <see cref="IBufferWriter{T}"/> underlying this instance.
72+
/// </summary>
73+
internal IBufferWriter<byte> UnderlyingWriter => _output;
74+
75+
public Span<byte> GetSpan(int sizeHint)
76+
{
77+
Ensure(sizeHint);
78+
return this.Span;
79+
}
80+
81+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
82+
public ref byte GetPointer(int sizeHint)
83+
{
84+
Ensure(sizeHint);
85+
86+
if (_segment.Array != null)
87+
{
88+
return ref _segment.Array[_segment.Offset + _buffered];
89+
}
90+
else
91+
{
92+
return ref _span.GetPinnableReference();
93+
}
94+
}
95+
96+
/// <summary>
97+
/// Calls <see cref="IBufferWriter{T}.Advance(int)"/> on the underlying writer
98+
/// with the number of uncommitted bytes.
99+
/// </summary>
100+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
101+
public void Commit()
102+
{
103+
var buffered = _buffered;
104+
if (buffered > 0)
105+
{
106+
_bytesCommitted += buffered;
107+
_buffered = 0;
108+
_output.Advance(buffered);
109+
_span = default;
110+
}
111+
}
112+
113+
/// <summary>
114+
/// Used to indicate that part of the buffer has been written to.
115+
/// </summary>
116+
/// <param name="count">The number of bytes written to.</param>
117+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
118+
public void Advance(int count)
119+
{
120+
_buffered += count;
121+
_span = _span.Slice(count);
122+
}
123+
124+
/// <summary>
125+
/// Copies the caller's buffer into this writer and calls <see cref="Advance(int)"/> with the length of the source buffer.
126+
/// </summary>
127+
/// <param name="source">The buffer to copy in.</param>
128+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
129+
public void Write(ReadOnlySpan<byte> source)
130+
{
131+
if (_span.Length >= source.Length)
132+
{
133+
source.CopyTo(_span);
134+
Advance(source.Length);
135+
}
136+
else
137+
{
138+
WriteMultiBuffer(source);
139+
}
140+
}
141+
142+
/// <summary>
143+
/// Acquires a new buffer if necessary to ensure that some given number of bytes can be written to a single buffer.
144+
/// </summary>
145+
/// <param name="count">The number of bytes that must be allocated in a single buffer.</param>
146+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
147+
public void Ensure(int count = 1)
148+
{
149+
if (_span.Length < count)
150+
{
151+
EnsureMore(count);
152+
}
153+
}
154+
155+
/// <summary>
156+
/// Gets a fresh span to write to, with an optional minimum size.
157+
/// </summary>
158+
/// <param name="count">The minimum size for the next requested buffer.</param>
159+
[MethodImpl(MethodImplOptions.NoInlining)]
160+
private void EnsureMore(int count = 0)
161+
{
162+
if (_buffered > 0)
163+
{
164+
Commit();
165+
}
166+
167+
var memory = _output.GetMemory(count);
168+
MemoryMarshal.TryGetArray(memory, out _segment);
169+
_span = memory.Span;
170+
}
171+
172+
/// <summary>
173+
/// Copies the caller's buffer into this writer, potentially across multiple buffers from the underlying writer.
174+
/// </summary>
175+
/// <param name="source">The buffer to copy into this writer.</param>
176+
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
177+
{
178+
while (source.Length > 0)
179+
{
180+
if (_span.Length == 0)
181+
{
182+
EnsureMore();
183+
}
184+
185+
var writable = Math.Min(source.Length, _span.Length);
186+
source.Slice(0, writable).CopyTo(_span);
187+
source = source.Slice(writable);
188+
Advance(writable);
189+
}
190+
}
191+
}
192+
}

0 commit comments

Comments
 (0)