|
@@ -0,0 +1,414 @@
|
|
|
|
+#region Copyright notice and license
|
|
|
|
+// Protocol Buffers - Google's data interchange format
|
|
|
|
+// Copyright 2019 Google Inc. All rights reserved.
|
|
|
|
+// https://github.com/protocolbuffers/protobuf
|
|
|
|
+//
|
|
|
|
+// Redistribution and use in source and binary forms, with or without
|
|
|
|
+// modification, are permitted provided that the following conditions are
|
|
|
|
+// met:
|
|
|
|
+//
|
|
|
|
+// * Redistributions of source code must retain the above copyright
|
|
|
|
+// notice, this list of conditions and the following disclaimer.
|
|
|
|
+// * Redistributions in binary form must reproduce the above
|
|
|
|
+// copyright notice, this list of conditions and the following disclaimer
|
|
|
|
+// in the documentation and/or other materials provided with the
|
|
|
|
+// distribution.
|
|
|
|
+// * Neither the name of Google Inc. nor the names of its
|
|
|
|
+// contributors may be used to endorse or promote products derived from
|
|
|
|
+// this software without specific prior written permission.
|
|
|
|
+//
|
|
|
|
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
|
|
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
|
|
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
|
|
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
|
|
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
|
|
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
|
|
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
|
|
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
|
|
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
|
|
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
|
|
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
|
|
+#endregion
|
|
|
|
+
|
|
|
|
+using BenchmarkDotNet.Attributes;
|
|
|
|
+using System;
|
|
|
|
+using System.Buffers.Binary;
|
|
|
|
+using System.Collections.Generic;
|
|
|
|
+using System.IO;
|
|
|
|
+using System.Buffers;
|
|
|
|
+using System.Text;
|
|
|
|
+
|
|
|
|
+namespace Google.Protobuf.Benchmarks
|
|
|
|
+{
|
|
|
|
+ /// <summary>
|
|
|
|
+ /// Benchmarks throughput when writing raw primitives.
|
|
|
|
+ /// </summary>
|
|
|
|
+ [MemoryDiagnoser]
|
|
|
|
+ public class WriteRawPrimitivesBenchmark
|
|
|
|
+ {
|
|
|
|
+ // key is the encodedSize of varint values
|
|
|
|
+ Dictionary<int, uint[]> varint32Values;
|
|
|
|
+ Dictionary<int, ulong[]> varint64Values;
|
|
|
|
+
|
|
|
|
+ double[] doubleValues;
|
|
|
|
+ float[] floatValues;
|
|
|
|
+
|
|
|
|
+ // key is the encodedSize of string values
|
|
|
|
+ Dictionary<int, string[]> stringValues;
|
|
|
|
+
|
|
|
|
+ // key is the encodedSize of string values
|
|
|
|
+ Dictionary<int, ByteString[]> byteStringValues;
|
|
|
|
+
|
|
|
|
+ // the buffer to which all the data will be written
|
|
|
|
+ byte[] outputBuffer;
|
|
|
|
+
|
|
|
|
+ Random random = new Random(417384220); // random but deterministic seed
|
|
|
|
+
|
|
|
|
+ public IEnumerable<int> StringEncodedSizes => new[] { 1, 4, 10, 105, 10080 };
|
|
|
|
+
|
|
|
|
+ [GlobalSetup]
|
|
|
|
+ public void GlobalSetup()
|
|
|
|
+ {
|
|
|
|
+ outputBuffer = new byte[BytesToWrite];
|
|
|
|
+
|
|
|
|
+ varint32Values = new Dictionary<int, uint[]>();
|
|
|
|
+ varint64Values = new Dictionary<int, ulong[]>();
|
|
|
|
+ for (int encodedSize = 1; encodedSize <= 10; encodedSize++)
|
|
|
|
+ {
|
|
|
|
+ if (encodedSize <= 5)
|
|
|
|
+ {
|
|
|
|
+ varint32Values.Add(encodedSize, CreateRandomVarints32(random, BytesToWrite / encodedSize, encodedSize));
|
|
|
|
+ }
|
|
|
|
+ varint64Values.Add(encodedSize, CreateRandomVarints64(random, BytesToWrite / encodedSize, encodedSize));
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ doubleValues = CreateRandomDoubles(random, BytesToWrite / sizeof(double));
|
|
|
|
+ floatValues = CreateRandomFloats(random, BytesToWrite / sizeof(float));
|
|
|
|
+
|
|
|
|
+ stringValues = new Dictionary<int, string[]>();
|
|
|
|
+ byteStringValues = new Dictionary<int, ByteString[]>();
|
|
|
|
+ foreach(var encodedSize in StringEncodedSizes)
|
|
|
|
+ {
|
|
|
|
+ stringValues.Add(encodedSize, CreateStrings(BytesToWrite / encodedSize, encodedSize));
|
|
|
|
+ byteStringValues.Add(encodedSize, CreateByteStrings(BytesToWrite / encodedSize, encodedSize));
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // Total number of bytes that each benchmark will write.
|
|
|
|
+ // Measuring the time taken to write buffer of given size makes it easier to compare parsing speed for different
|
|
|
|
+ // types and makes it easy to calculate the througput (in MB/s)
|
|
|
|
+ // 10800 bytes is chosen because it is divisible by all possible encoded sizes for all primitive types {1..10}
|
|
|
|
+ [Params(10080)]
|
|
|
|
+ public int BytesToWrite { get; set; }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [Arguments(1)]
|
|
|
|
+ [Arguments(2)]
|
|
|
|
+ [Arguments(3)]
|
|
|
|
+ [Arguments(4)]
|
|
|
|
+ [Arguments(5)]
|
|
|
|
+ public void WriteRawVarint32_CodedOutputStream(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = varint32Values[encodedSize];
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteRawVarint32(value);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [Arguments(1)]
|
|
|
|
+ [Arguments(2)]
|
|
|
|
+ [Arguments(3)]
|
|
|
|
+ [Arguments(4)]
|
|
|
|
+ [Arguments(5)]
|
|
|
|
+ public void WriteRawVarint32_WriteContext(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = varint32Values[encodedSize];
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteUInt32(value);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [Arguments(1)]
|
|
|
|
+ [Arguments(2)]
|
|
|
|
+ [Arguments(3)]
|
|
|
|
+ [Arguments(4)]
|
|
|
|
+ [Arguments(5)]
|
|
|
|
+ [Arguments(6)]
|
|
|
|
+ [Arguments(7)]
|
|
|
|
+ [Arguments(8)]
|
|
|
|
+ [Arguments(9)]
|
|
|
|
+ [Arguments(10)]
|
|
|
|
+ public void WriteRawVarint64_CodedOutputStream(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = varint64Values[encodedSize];
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteRawVarint64(value);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [Arguments(1)]
|
|
|
|
+ [Arguments(2)]
|
|
|
|
+ [Arguments(3)]
|
|
|
|
+ [Arguments(4)]
|
|
|
|
+ [Arguments(5)]
|
|
|
|
+ [Arguments(6)]
|
|
|
|
+ [Arguments(7)]
|
|
|
|
+ [Arguments(8)]
|
|
|
|
+ [Arguments(9)]
|
|
|
|
+ [Arguments(10)]
|
|
|
|
+ public void WriteRawVarint64_WriteContext(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = varint64Values[encodedSize];
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteUInt64(value);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteFixed32_CodedOutputStream()
|
|
|
|
+ {
|
|
|
|
+ const int encodedSize = sizeof(uint);
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ for(int i = 0; i < BytesToWrite / encodedSize; i++)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteFixed32(12345);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteFixed32_WriteContext()
|
|
|
|
+ {
|
|
|
|
+ const int encodedSize = sizeof(uint);
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ for (uint i = 0; i < BytesToWrite / encodedSize; i++)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteFixed32(12345);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteFixed64_CodedOutputStream()
|
|
|
|
+ {
|
|
|
|
+ const int encodedSize = sizeof(ulong);
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ for(int i = 0; i < BytesToWrite / encodedSize; i++)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteFixed64(123456789);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteFixed64_WriteContext()
|
|
|
|
+ {
|
|
|
|
+ const int encodedSize = sizeof(uint);
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ for (uint i = 0; i < BytesToWrite / encodedSize; i++)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteFixed64(123456789);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteRawFloat_CodedOutputStream()
|
|
|
|
+ {
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ foreach (var value in floatValues)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteFloat(value);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteRawFloat_WriteContext()
|
|
|
|
+ {
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ foreach (var value in floatValues)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteFloat(value);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteRawDouble_CodedOutputStream()
|
|
|
|
+ {
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ foreach (var value in doubleValues)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteDouble(value);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ public void WriteRawDouble_WriteContext()
|
|
|
|
+ {
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ foreach (var value in doubleValues)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteDouble(value);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [ArgumentsSource(nameof(StringEncodedSizes))]
|
|
|
|
+ public void WriteString_CodedOutputStream(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = stringValues[encodedSize];
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteString(value);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [ArgumentsSource(nameof(StringEncodedSizes))]
|
|
|
|
+ public void WriteString_WriteContext(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = stringValues[encodedSize];
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteString(value);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [ArgumentsSource(nameof(StringEncodedSizes))]
|
|
|
|
+ public void WriteBytes_CodedOutputStream(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = byteStringValues[encodedSize];
|
|
|
|
+ var cos = new CodedOutputStream(outputBuffer);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ cos.WriteBytes(value);
|
|
|
|
+ }
|
|
|
|
+ cos.Flush();
|
|
|
|
+ cos.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ [Benchmark]
|
|
|
|
+ [ArgumentsSource(nameof(StringEncodedSizes))]
|
|
|
|
+ public void WriteBytes_WriteContext(int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var values = byteStringValues[encodedSize];
|
|
|
|
+ var span = new Span<byte>(outputBuffer);
|
|
|
|
+ WriteContext.Initialize(ref span, out WriteContext ctx);
|
|
|
|
+ foreach (var value in values)
|
|
|
|
+ {
|
|
|
|
+ ctx.WriteBytes(value);
|
|
|
|
+ }
|
|
|
|
+ ctx.Flush();
|
|
|
|
+ ctx.CheckNoSpaceLeft();
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static uint[] CreateRandomVarints32(Random random, int valueCount, int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var result = new uint[valueCount];
|
|
|
|
+ for (int i = 0; i < valueCount; i++)
|
|
|
|
+ {
|
|
|
|
+ result[i] = (uint) ParseRawPrimitivesBenchmark.RandomUnsignedVarint(random, encodedSize, true);
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static ulong[] CreateRandomVarints64(Random random, int valueCount, int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var result = new ulong[valueCount];
|
|
|
|
+ for (int i = 0; i < valueCount; i++)
|
|
|
|
+ {
|
|
|
|
+ result[i] = ParseRawPrimitivesBenchmark.RandomUnsignedVarint(random, encodedSize, false);
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static float[] CreateRandomFloats(Random random, int valueCount)
|
|
|
|
+ {
|
|
|
|
+ var result = new float[valueCount];
|
|
|
|
+ for (int i = 0; i < valueCount; i++)
|
|
|
|
+ {
|
|
|
|
+ result[i] = (float)random.NextDouble();
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static double[] CreateRandomDoubles(Random random, int valueCount)
|
|
|
|
+ {
|
|
|
|
+ var result = new double[valueCount];
|
|
|
|
+ for (int i = 0; i < valueCount; i++)
|
|
|
|
+ {
|
|
|
|
+ result[i] = random.NextDouble();
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static string[] CreateStrings(int valueCount, int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var str = ParseRawPrimitivesBenchmark.CreateStringWithEncodedSize(encodedSize);
|
|
|
|
+
|
|
|
|
+ var result = new string[valueCount];
|
|
|
|
+ for (int i = 0; i < valueCount; i++)
|
|
|
|
+ {
|
|
|
|
+ result[i] = str;
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ private static ByteString[] CreateByteStrings(int valueCount, int encodedSize)
|
|
|
|
+ {
|
|
|
|
+ var str = ParseRawPrimitivesBenchmark.CreateStringWithEncodedSize(encodedSize);
|
|
|
|
+
|
|
|
|
+ var result = new ByteString[valueCount];
|
|
|
|
+ for (int i = 0; i < valueCount; i++)
|
|
|
|
+ {
|
|
|
|
+ result[i] = ByteString.CopyFrom(Encoding.UTF8.GetBytes(str));
|
|
|
|
+ }
|
|
|
|
+ return result;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+}
|