Skip to content

Commit

Permalink
refs #2,#4 add breaking change to readonly stream API, usage and perf…
Browse files Browse the repository at this point in the history
…ormance test
  • Loading branch information
itn3000 committed Jul 7, 2017
1 parent 43becd6 commit 4b0befc
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 41 deletions.
25 changes: 13 additions & 12 deletions PooledStream.Benchmark/PooledStream.Benchmark.csproj
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.10.8" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PooledStream\PooledStream.csproj" />
</ItemGroup>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.10.8" />
<PackageReference Include="CodeProject.ObjectPool" Version="3.1.1" />
<PackageReference Include="Microsoft.IO.RecyclableMemoryStream" Version="1.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\PooledStream\PooledStream.csproj" />
</ItemGroup>
</Project>
17 changes: 16 additions & 1 deletion PooledStream.Benchmark/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ namespace PooledStream.Benchmark
using System.Buffers;
using Microsoft.IO;
using System.IO;
using ObjectMemoryStream = CodeProject.ObjectPool.Specialized.MemoryStreamPool;
[MemoryDiagnoser]
public class StreamBenchmark
{
[Params(100, 1_000, 100_000)]
[Params(100, 1_000, 50_000)]
public int DataSize { get; set; }
[Params(10000)]
public int MaxLoop { get; set; }
Expand Down Expand Up @@ -52,12 +53,26 @@ public void RecyclableStreamTest()
}
}
}
[Benchmark]
public void ObjectPoolTest()
{
var pool = ObjectMemoryStream.Instance;
var data = new byte[DataSize];
for(int i = 0;i<MaxLoop;i++)
{
using(var stm = pool.GetObject())
{
stm.MemoryStream.Write(data, 0, data.Length);
}
}
}
}
class Program
{
static void Main(string[] args)
{
var reporter = BenchmarkRunner.Run<StreamBenchmark>();
reporter = BenchmarkRunner.Run<StreamPrallelBenchmark>();
}
}
}
88 changes: 88 additions & 0 deletions PooledStream.Benchmark/StreamParallelBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
namespace PooledStream.Benchmark
{
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
using BenchmarkDotNet.Diagnosers;
using System.Buffers;
using Microsoft.IO;
using System.IO;
using System.Threading.Tasks;
using System.Threading;
using System.Linq;
using ObjectMemoryStream = CodeProject.ObjectPool.Specialized.MemoryStreamPool;
[MemoryDiagnoser]
public class StreamPrallelBenchmark
{
[Params(5, 10)]
public int ParallelNum{get;set;}
[Params(1000)]
public int DataSize{get;set;}
[Params(10000)]
public int MaxLoop{get;set;}
[Benchmark(Baseline=true)]
public void NormalStreamParallel()
{
var data = new byte[DataSize];
Task.WhenAll(Enumerable.Range(0, ParallelNum)
.Select(async (idx) => {
for(int i = 0;i<MaxLoop/ParallelNum;i++)
{
using(var stm = new MemoryStream(DataSize))
{
await stm.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
}
}
return idx;
})).Wait();
}
[Benchmark]
public void PooledStreamParallel()
{
var data = new byte[DataSize];
Task.WhenAll(Enumerable.Range(0, ParallelNum)
.Select(async (idx) => {
for(int i = 0;i<MaxLoop/ParallelNum;i++)
{
using(var stm = new PooledMemoryStream(ArrayPool<byte>.Shared, data.Length))
{
await stm.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
}
}
return idx;
})).Wait();
}
[Benchmark]
public void ObjectPoolParallel()
{
var data = new byte[DataSize];
var pool = ObjectMemoryStream.Instance;
Task.WhenAll(Enumerable.Range(0, ParallelNum)
.Select(async (idx) => {
for(int i = 0;i<MaxLoop;i++)
{
using(var stm = pool.GetObject())
{
await stm.MemoryStream.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
}
}
return idx;
})).Wait();
}
[Benchmark]
public void RecyclableStreamParallel()
{
var data = new byte[DataSize];
var manager = new RecyclableMemoryStreamManager();
Task.WhenAll(Enumerable.Range(0, ParallelNum)
.Select(async (idx) => {
for(int i = 0;i<MaxLoop;i++)
{
using(var stm = manager.GetStream())
{
await stm.WriteAsync(data, 0, data.Length).ConfigureAwait(false);
}
}
})).Wait();
}
}
}
2 changes: 1 addition & 1 deletion PooledStream.Test/TestPooledMemoryStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ public void TestWriteMiddle()
public void TestReadOnly()
{
var data = new byte[] { 1, 2, 3, 4 };
using (var stm = new PooledMemoryStream(ArrayPool<byte>.Shared, data))
using (var stm = new PooledMemoryStream(data))
{
Assert.True(stm.CanRead);
Assert.False(stm.CanWrite);
Expand Down
22 changes: 8 additions & 14 deletions PooledStream/PooledMemoryStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,18 @@ public PooledMemoryStream(ArrayPool<byte> pool, int capacity)
{
m_Pool = pool;
_currentbuffer = m_Pool.Rent(capacity);
_FromPool = true;
_Length = 0;
_CanWrite = true;
_Position = 0;
}
/// <summary>create readonly MemoryStream without buffer copy</summary>
/// <remarks>data will be read from 'data' parameter</summary>
public PooledMemoryStream(ArrayPool<byte> pool, byte[] data)
public PooledMemoryStream(byte[] data)
{
m_Pool = pool;
m_Pool = null;
_currentbuffer = data;
_Length = data.Length;
_CanWrite = false;
_FromPool = false;
}
public override bool CanRead
{
Expand Down Expand Up @@ -143,10 +142,6 @@ public override void Write(byte[] buffer, int offset, int count)
throw new InvalidOperationException("stream is readonly");
}
long endOffset = _Position + count;
if (endOffset > int.MaxValue)
{
throw new IndexOutOfRangeException("overflow");
}
if (endOffset > _currentbuffer.Length)
{
ReallocateBuffer((int)(endOffset) * 2);
Expand All @@ -163,7 +158,7 @@ public override void Write(byte[] buffer, int offset, int count)
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
if (_currentbuffer != null && _FromPool)
if (m_Pool != null && _currentbuffer != null)
{
m_Pool.Return(_currentbuffer);
_currentbuffer = null;
Expand Down Expand Up @@ -193,10 +188,9 @@ public ArraySegment<byte> ToUnsafeArraySegment()
return new ArraySegment<byte>(_currentbuffer, 0, (int)_Length);
}
ArrayPool<byte> m_Pool;
byte[] _currentbuffer = null;
bool _CanWrite = false;
long _Length = 0;
long _Position = 0;
bool _FromPool = false;
byte[] _currentbuffer;
bool _CanWrite;
long _Length;
long _Position;
}
}
7 changes: 5 additions & 2 deletions PooledStream/PooledStream.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
<PropertyGroup>
<TargetFramework>netstandard1.1</TargetFramework>
<PackageId>PooledStream</PackageId>
<PackageVersion>0.0.1</PackageVersion>
<PackageVersion>0.1.0</PackageVersion>
<Authors>itn3000</Authors>
<Description>Efficient MemoryStream powered by System.Buffers.ArrayPool</Description>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageReleaseNotes>First release</PackageReleaseNotes>
<PackageReleaseNotes>breaking change to readonly-constructor</PackageReleaseNotes>
<Copyright>Copyright(c) 2017 itn3000. All rights reserved</Copyright>
<PackageTags>MemoryStream</PackageTags>
<ProjectUrl>https://github.com/itn3000/PooledStream/</ProjectUrl>
<LicenseUrl>https://opensource.org/licenses/MIT</LicenseUrl>
<RepositoryUrl>https://github.com/itn3000/PooledStream/</RepositoryUrl>
</PropertyGroup>
<ItemGroup>
</ItemGroup>
Expand Down
97 changes: 86 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,87 @@
# Overview

[![NuGet version](https://badge.fury.io/nu/PooledStream.svg)](https://badge.fury.io/nu/PooledStream)

[![Build status](https://ci.appveyor.com/api/projects/status/yhm2jto1ed0q0x0y/branch/master?svg=true)](https://ci.appveyor.com/project/itn3000/pooledstream-kviqp/branch/master)

this project is library which aims to efficient MemoryStream for large data.

**STILL UNDER DEVELOPMENT**

# Usage

You can add reference as [NuGet package](https://www.nuget.org/packages/PooledStream/).
Once you add the reference, you can use PooledStream.PooledMemoryStream.

## Code Examples

### Basic usage

```csharp
// you can create stream with no parameter(using default setting).
// you can specify ArrayPool instance and initial capacity.
using(var stm = new PooledStream.PooledMemoryStream())
{
// you can ensure internal buffer length before write to stream.
stm.Reserve(1024);
var wdata = new byte[128];
// write stream
stm.Write(wdata, 0, wdata.Length);
// you can get written data by ToArray()
var data = stm.ToArray();
// or you can get ArraySegment without allocate new data.
// WARNING: you must not use the returned ArraySegment after dispose Stream
var segment = stm.ToUnsafeArraySegment();
}
```

### Readonly stream

```csharp
var data = new byte[128];
// if you specify bytearray to constructor parameter,
// PooledMemoryStream will be created as readonly stream.
using(var stm = new PooledStream.PooledMemoryStream(data))
{
var buf = new byte[32];
stm.Read(buf, 0, buf.Length);
// if you try to write, InvalidOperationException will be thrown
stm.Write(buf, 0, buf.Length);
}
```

# Micro benchmark result(powered by [BenchmarkDotNet](http://benchmarkdotnet.org/))

## Comparison of single thread performance

``` ini

BenchmarkDotNet=v0.10.8, OS=Windows 8.1 (6.3.9600)
Processor=Intel Core i7-4770 CPU 3.40GHz (Haswell), ProcessorCount=8
Frequency=3312643 Hz, Resolution=301.8738 ns, Timer=TSC
dotnet cli version=2.0.0-preview2-006497
[Host] : .NET Core 4.6.00001.0, 64bit RyuJIT
DefaultJob : .NET Core 4.6.00001.0, 64bit RyuJIT


```
| Method | DataSize | MaxLoop | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------------- |--------- |-------- |------------:|-----------:|-----------:|-------:|---------:|------------:|----------:|--------:|------------:|
| **NormalStreamTest** | **100** | **10000** | **541.5 us** | **2.272 us** | **2.126 us** | **1.00** | **0.00** | **838.8672** | **-** | **-** | **3437.63 KB** |
| PooledStreamBench | 100 | 10000 | 612.1 us | 10.134 us | 10.407 us | 1.13 | 0.02 | 170.8984 | - | - | 703.25 KB |
| RecyclableStreamTest | 100 | 10000 | 21,536.6 us | 276.913 us | 259.025 us | 39.77 | 0.49 | 1062.5000 | 31.2500 | 31.2500 | 4510.44 KB |
| ObjectPoolTest | 100 | 10000 | 1,198.0 us | 18.856 us | 17.637 us | 2.21 | 0.03 | 152.3438 | - | - | 625.13 KB |
| **NormalStreamTest** | **1000** | **10000** | **1,147.0 us** | **7.268 us** | **6.443 us** | **1.00** | **0.00** | **2611.3281** | **-** | **-** | **10704.13 KB** |
| PooledStreamBench | 1000 | 10000 | 839.7 us | 11.962 us | 11.190 us | 0.73 | 0.01 | 171.8750 | - | - | 704.13 KB |
| RecyclableStreamTest | 1000 | 10000 | 21,669.4 us | 286.048 us | 267.570 us | 18.89 | 0.25 | 1062.5000 | 31.2500 | 31.2500 | 4511.31 KB |
| ObjectPoolTest | 1000 | 10000 | 1,318.9 us | 3.252 us | 2.539 us | 1.15 | 0.01 | 152.3438 | - | - | 626 KB |
| **NormalStreamTest** | **50000** | **10000** | **43,009.7 us** | **147.280 us** | **137.766 us** | **1.00** | **0.00** | **119000.0000** | **3125.0000** | **-** | **489267.6 KB** |
| PooledStreamBench | 50000 | 10000 | 19,792.9 us | 230.006 us | 215.148 us | 0.46 | 0.01 | 156.2500 | - | - | 751.98 KB |
| RecyclableStreamTest | 50000 | 10000 | 40,295.7 us | 242.423 us | 214.902 us | 0.94 | 0.01 | 1062.5000 | - | - | 4559.16 KB |
| ObjectPoolTest | 50000 | 10000 | 19,891.6 us | 176.917 us | 165.489 us | 0.46 | 0.00 | 156.2500 | - | - | 673.85 KB |

## Comparison of multithreaded performance

``` ini

BenchmarkDotNet=v0.10.8, OS=Windows 8.1 (6.3.9600)
Expand All @@ -17,14 +93,13 @@ dotnet cli version=2.0.0-preview2-006497


```
| Method | DataSize | MaxLoop | Mean | Error | StdDev | Median | Scaled | ScaledSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|--------------------- |--------- |-------- |-------------:|-------------:|-------------:|-------------:|-------:|---------:|------------:|------------:|------------:|-------------:|
| **NormalStreamTest** | **100** | **10000** | **530.9 us** | **3.066 us** | **2.560 us** | **531.0 us** | **1.00** | **0.00** | **838.8672** | **-** | **-** | **3437.63 KB** |
| PooledStreamBench | 100 | 10000 | 657.3 us | 9.856 us | 8.737 us | 654.9 us | 1.24 | 0.02 | 170.8984 | - | - | 703.25 KB |
| RecyclableStreamTest | 100 | 10000 | 21,652.7 us | 430.904 us | 798.709 us | 21,225.3 us | 40.79 | 1.50 | 1062.5000 | 31.2500 | 31.2500 | 4510.44 KB |
| **NormalStreamTest** | **1000** | **10000** | **1,167.9 us** | **23.153 us** | **22.739 us** | **1,160.4 us** | **1.00** | **0.00** | **2611.3281** | **-** | **-** | **10704.13 KB** |
| PooledStreamBench | 1000 | 10000 | 854.8 us | 4.704 us | 4.400 us | 855.3 us | 0.73 | 0.01 | 171.8750 | - | - | 704.13 KB |
| RecyclableStreamTest | 1000 | 10000 | 21,670.9 us | 222.349 us | 185.672 us | 21,621.6 us | 18.56 | 0.38 | 1062.5000 | 31.2500 | 31.2500 | 4511.31 KB |
| **NormalStreamTest** | **100000** | **10000** | **192,641.2 us** | **3,706.515 us** | **3,640.293 us** | **193,404.1 us** | **1.00** | **0.00** | **312500.0000** | **312500.0000** | **312500.0000** | **977597.68 KB** |
| PooledStreamBench | 100000 | 10000 | 37,751.8 us | 233.532 us | 218.446 us | 37,740.3 us | 0.20 | 0.00 | 125.0000 | - | - | 800.8 KB |
| RecyclableStreamTest | 100000 | 10000 | 58,598.4 us | 1,116.200 us | 932.077 us | 58,607.9 us | 0.30 | 0.01 | 1062.5000 | 62.5000 | 62.5000 | 4607.99 KB |
| Method | ParallelNum | DataSize | MaxLoop | Mean | Error | StdDev | Scaled | ScaledSD | Gen 0 | Allocated |
|------------------------- |------------ |--------- |-------- |-----------:|----------:|----------:|-------:|---------:|-----------:|------------:|
| **NormalStreamParallel** | **5** | **1000** | **10000** | **1.183 ms** | **0.0091 ms** | **0.0081 ms** | **1.00** | **0.00** | **2611.3281** | **10704.69 KB** |
| PooledStreamParallel | 5 | 1000 | 10000 | 3.695 ms | 0.0370 ms | 0.0328 ms | 3.12 | 0.03 | 652.3438 | 4.12 KB |
| ObjectPoolParallel | 5 | 1000 | 10000 | 7.776 ms | 0.1162 ms | 0.1030 ms | 6.57 | 0.09 | 757.8125 | 3126.57 KB |
| RecyclableStreamParallel | 5 | 1000 | 10000 | 108.640 ms | 1.6470 ms | 1.5406 ms | 91.85 | 1.39 | 5312.5000 | 22011.83 KB |
| **NormalStreamParallel** | **10** | **1000** | **10000** | **1.186 ms** | **0.0059 ms** | **0.0052 ms** | **1.00** | **0.00** | **2611.3281** | **10704.96 KB** |
| PooledStreamParallel | 10 | 1000 | 10000 | 3.317 ms | 0.0088 ms | 0.0082 ms | 2.80 | 0.01 | 652.3438 | 6.86 KB |
| ObjectPoolParallel | 10 | 1000 | 10000 | 15.258 ms | 0.0872 ms | 0.0728 ms | 12.86 | 0.08 | 1515.6250 | 6251.84 KB |
| RecyclableStreamParallel | 10 | 1000 | 10000 | 215.123 ms | 1.0908 ms | 0.8517 ms | 181.33 | 1.03 | 10625.0000 | 43887.02 KB |

0 comments on commit 4b0befc

Please sign in to comment.