forked from JamesNK/SerializationBenchmark
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathBufferWriter.cs
191 lines (172 loc) · 7.9 KB
/
BufferWriter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
namespace SerializationBenchmark
{
/// <summary>
/// Represents an output sink into which <typeparamref name="T"/> data can be written.
/// </summary>
/// <remarks>
/// This class is similar to <c>System.Buffers.ArrayBufferWriter<T></c>, with the exception that the
/// <c>ArrayBufferWriter<T>.Clear</c> method has been replaced with a <see cref="Reset"/>
/// method. When used with value types, <see cref="Reset"/> doesn't clear the underlying memory
/// buffer to <c>default(T)</c>, which makes it confiderably faster. Additionally, this class
/// lets you explicitly set the capacity of the underlying buffer.
/// </remarks>
/// <typeparam name="T">The type of value that will be written into the writer.</typeparam>
public sealed class BufferWriter<T> : IBufferWriter<T>
{
private const int MaxArrayLength = 0X7FEF_FFFF; // Copy of the internal Array.MaxArrayLength const
private const int DefaultCapacity = 256;
private T[] _buffer = Array.Empty<T>();
/// <summary>
/// Gets the data written to the underlying buffer so far.
/// </summary>
/// <remarks>
/// The returned value can become stale whenever the buffer is written to again. As such, you should
/// always call this property in order to get a fresh value before reading from the buffer.
/// </remarks>
public ReadOnlyMemory<T> WrittenMemory => _buffer.AsMemory(0, WrittenCount);
/// <summary>
/// Gets the data written to the underlying buffer so far.
/// </summary>
/// <remarks>
/// The returned span can become stale whenever the buffer is written to again. As such, you should
/// always call this property in order to get a fresh span before reading from the buffer.
/// </remarks>
public ReadOnlySpan<T> WrittenSpan => _buffer.AsSpan(0, WrittenCount);
/// <summary>
/// Gets the amount of data written to the underlying buffer so far.
/// </summary>
public int WrittenCount { get; private set; }
/// <summary>
/// Gets or sets the total amount of space within the underlying buffer.
/// </summary>
/// <remarks>
/// When reducing the capacity, the value of <see cref="WrittenCount" /> is clamped to the
/// new capacity.
/// </remarks>
public int Capacity
{
get => _buffer.Length;
set
{
if (value < 0)
{
throw new ArgumentOutOfRangeException(nameof(value));
}
Array.Resize(ref _buffer, value);
if (WrittenCount > value)
{
WrittenCount = value;
}
}
}
/// <summary>
/// Clears the data written to the underlying buffer.
/// </summary>
/// <remarks>
/// You must reset the <see cref="BufferWriter{T}"/> before trying to re-use it.
/// If <typeparamref name="T"/> is or contains reference types, than this method
/// will clear the underlying memory buffers to default(T) in order to ensure the
/// GC is able to reclaim references correctly. If <typeparamref name="T"/>
/// is a value type and only contains value types, then this method doesn't
/// clear memory and so completes considerably faster.
/// </remarks>
public void Reset()
{
#if NETSTANDARD2_0
_buffer.AsSpan(0, WrittenCount).Clear();
#else
if (RuntimeHelpers.IsReferenceOrContainsReferences<T>())
{
_buffer.AsSpan(0, WrittenCount).Clear();
}
#endif
WrittenCount = 0;
}
/// <summary>
/// Notifies <see cref="IBufferWriter{T}"/> that <paramref name="count"/> amount of data was written to the output <see cref="Span{T}"/>/<see cref="Memory{T}"/>.
/// </summary>
/// <param name="count">The amount of data that has been consumed.</param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="count"/> is negative or when attempting to advance past the end of the underlying buffer.
/// </exception>
/// <remarks>
/// You must request a new buffer after calling <see cref="Advance"/> to continue writing more data and cannot write to a previously acquired buffer.
/// </remarks>
public void Advance(int count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
if (count > _buffer.Length - WrittenCount)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
WrittenCount += count;
}
/// <summary>
/// Returns a <see cref="Memory{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
/// </summary>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="sizeHint"/> is negative.
/// </exception>
/// <remarks>
/// This will never return an empty <see cref="Memory{T}"/>.
/// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
/// You must request a new buffer after calling <see cref="Advance"/> to continue writing more data and cannot write to a previously acquired buffer.
/// </remarks>
/// <param name="sizeHint">THe minimum size of the returned buffer. If this is 0, returns a buffer with at least 1 byte available.</param>
/// <returns>A block of memory that can be written to.</returns>
public Memory<T> GetMemory(int sizeHint = 0)
{
EnsureCapacity(sizeHint);
return _buffer.AsMemory(WrittenCount);
}
/// <summary>
/// Returns a <see cref="Span{T}"/> to write to that is at least the requested length (specified by <paramref name="sizeHint"/>).
/// </summary>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown when <paramref name="sizeHint"/> is negative.
/// </exception>
/// <remarks>
/// This will never return an empty <see cref="Span{T}"/>.
/// There is no guarantee that successive calls will return the same buffer or the same-sized buffer.
/// You must request a new buffer after calling <see cref="Advance"/> to continue writing more data and cannot write to a previously acquired buffer.
/// </remarks>
/// <param name="sizeHint">THe minimum size of the returned buffer. If this is 0, returns a buffer with at least 1 byte available.</param>
/// <returns>A block of memory that can be written to.</returns>
public Span<T> GetSpan(int sizeHint = 0)
{
EnsureCapacity(sizeHint);
return _buffer.AsSpan(WrittenCount);
}
private void EnsureCapacity(int sizeHint)
{
if (sizeHint == 0)
{
sizeHint = 1;
}
var avail = _buffer.Length - WrittenCount;
if (sizeHint > avail)
{
var targetCapacity = _buffer.Length == 0 ? DefaultCapacity : _buffer.Length * 2;
if (targetCapacity - WrittenCount < sizeHint)
{
targetCapacity = WrittenCount + sizeHint;
}
if ((uint)targetCapacity > MaxArrayLength)
{
throw new InvalidOperationException("Exceeded array capacity");
}
Array.Resize(ref _buffer, targetCapacity);
}
else if (sizeHint < 0)
{
throw new ArgumentOutOfRangeException(nameof(sizeHint));
}
}
}
}