Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Pickling performance #67

Merged
merged 14 commits into from
May 15, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions dotnet/Razorvine.Pyrolite/Benchmarks/PicklerBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using BenchmarkDotNet.Attributes;
using Razorvine.Pickle;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

namespace Benchmarks
{
[GenericTypeArguments(typeof(string))]
[GenericTypeArguments(typeof(double))]
[GenericTypeArguments(typeof(int))]
[GenericTypeArguments(typeof(bool))]
public class PicklerBenchmarks<T>
{
[Params(1000)]
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
public int Count { get; set; }

[Params(false, true)]
public bool UseMemo { get; set; }
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved

[Params(false, true)]
public bool Boxed { get; set; }

private object _value;
private Pickler _pickler;
private byte[] _reusable;

[GlobalSetup]
public void Setup()
{
IEnumerable<T> input = Enumerable.Range(0, Count).Select(GetValue);

if (Boxed) // a common case in dotnet/spark
_value = input.Select(x => (object)x).ToArray();
else
_value = input.ToArray();

_reusable = new byte[Count * 10];
_pickler = new Pickler(UseMemo);
}

[GlobalCleanup]
public void Cleanup() => _pickler.Dispose();

[Benchmark]
public byte[] ToByteArray() => _pickler.dumps(_value);

[Benchmark]
public void ToReusableByteArray() => _pickler.dumps(_value, ref _reusable, out _);

[Benchmark]
public void ToStream() => _pickler.dump(_value, new MemoryStream());

private static T GetValue(int index)
{
if (typeof(T) == typeof(int))
return (T)(object)index;
if (typeof(T) == typeof(double))
return (T)(object)(index * 0.5);
if (typeof(T) == typeof(bool))
return (T)(object)(index % 2 == 0);
if (typeof(T) == typeof(string))
return (T)(object)index.ToString();

throw new NotSupportedException();
}
}
}
2 changes: 2 additions & 0 deletions dotnet/Razorvine.Pyrolite/Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Exporters.Json;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;

Expand All @@ -12,6 +13,7 @@ static void Main(string[] args)
.FromAssembly(typeof(Program).Assembly)
.Run(args, DefaultConfig.Instance
.With(MemoryDiagnoser.Default)
.With(JsonExporter.Full)
.With(Job.ShortRun.AsDefault()));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
/* part of Pyrolite, by Irmen de Jong (irmen@razorvine.net) */

using System;

namespace Razorvine.Pickle
{
Expand All @@ -13,10 +15,7 @@ public ArrayReader(byte[] bytes)
position = 0;
}

public byte ReadByte()
{
return input[position++];
}
public byte ReadByte() => input[position++];

public ReadOnlySpan<byte> ReadBytes(int bytesCount)
{
Expand All @@ -25,10 +24,7 @@ public ReadOnlySpan<byte> ReadBytes(int bytesCount)
return result;
}

public string ReadLine(bool includeLF = false)
{
return PickleUtils.rawStringFromBytes(ReadLineBytes(includeLF));
}
public string ReadLine(bool includeLF = false) => PickleUtils.rawStringFromBytes(ReadLineBytes(includeLF));

public ReadOnlySpan<byte> ReadLineBytes(bool includeLF = false)
{
Expand All @@ -38,10 +34,7 @@ public ReadOnlySpan<byte> ReadLineBytes(bool includeLF = false)
return result;
}

public void Skip(int bytesCount)
{
position += bytesCount;
}
public void Skip(int bytesCount) => position += bytesCount;

private int GetLineEndIndex(bool includeLF = false)
{
Expand Down
105 changes: 105 additions & 0 deletions dotnet/Razorvine.Pyrolite/Pyrolite/Pickle/Internals/ArrayWriter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
/* part of Pyrolite, by Irmen de Jong (irmen@razorvine.net) */

using System;
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Text;

namespace Razorvine.Pickle
{
internal struct ArrayWriter : IOutputWriter
{
private byte[] output;
private int position;

public ArrayWriter(byte[] output)
{
this.output = output;
position = 0;
}

public void Dispose() => output = null;

public int BytesWritten => position;

public byte[] Buffer => output;

public void WriteByte(byte value)
{
EnsureSize(1);
output[position++] = value;
}

// this method exists so we don't have to call EnsureSize(1) twice
public void WriteBytes(byte first, byte second)
{
EnsureSize(2);
output[position++] = first;
output[position++] = second;
}

// this method exists so we don't have to call EnsureSize(1) three times
public void WriteBytes(byte first, byte second, byte third)
{
EnsureSize(3);
output[position++] = first;
output[position++] = second;
output[position++] = third;
}

public void Write(byte[] buffer, int offset, int count)
{
EnsureSize(count);
buffer.AsSpan(offset, count).CopyTo(output.AsSpan(position, count));
position += count;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)] // crucial for the performance, do NOT remove
public void WriteInt32LittleEndian(int value)
{
EnsureSize(sizeof(int));
BinaryPrimitives.WriteInt32LittleEndian(output.AsSpan(position, sizeof(int)), value);
position += sizeof(int);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)] // crucial for the performance, do NOT remove
public void WriteInt64BigEndian(long value)
{
EnsureSize(sizeof(long));
BinaryPrimitives.WriteInt64BigEndian(output.AsSpan(position, sizeof(long)), value);
position += sizeof(long);
}

public void WriteAsUtf8String(string str)
{
int byteCount = Encoding.UTF8.GetByteCount(str);
WriteInt32LittleEndian(byteCount);

EnsureSize(byteCount);

unsafe
{
fixed (char* source = str)
fixed (byte* destination = output)
{
// this part is crucial for the performance: instead of allocating a new byte array
// the output is written to the existing buffer
Encoding.UTF8.GetBytes(source, str.Length, destination + position, byteCount);
}
}

position += byteCount;
}

public void Flush() { } // does nothing on purpose

[MethodImpl(MethodImplOptions.AggressiveInlining)] // crucial for the performance, do NOT remove
private void EnsureSize(int requested)
{
if (output.Length < position + requested)
{
Array.Resize(ref output, Math.Max(output.Length + requested, output.Length * 2));
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
/* part of Pyrolite, by Irmen de Jong (irmen@razorvine.net) */

using System;

namespace Razorvine.Pickle
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* part of Pyrolite, by Irmen de Jong (irmen@razorvine.net) */

using System;

namespace Razorvine.Pickle
{
internal interface IOutputWriter : IDisposable
{
int BytesWritten { get; }
byte[] Buffer { get; }

void WriteByte(byte value);
void WriteBytes(byte first, byte second);
void WriteBytes(byte first, byte second, byte third);

void Write(byte[] buffer, int offset, int count);

void WriteInt32LittleEndian(int value);
void WriteInt64BigEndian(long value);
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
void WriteAsUtf8String(string str);

void Flush();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* part of Pyrolite, by Irmen de Jong (irmen@razorvine.net) */

using System;

namespace Razorvine.Pickle
{
internal interface IPicklerImplementation : IDisposable
adamsitnik marked this conversation as resolved.
Show resolved Hide resolved
{
int BytesWritten { get; }
byte[] Buffer { get; }

void dump(object o);
void save(object o);
}
}
Loading