parent
723a85f797
commit
b90d3bd854
1 changed files with 265 additions and 0 deletions
@ -0,0 +1,265 @@ |
||||
#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; |
||||
|
||||
namespace Google.Protobuf.Benchmarks |
||||
{ |
||||
/// <summary> |
||||
/// Benchmarks throughput when parsing raw primitives. |
||||
/// </summary> |
||||
[MemoryDiagnoser] |
||||
public class ParseRawPrimitivesBenchmark |
||||
{ |
||||
// key is the encodedSize of varint values |
||||
Dictionary<int, byte[]> varintInputBuffers; |
||||
|
||||
byte[] doubleInputBuffer; |
||||
byte[] floatInputBuffer; |
||||
byte[] fixedIntInputBuffer; |
||||
|
||||
Random random = new Random(417384220); // random but deterministic seed |
||||
|
||||
[GlobalSetup] |
||||
public void GlobalSetup() |
||||
{ |
||||
// add some extra values that we won't read just to make sure we are far enough from the end of the buffer |
||||
// which allows the parser fastpath to always kick in. |
||||
const int paddingValueCount = 100; |
||||
|
||||
varintInputBuffers = new Dictionary<int, byte[]>(); |
||||
for (int encodedSize = 1; encodedSize <= 10; encodedSize++) |
||||
{ |
||||
byte[] buffer = CreateBufferWithRandomVarints(random, BytesToParse / encodedSize, encodedSize, paddingValueCount); |
||||
varintInputBuffers.Add(encodedSize, buffer); |
||||
} |
||||
|
||||
doubleInputBuffer = CreateBufferWithRandomDoubles(random, BytesToParse / sizeof(double), paddingValueCount); |
||||
floatInputBuffer = CreateBufferWithRandomFloats(random, BytesToParse / sizeof(float), paddingValueCount); |
||||
fixedIntInputBuffer = CreateBufferWithRandomData(random, BytesToParse / sizeof(long), sizeof(long), paddingValueCount); |
||||
} |
||||
|
||||
// Total number of bytes that each benchmark will parse. |
||||
// Measuring the time taken to parse 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 BytesToParse { get; set; } |
||||
|
||||
[Benchmark] |
||||
[Arguments(1)] |
||||
[Arguments(2)] |
||||
[Arguments(3)] |
||||
[Arguments(4)] |
||||
[Arguments(5)] |
||||
public int ParseRawVarint32(int encodedSize) |
||||
{ |
||||
CodedInputStream cis = new CodedInputStream(varintInputBuffers[encodedSize]); |
||||
int sum = 0; |
||||
for (int i = 0; i < BytesToParse / encodedSize; i++) |
||||
{ |
||||
sum += cis.ReadInt32(); |
||||
} |
||||
return sum; |
||||
} |
||||
|
||||
[Benchmark] |
||||
[Arguments(1)] |
||||
[Arguments(2)] |
||||
[Arguments(3)] |
||||
[Arguments(4)] |
||||
[Arguments(5)] |
||||
[Arguments(6)] |
||||
[Arguments(7)] |
||||
[Arguments(8)] |
||||
[Arguments(9)] |
||||
[Arguments(10)] |
||||
public long ParseRawVarint64(int encodedSize) |
||||
{ |
||||
CodedInputStream cis = new CodedInputStream(varintInputBuffers[encodedSize]); |
||||
long sum = 0; |
||||
for (int i = 0; i < BytesToParse / encodedSize; i++) |
||||
{ |
||||
sum += cis.ReadInt64(); |
||||
} |
||||
return sum; |
||||
} |
||||
|
||||
[Benchmark] |
||||
public uint ParseFixed32() |
||||
{ |
||||
const int encodedSize = sizeof(uint); |
||||
CodedInputStream cis = new CodedInputStream(fixedIntInputBuffer); |
||||
uint sum = 0; |
||||
for (uint i = 0; i < BytesToParse / encodedSize; i++) |
||||
{ |
||||
sum += cis.ReadFixed32(); |
||||
} |
||||
return sum; |
||||
} |
||||
|
||||
[Benchmark] |
||||
public ulong ParseFixed64() |
||||
{ |
||||
const int encodedSize = sizeof(ulong); |
||||
CodedInputStream cis = new CodedInputStream(fixedIntInputBuffer); |
||||
ulong sum = 0; |
||||
for (int i = 0; i < BytesToParse / encodedSize; i++) |
||||
{ |
||||
sum += cis.ReadFixed64(); |
||||
} |
||||
return sum; |
||||
} |
||||
|
||||
[Benchmark] |
||||
public float ParseRawFloat() |
||||
{ |
||||
const int encodedSize = sizeof(float); |
||||
CodedInputStream cis = new CodedInputStream(floatInputBuffer); |
||||
float sum = 0; |
||||
for (int i = 0; i < BytesToParse / encodedSize; i++) |
||||
{ |
||||
sum += cis.ReadFloat(); |
||||
} |
||||
return sum; |
||||
} |
||||
|
||||
[Benchmark] |
||||
public double ParseRawDouble() |
||||
{ |
||||
const int encodedSize = sizeof(double); |
||||
CodedInputStream cis = new CodedInputStream(doubleInputBuffer); |
||||
double sum = 0; |
||||
for (int i = 0; i < BytesToParse / encodedSize; i++) |
||||
{ |
||||
sum += cis.ReadDouble(); |
||||
} |
||||
return sum; |
||||
} |
||||
|
||||
private static byte[] CreateBufferWithRandomVarints(Random random, int valueCount, int encodedSize, int paddingValueCount) |
||||
{ |
||||
MemoryStream ms = new MemoryStream(); |
||||
CodedOutputStream cos = new CodedOutputStream(ms); |
||||
for (int i = 0; i < valueCount + paddingValueCount; i++) |
||||
{ |
||||
cos.WriteUInt64(RandomUnsignedVarint(random, encodedSize)); |
||||
} |
||||
cos.Flush(); |
||||
var buffer = ms.ToArray(); |
||||
|
||||
if (buffer.Length != encodedSize * (valueCount + paddingValueCount)) |
||||
{ |
||||
throw new InvalidOperationException($"Unexpected output buffer length {buffer.Length}"); |
||||
} |
||||
return buffer; |
||||
} |
||||
|
||||
private static byte[] CreateBufferWithRandomFloats(Random random, int valueCount, int paddingValueCount) |
||||
{ |
||||
MemoryStream ms = new MemoryStream(); |
||||
CodedOutputStream cos = new CodedOutputStream(ms); |
||||
for (int i = 0; i < valueCount + paddingValueCount; i++) |
||||
{ |
||||
cos.WriteFloat((float)random.NextDouble()); |
||||
} |
||||
cos.Flush(); |
||||
var buffer = ms.ToArray(); |
||||
return buffer; |
||||
} |
||||
|
||||
private static byte[] CreateBufferWithRandomDoubles(Random random, int valueCount, int paddingValueCount) |
||||
{ |
||||
MemoryStream ms = new MemoryStream(); |
||||
CodedOutputStream cos = new CodedOutputStream(ms); |
||||
for (int i = 0; i < valueCount + paddingValueCount; i++) |
||||
{ |
||||
cos.WriteDouble(random.NextDouble()); |
||||
} |
||||
cos.Flush(); |
||||
var buffer = ms.ToArray(); |
||||
return buffer; |
||||
} |
||||
|
||||
private static byte[] CreateBufferWithRandomData(Random random, int valueCount, int encodedSize, int paddingValueCount) |
||||
{ |
||||
int bufferSize = (valueCount + paddingValueCount) * encodedSize; |
||||
byte[] buffer = new byte[bufferSize]; |
||||
random.NextBytes(buffer); |
||||
return buffer; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// Generate a random value that will take exactly "encodedSize" bytes when varint-encoded. |
||||
/// </summary> |
||||
private static ulong RandomUnsignedVarint(Random random, int encodedSize) |
||||
{ |
||||
Span<byte> randomBytesBuffer = stackalloc byte[8]; |
||||
|
||||
if (encodedSize < 1 || encodedSize > 10) |
||||
{ |
||||
throw new ArgumentException("Illegal encodedSize value requested", nameof(encodedSize)); |
||||
} |
||||
const int bitsPerByte = 7; |
||||
|
||||
ulong result = 0; |
||||
while (true) |
||||
{ |
||||
random.NextBytes(randomBytesBuffer); |
||||
ulong randomValue = BinaryPrimitives.ReadUInt64LittleEndian(randomBytesBuffer); |
||||
|
||||
// only use the number of random bits we need |
||||
ulong bitmask = encodedSize < 10 ? ((1UL << (encodedSize * bitsPerByte)) - 1) : ulong.MaxValue; |
||||
result = randomValue & bitmask; |
||||
|
||||
if (encodedSize == 10) |
||||
{ |
||||
// for 10-byte values the highest bit always needs to be set (7*9=63) |
||||
result |= ulong.MaxValue; |
||||
break; |
||||
} |
||||
|
||||
// some random values won't require the full "encodedSize" bytes, check that at least |
||||
// one of the top 7 bits is set. Retrying is fine since it only happens rarely |
||||
if (encodedSize == 1 || (result & (0x7FUL << ((encodedSize - 1) * bitsPerByte))) != 0) |
||||
{ |
||||
break; |
||||
} |
||||
} |
||||
return result; |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue