Skip to content

Commit f2e8a45

Browse files
committed
performance tuning commit(WIP)
1 parent 490dcd8 commit f2e8a45

File tree

10 files changed

+316
-109
lines changed

10 files changed

+316
-109
lines changed

.config/dotnet-tools.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"isRoot": true,
44
"tools": {
55
"cake.tool": {
6-
"version": "0.32.1",
6+
"version": "0.35.0",
77
"commands": [
88
"dotnet-cake"
99
]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
using System;
2+
using System.IO;
3+
using BenchmarkDotNet.Attributes;
4+
using Microsoft.Extensions.ObjectPool;
5+
using System.Buffers;
6+
using System.Linq;
7+
8+
namespace PooledStream.Benchmark
9+
{
10+
[Config(typeof(MultiPlatformConfig))]
11+
[MemoryDiagnoser]
12+
[DisassemblyDiagnoser(printAsm: true, printIL: true, printSource: true)]
13+
public class ObjectPoolStreamBench
14+
{
15+
[Params(10000)]
16+
public int DataLength;
17+
[Params(1000)]
18+
public int LoopNum;
19+
[Benchmark(Baseline = true)]
20+
public void Normal()
21+
{
22+
var data = new byte[DataLength];
23+
for (int i = 0; i < LoopNum; i++)
24+
{
25+
using (var mstm = new PooledMemoryStream(ArrayPool<byte>.Shared, DataLength))
26+
{
27+
#if NETCOREAPP3_0
28+
mstm.Write(data.AsSpan());
29+
#else
30+
mstm.Write(data, 0, data.Length);
31+
#endif
32+
}
33+
}
34+
}
35+
readonly ObjectPool<PooledMemoryStream> _Pool = ObjectPool.Create<PooledMemoryStream>();
36+
[Benchmark]
37+
public void Pooled()
38+
{
39+
var data = new byte[DataLength];
40+
for (int i = 0; i < LoopNum; i++)
41+
{
42+
var mstm = _Pool.Get();
43+
{
44+
// mstm.Reserve(DataLength);
45+
mstm.SetLength(0);
46+
// #if NETCOREAPP3_0
47+
// mstm.Write(data.AsSpan());
48+
// #else
49+
mstm.Write(data, 0, data.Length);
50+
// #endif
51+
_Pool.Return(mstm);
52+
}
53+
}
54+
}
55+
}
56+
57+
}

PooledStream.Benchmark/PooledStream.Benchmark.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
33
<OutputType>Exe</OutputType>
4-
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0</TargetFrameworks>
4+
<TargetFrameworks>netcoreapp3.0;netcoreapp2.1</TargetFrameworks>
55
<PlatformTarget>AnyCPU</PlatformTarget>
66
<DebugType>pdbonly</DebugType>
77
<DebugSymbols>true</DebugSymbols>
@@ -11,6 +11,7 @@
1111
<PackageReference Include="BenchmarkDotNet" Version="0.12.0" />
1212
<PackageReference Include="CodeProject.ObjectPool" Version="3.2.2" />
1313
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.2.2" />
14+
<PackageReference Include="Microsoft.Extensions.ObjectPool" Version="3.0.0"/>
1415
</ItemGroup>
1516
<ItemGroup>
1617
<ProjectReference Include="..\PooledStream\PooledStream.csproj" />

PooledStream.Benchmark/Program.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,14 @@ public MultiPlatformConfig()
8181
.With(CsProjCoreToolchain.NetCoreApp21));
8282
Add(Job.Default.WithWarmupCount(3).WithIterationCount(3)
8383
.With(CsProjCoreToolchain.NetCoreApp30));
84+
this.Options |= ConfigOptions.DisableOptimizationsValidator;
8485
}
8586
}
8687
class Program
8788
{
8889
static void Main(string[] args)
8990
{
90-
var switcher = new BenchmarkSwitcher(new Type[]
91-
{
92-
typeof(StreamBenchmark),
93-
typeof(StreamPrallelBenchmark)
94-
}
95-
);
91+
var switcher = new BenchmarkSwitcher(typeof(Program).Assembly);
9692
switcher.Run();
9793
}
9894
}

