diff --git a/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs b/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
index 863e74dc49..75658a9ef8 100644
--- a/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
+++ b/csharp/src/Google.Protobuf.Benchmarks/ParseRawPrimitivesBenchmark.cs
@@ -337,7 +337,7 @@ namespace Google.Protobuf.Benchmarks
CodedOutputStream cos = new CodedOutputStream(ms);
for (int i = 0; i < valueCount + paddingValueCount; i++)
{
- cos.WriteUInt64(RandomUnsignedVarint(random, encodedSize));
+ cos.WriteUInt64(RandomUnsignedVarint(random, encodedSize, false));
}
cos.Flush();
var buffer = ms.ToArray();
@@ -386,11 +386,11 @@ namespace Google.Protobuf.Benchmarks
///
/// Generate a random value that will take exactly "encodedSize" bytes when varint-encoded.
///
- private static ulong RandomUnsignedVarint(Random random, int encodedSize)
+ public static ulong RandomUnsignedVarint(Random random, int encodedSize, bool fitsIn32Bits)
{
Span randomBytesBuffer = stackalloc byte[8];
- if (encodedSize < 1 || encodedSize > 10)
+ if (encodedSize < 1 || encodedSize > 10 || (fitsIn32Bits && encodedSize > 5))
{
throw new ArgumentException("Illegal encodedSize value requested", nameof(encodedSize));
}
@@ -406,6 +406,12 @@ namespace Google.Protobuf.Benchmarks
ulong bitmask = encodedSize < 10 ? ((1UL << (encodedSize * bitsPerByte)) - 1) : ulong.MaxValue;
result = randomValue & bitmask;
+ if (fitsIn32Bits)
+ {
+ // make sure the resulting value is representable by a uint.
+ result &= uint.MaxValue;
+ }
+
if (encodedSize == 10)
{
// for 10-byte values the highest bit always needs to be set (7*9=63)
@@ -443,7 +449,7 @@ namespace Google.Protobuf.Benchmarks
return buffer;
}
- private static string CreateStringWithEncodedSize(int encodedSize)
+ public static string CreateStringWithEncodedSize(int encodedSize)
{
var str = new string('a', encodedSize);
while (CodedOutputStream.ComputeStringSize(str) > encodedSize)
diff --git a/csharp/src/Google.Protobuf.Benchmarks/WriteRawPrimitivesBenchmark.cs b/csharp/src/Google.Protobuf.Benchmarks/WriteRawPrimitivesBenchmark.cs
new file mode 100644
index 0000000000..7dacc9c48c
--- /dev/null
+++ b/csharp/src/Google.Protobuf.Benchmarks/WriteRawPrimitivesBenchmark.cs
@@ -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
+{
+ ///
+ /// Benchmarks throughput when writing raw primitives.
+ ///
+ [MemoryDiagnoser]
+ public class WriteRawPrimitivesBenchmark
+ {
+ // key is the encodedSize of varint values
+ Dictionary varint32Values;
+ Dictionary varint64Values;
+
+ double[] doubleValues;
+ float[] floatValues;
+
+ // key is the encodedSize of string values
+ Dictionary stringValues;
+
+ // key is the encodedSize of string values
+ Dictionary byteStringValues;
+
+ // the buffer to which all the data will be written
+ byte[] outputBuffer;
+
+ Random random = new Random(417384220); // random but deterministic seed
+
+ public IEnumerable StringEncodedSizes => new[] { 1, 4, 10, 105, 10080 };
+
+ [GlobalSetup]
+ public void GlobalSetup()
+ {
+ outputBuffer = new byte[BytesToWrite];
+
+ varint32Values = new Dictionary();
+ varint64Values = new Dictionary();
+ 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();
+ byteStringValues = new Dictionary();
+ 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(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(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(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(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(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(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(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(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;
+ }
+ }
+}