Kaynağa Gözat

initial version of write benchmarks

Jan Tattermusch 5 yıl önce
ebeveyn
işleme
886c263a3c

+ 10 - 4
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
         /// <summary>
         /// Generate a random value that will take exactly "encodedSize" bytes when varint-encoded.
         /// </summary>
-        private static ulong RandomUnsignedVarint(Random random, int encodedSize)
+        public static ulong RandomUnsignedVarint(Random random, int encodedSize, bool fitsIn32Bits)
         {
             Span<byte> 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)

+ 414 - 0
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
+{
+    /// <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;
+        }
+    }
+}