PooledStream.Test/TestPooledMemoryStream.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,5 +72,37 @@ public void TestSetLength()
7272
Assert.Equal(data, ar.Take(data.Length));
7373
}
7474
}
75+
[Fact]
76+
public void TestDispose()
77+
{
78+
var stm = new PooledMemoryStream();
79+
stm.Write(new byte[]{ 1, 2, 3, 4 }, 0, 4);
80+
stm.Dispose();
81+
stm.Write(new byte[]{ 1, 2, 3, 4 }, 0, 4);
82+
stm.Dispose();
83+
}
84+
[Fact]
85+
public void TestToMemory()
86+
{
87+
var data = new byte[] { 1, 2, 3, 4 };
88+
using(var stm = new PooledMemoryStream(data))
89+
{
90+
var mem = stm.ToMemoryUnsafe();
91+
Assert.Equal(data, mem);
92+
var sp = stm.ToSpanUnsafe();
93+
Assert.True(sp.SequenceEqual(data));
94+
}
95+
}
96+
[Fact]
97+
public void TestShrink()
98+
{
99+
var data = new byte[] { 1, 2, 3, 4 };
100+
using(var stm = new PooledMemoryStream())
101+
{
102+
stm.Write(data, 0, data.Length);
103+
stm.Shrink(2);
104+
Assert.Equal(data.AsSpan(0, 2).ToArray(), stm.ToArray());
105+
}
106+
}
75107
}
76108
}

PooledStream/PooledMemoryStream.cs

Lines changed: 118 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ namespace PooledStream
33
using System;
44
using System.IO;
55
using System.Buffers;
6-
public partial class PooledMemoryStream : Stream
6+
using System.Runtime.CompilerServices;
7+
public sealed partial class PooledMemoryStream : Stream
78
{
89
/// <summary>create writable memory stream with default parameters</summary>
910
/// <remarks>buffer is allocated from ArrayPool.Shared</remarks>
@@ -63,17 +64,25 @@ public override void Flush()
6364
{
6465
}
6566

67+
[MethodImpl(MethodImplOptions.AggressiveInlining)]
6668
void ReallocateBuffer(int minimumRequired)
6769
{
6870
var tmp = m_Pool.Rent(minimumRequired);
69-
#if NETSTANDARD2_1
70-
_currentbuffer.AsSpan().CopyTo(tmp);
71-
#else
72-
Buffer.BlockCopy(_currentbuffer, 0, tmp, 0, _currentbuffer.Length);
73-
#endif
74-
m_Pool.Return(_currentbuffer);
71+
if (_currentbuffer != null)
72+
{
73+
// #if NETSTANDARD2_1
74+
// _currentbuffer.AsSpan().CopyTo(tmp);
75+
// #else
76+
// Buffer.BlockCopy(_currentbuffer, 0, tmp, 0, _currentbuffer.Length);
77+
// #endif
78+
Buffer.BlockCopy(_currentbuffer, 0, tmp, 0, _currentbuffer.Length < tmp.Length ? _currentbuffer.Length : tmp.Length);
79+
m_Pool.Return(_currentbuffer);
80+
}
7581
_currentbuffer = tmp;
7682
}
83+
/// <summary>set stream length</summary>
84+
/// <remarks>if length is larger than current buffer length, re-allocating buffer</remarks>
85+
/// <exception cref="System.InvalidOperationException">if stream is readonly</exception>
7786
public override void SetLength(long value)
7887
{
7988
if (!_CanWrite)
@@ -89,10 +98,21 @@ public override void SetLength(long value)
8998
throw new IndexOutOfRangeException("underflow");
9099
}
91100
_Length = (int)value;
92-
if (_currentbuffer.Length < _Length)
101+
if (_currentbuffer == null || _currentbuffer.Length < _Length)
93102
{
94103
ReallocateBuffer((int)_Length);
95104
}
105+
if (_Position >= _Length)
106+
{
107+
if (_Length == 0)
108+
{
109+
_Position = 0;
110+
}
111+
else
112+
{
113+
_Position = _Length - 1;
114+
}
115+
}
96116
}
97117
protected override void Dispose(bool disposing)
98118
{
@@ -102,11 +122,18 @@ protected override void Dispose(bool disposing)
102122
m_Pool.Return(_currentbuffer);
103123
_currentbuffer = null;
104124
}
125+
_Length = 0;
126+
_Position = 0;
105127
}
106128
/// <summary>ensure the buffer size</summary>
107129
/// <remarks>capacity != stream buffer length</remarks>
130+
/// <exception cref="System.InvalidOperationException">if stream is readonly</exception>
108131
public void Reserve(int capacity)
109132
{
133+
if (!_CanWrite)
134+
{
135+
throw new InvalidOperationException("stream is readonly");
136+
}
110137
if (capacity > _currentbuffer.Length)
111138
{
112139
ReallocateBuffer(capacity);
@@ -155,5 +182,88 @@ public override long Seek(long offset, SeekOrigin origin)
155182
bool _CanWrite;
156183
int _Length;
157184
int _Position;
185+
public override int Read(byte[] buffer, int offset, int count)
186+
{
187+
int readlen = count > (int)(_Length - _Position) ? (int)(_Length - _Position) : count;
188+
if (readlen > 0)
189+
{
190+
Buffer.BlockCopy(_currentbuffer
191+
, (int)_Position
192+
, buffer, offset
193+
, readlen)
194+
;
195+
_Position += readlen;
196+
return readlen;
197+
}
198+
else
199+
{
200+
return 0;
201+
}
202+
}
203+
204+
/// <summary>write data to stream</summary>
205+
/// <remarks>if stream data length is over int.MaxValue, this method throws IndexOutOfRangeException</remarks>
206+
/// <exception cref="System.InvalidOperationException">if stream is readonly</exception>
207+
public override void Write(byte[] buffer, int offset, int count)
208+
{
209+
if (!_CanWrite)
210+
{
211+
throw new InvalidOperationException("stream is readonly");
212+
}
213+
int endOffset = _Position + count;
214+
if (_currentbuffer == null || endOffset > _currentbuffer.Length)
215+
{
216+
ReallocateBuffer((int)(endOffset) * 2);
217+
}
218+
Buffer.BlockCopy(buffer, offset,
219+
_currentbuffer, (int)_Position, count);
220+
if (endOffset > _Length)
221+
{
222+
_Length = endOffset;
223+
}
224+
_Position = endOffset;
225+
}
226+
/// <summary>shrink internal buffer by re-allocating memory</summary>
227+
/// <remarks>if internal buffer is shorter than minimumRequired, nothing to do</remarks>
228+
/// <exception cref="System.InvalidOperationException">if stream is readonly</exception>
229+
public void Shrink(int minimumRequired)
230+
{
231+
if (!_CanWrite)
232+
{
233+
throw new InvalidOperationException("stream is readonly");
234+
}
235+
if (_currentbuffer == null)
236+
{
237+
return;
238+
}
239+
if(_currentbuffer.Length > minimumRequired)
240+
{
241+
ReallocateBuffer(minimumRequired);
242+
}
243+
if (minimumRequired <= _Length)
244+
{
245+
_Length = minimumRequired;
246+
}
247+
}
248+
/// <summary>get internal data as Span</summary>
249+
/// <remarks>you must not use returned value outside of stream's lifetime</remarks>
250+
public ReadOnlySpan<byte> ToSpanUnsafe()
251+
{
252+
if (_currentbuffer == null || _Length <= 0)
253+
{
254+
return Span<byte>.Empty;
255+
}
256+
return _currentbuffer.AsSpan(0, _Length);
257+
}
258+
/// <summary>get internal data as Memory</summary>
259+
/// <remarks>you must not use returned value outside of stream's lifetime</remarks>
260+
public ReadOnlyMemory<byte> ToMemoryUnsafe()
261+
{
262+
if (_currentbuffer == null || _Length <= 0)
263+
{
264+
return Memory<byte>.Empty;
265+
}
266+
return _currentbuffer.AsMemory(0, _Length);
267+
}
158268
}
159269
}

