#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 nonAsciiStringValues; // 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 }; public IEnumerable NonAsciiStringEncodedSizes => new[] { 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)); } nonAsciiStringValues = new Dictionary(); foreach(var encodedSize in NonAsciiStringEncodedSizes) { nonAsciiStringValues.Add(encodedSize, CreateNonAsciiStrings(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); for (int i = 0; i < values.Length; i++) { cos.WriteRawVarint32(values[i]); } 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); for (int i = 0; i < values.Length; i++) { ctx.WriteUInt32(values[i]); } 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); for (int i = 0; i < values.Length; i++) { cos.WriteRawVarint64(values[i]); } 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); for (int i = 0; i < values.Length; i++) { ctx.WriteUInt64(values[i]); } 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(ulong); 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 WriteRawTag_OneByte_WriteContext() { const int encodedSize = 1; var span = new Span(outputBuffer); WriteContext.Initialize(ref span, out WriteContext ctx); for (uint i = 0; i < BytesToWrite / encodedSize; i++) { ctx.WriteRawTag(16); } ctx.Flush(); ctx.CheckNoSpaceLeft(); } [Benchmark] public void WriteRawTag_TwoBytes_WriteContext() { const int encodedSize = 2; var span = new Span(outputBuffer); WriteContext.Initialize(ref span, out WriteContext ctx); for (uint i = 0; i < BytesToWrite / encodedSize; i++) { ctx.WriteRawTag(137, 6); } ctx.Flush(); ctx.CheckNoSpaceLeft(); } [Benchmark] public void WriteRawTag_ThreeBytes_WriteContext() { const int encodedSize = 3; var span = new Span(outputBuffer); WriteContext.Initialize(ref span, out WriteContext ctx); for (uint i = 0; i < BytesToWrite / encodedSize; i++) { ctx.WriteRawTag(160, 131, 1); } ctx.Flush(); ctx.CheckNoSpaceLeft(); } [Benchmark] public void Baseline_WriteContext() { var span = new Span(outputBuffer); WriteContext.Initialize(ref span, out WriteContext ctx); ctx.state.position = outputBuffer.Length; 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(NonAsciiStringEncodedSizes))] public void WriteNonAsciiString_CodedOutputStream(int encodedSize) { var values = nonAsciiStringValues[encodedSize]; var cos = new CodedOutputStream(outputBuffer); foreach (var value in values) { cos.WriteString(value); } cos.Flush(); cos.CheckNoSpaceLeft(); } [Benchmark] [ArgumentsSource(nameof(NonAsciiStringEncodedSizes))] public void WriteNonAsciiString_WriteContext(int encodedSize) { var values = nonAsciiStringValues[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 string[] CreateNonAsciiStrings(int valueCount, int encodedSize) { var str = ParseRawPrimitivesBenchmark.CreateNonAsciiStringWithEncodedSize(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; } } }