PooledStream/PooledMemoryStream.netstd11.cs

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -5,46 +5,6 @@ namespace PooledStream
55
using System.Buffers;
66
public partial class PooledMemoryStream : Stream
77
{
8-
public override int Read(byte[] buffer, int offset, int count)
9-
{
10-
int readlen = count > (int)(_Length - _Position) ? (int)(_Length - _Position) : count;
11-
if (readlen > 0)
12-
{
13-
Buffer.BlockCopy(_currentbuffer
14-
, (int)_Position
15-
, buffer, offset
16-
, readlen)
17-
;
18-
_Position += readlen;
19-
return readlen;
20-
}
21-
else
22-
{
23-
return 0;
24-
}
25-
}
26-
27-
/// <summary>write data to stream</summary>
28-
/// <remarks>if stream data length is over int.MaxValue, this method throws IndexOutOfRangeException</remarks>
29-
public override void Write(byte[] buffer, int offset, int count)
30-
{
31-
if (!_CanWrite)
32-
{
33-
throw new InvalidOperationException("stream is readonly");
34-
}
35-
int endOffset = _Position + count;
36-
if (endOffset > _currentbuffer.Length)
37-
{
38-
ReallocateBuffer((int)(endOffset) * 2);
39-
}
40-
Buffer.BlockCopy(buffer, offset,
41-
_currentbuffer, (int)_Position, count);
42-
if (endOffset > _Length)
43-
{
44-
_Length = endOffset;
45-
}
46-
_Position = endOffset;
47-
}
488

499
}
5010
}

0 commit comments

Comments
 (0)