1 #region Copyright notice and license 2 // Protocol Buffers - Google's data interchange format 3 // Copyright 2019 Google Inc. All rights reserved. 4 // https://github.com/protocolbuffers/protobuf 5 // 6 // Redistribution and use in source and binary forms, with or without 7 // modification, are permitted provided that the following conditions are 8 // met: 9 // 10 // * Redistributions of source code must retain the above copyright 11 // notice, this list of conditions and the following disclaimer. 12 // * Redistributions in binary form must reproduce the above 13 // copyright notice, this list of conditions and the following disclaimer 14 // in the documentation and/or other materials provided with the 15 // distribution. 16 // * Neither the name of Google Inc. nor the names of its 17 // contributors may be used to endorse or promote products derived from 18 // this software without specific prior written permission. 19 // 20 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 #endregion 32 33 using BenchmarkDotNet.Attributes; 34 using System; 35 using System.Buffers.Binary; 36 using System.Collections.Generic; 37 using System.IO; 38 using System.Buffers; 39 using System.Text; 40 41 namespace Google.Protobuf.Benchmarks 42 { 43 /// <summary> 44 /// Benchmarks throughput when writing raw primitives. 45 /// </summary> 46 [MemoryDiagnoser] 47 public class WriteRawPrimitivesBenchmark 48 { 49 // key is the encodedSize of varint values 50 Dictionary<int, uint[]> varint32Values; 51 Dictionary<int, ulong[]> varint64Values; 52 53 double[] doubleValues; 54 float[] floatValues; 55 56 // key is the encodedSize of string values 57 Dictionary<int, string[]> stringValues; 58 59 // key is the encodedSize of string values 60 Dictionary<int, string[]> nonAsciiStringValues; 61 62 // key is the encodedSize of string values 63 Dictionary<int, ByteString[]> byteStringValues; 64 65 // the buffer to which all the data will be written 66 byte[] outputBuffer; 67 68 Random random = new Random(417384220); // random but deterministic seed 69 70 public IEnumerable<int> StringEncodedSizes => new[] { 1, 4, 10, 105, 10080 }; 71 72 public IEnumerable<int> NonAsciiStringEncodedSizes => new[] { 4, 10, 105, 10080 }; 73 74 [GlobalSetup] GlobalSetup()75 public void GlobalSetup() 76 { 77 outputBuffer = new byte[BytesToWrite]; 78 79 varint32Values = new Dictionary<int, uint[]>(); 80 varint64Values = new Dictionary<int, ulong[]>(); 81 for (int encodedSize = 1; encodedSize <= 10; encodedSize++) 82 { 83 if (encodedSize <= 5) 84 { 85 varint32Values.Add(encodedSize, CreateRandomVarints32(random, BytesToWrite / encodedSize, encodedSize)); 86 } 87 varint64Values.Add(encodedSize, CreateRandomVarints64(random, BytesToWrite / encodedSize, encodedSize)); 88 } 89 90 doubleValues = CreateRandomDoubles(random, BytesToWrite / sizeof(double)); 91 floatValues = CreateRandomFloats(random, BytesToWrite / sizeof(float)); 92 93 stringValues = new Dictionary<int, string[]>(); 94 95 byteStringValues = new Dictionary<int, ByteString[]>(); 96 foreach(var encodedSize in StringEncodedSizes) 97 { 98 stringValues.Add(encodedSize, CreateStrings(BytesToWrite / encodedSize, encodedSize)); 99 byteStringValues.Add(encodedSize, CreateByteStrings(BytesToWrite / encodedSize, encodedSize)); 100 } 101 102 nonAsciiStringValues = new Dictionary<int, string[]>(); 103 foreach(var encodedSize in NonAsciiStringEncodedSizes) 104 { 105 nonAsciiStringValues.Add(encodedSize, CreateNonAsciiStrings(BytesToWrite / encodedSize, encodedSize)); 106 } 107 } 108 109 // Total number of bytes that each benchmark will write. 110 // Measuring the time taken to write buffer of given size makes it easier to compare parsing speed for different 111 // types and makes it easy to calculate the througput (in MB/s) 112 // 10800 bytes is chosen because it is divisible by all possible encoded sizes for all primitive types {1..10} 113 [Params(10080)] 114 public int BytesToWrite { get; set; } 115 116 [Benchmark] 117 [Arguments(1)] 118 [Arguments(2)] 119 [Arguments(3)] 120 [Arguments(4)] 121 [Arguments(5)] WriteRawVarint32_CodedOutputStream(int encodedSize)122 public void WriteRawVarint32_CodedOutputStream(int encodedSize) 123 { 124 var values = varint32Values[encodedSize]; 125 var cos = new CodedOutputStream(outputBuffer); 126 for (int i = 0; i < values.Length; i++) 127 { 128 cos.WriteRawVarint32(values[i]); 129 } 130 cos.Flush(); 131 cos.CheckNoSpaceLeft(); 132 } 133 134 [Benchmark] 135 [Arguments(1)] 136 [Arguments(2)] 137 [Arguments(3)] 138 [Arguments(4)] 139 [Arguments(5)] WriteRawVarint32_WriteContext(int encodedSize)140 public void WriteRawVarint32_WriteContext(int encodedSize) 141 { 142 var values = varint32Values[encodedSize]; 143 var span = new Span<byte>(outputBuffer); 144 WriteContext.Initialize(ref span, out WriteContext ctx); 145 for (int i = 0; i < values.Length; i++) 146 { 147 ctx.WriteUInt32(values[i]); 148 } 149 ctx.Flush(); 150 ctx.CheckNoSpaceLeft(); 151 } 152 153 [Benchmark] 154 [Arguments(1)] 155 [Arguments(2)] 156 [Arguments(3)] 157 [Arguments(4)] 158 [Arguments(5)] 159 [Arguments(6)] 160 [Arguments(7)] 161 [Arguments(8)] 162 [Arguments(9)] 163 [Arguments(10)] WriteRawVarint64_CodedOutputStream(int encodedSize)164 public void WriteRawVarint64_CodedOutputStream(int encodedSize) 165 { 166 var values = varint64Values[encodedSize]; 167 var cos = new CodedOutputStream(outputBuffer); 168 for (int i = 0; i < values.Length; i++) 169 { 170 cos.WriteRawVarint64(values[i]); 171 } 172 cos.Flush(); 173 cos.CheckNoSpaceLeft(); 174 } 175 176 [Benchmark] 177 [Arguments(1)] 178 [Arguments(2)] 179 [Arguments(3)] 180 [Arguments(4)] 181 [Arguments(5)] 182 [Arguments(6)] 183 [Arguments(7)] 184 [Arguments(8)] 185 [Arguments(9)] 186 [Arguments(10)] WriteRawVarint64_WriteContext(int encodedSize)187 public void WriteRawVarint64_WriteContext(int encodedSize) 188 { 189 var values = varint64Values[encodedSize]; 190 var span = new Span<byte>(outputBuffer); 191 WriteContext.Initialize(ref span, out WriteContext ctx); 192 for (int i = 0; i < values.Length; i++) 193 { 194 ctx.WriteUInt64(values[i]); 195 } 196 ctx.Flush(); 197 ctx.CheckNoSpaceLeft(); 198 } 199 200 [Benchmark] WriteFixed32_CodedOutputStream()201 public void WriteFixed32_CodedOutputStream() 202 { 203 const int encodedSize = sizeof(uint); 204 var cos = new CodedOutputStream(outputBuffer); 205 for (int i = 0; i < BytesToWrite / encodedSize; i++) 206 { 207 cos.WriteFixed32(12345); 208 } 209 cos.Flush(); 210 cos.CheckNoSpaceLeft(); 211 } 212 213 [Benchmark] WriteFixed32_WriteContext()214 public void WriteFixed32_WriteContext() 215 { 216 const int encodedSize = sizeof(uint); 217 var span = new Span<byte>(outputBuffer); 218 WriteContext.Initialize(ref span, out WriteContext ctx); 219 for (uint i = 0; i < BytesToWrite / encodedSize; i++) 220 { 221 ctx.WriteFixed32(12345); 222 } 223 ctx.Flush(); 224 ctx.CheckNoSpaceLeft(); 225 } 226 227 [Benchmark] WriteFixed64_CodedOutputStream()228 public void WriteFixed64_CodedOutputStream() 229 { 230 const int encodedSize = sizeof(ulong); 231 var cos = new CodedOutputStream(outputBuffer); 232 for(int i = 0; i < BytesToWrite / encodedSize; i++) 233 { 234 cos.WriteFixed64(123456789); 235 } 236 cos.Flush(); 237 cos.CheckNoSpaceLeft(); 238 } 239 240 [Benchmark] WriteFixed64_WriteContext()241 public void WriteFixed64_WriteContext() 242 { 243 const int encodedSize = sizeof(ulong); 244 var span = new Span<byte>(outputBuffer); 245 WriteContext.Initialize(ref span, out WriteContext ctx); 246 for (uint i = 0; i < BytesToWrite / encodedSize; i++) 247 { 248 ctx.WriteFixed64(123456789); 249 } 250 ctx.Flush(); 251 ctx.CheckNoSpaceLeft(); 252 } 253 254 [Benchmark] WriteRawTag_OneByte_WriteContext()255 public void WriteRawTag_OneByte_WriteContext() 256 { 257 const int encodedSize = 1; 258 var span = new Span<byte>(outputBuffer); 259 WriteContext.Initialize(ref span, out WriteContext ctx); 260 for (uint i = 0; i < BytesToWrite / encodedSize; i++) 261 { 262 ctx.WriteRawTag(16); 263 } 264 ctx.Flush(); 265 ctx.CheckNoSpaceLeft(); 266 } 267 268 [Benchmark] WriteRawTag_TwoBytes_WriteContext()269 public void WriteRawTag_TwoBytes_WriteContext() 270 { 271 const int encodedSize = 2; 272 var span = new Span<byte>(outputBuffer); 273 WriteContext.Initialize(ref span, out WriteContext ctx); 274 for (uint i = 0; i < BytesToWrite / encodedSize; i++) 275 { 276 ctx.WriteRawTag(137, 6); 277 } 278 ctx.Flush(); 279 ctx.CheckNoSpaceLeft(); 280 } 281 282 [Benchmark] WriteRawTag_ThreeBytes_WriteContext()283 public void WriteRawTag_ThreeBytes_WriteContext() 284 { 285 const int encodedSize = 3; 286 var span = new Span<byte>(outputBuffer); 287 WriteContext.Initialize(ref span, out WriteContext ctx); 288 for (uint i = 0; i < BytesToWrite / encodedSize; i++) 289 { 290 ctx.WriteRawTag(160, 131, 1); 291 } 292 ctx.Flush(); 293 ctx.CheckNoSpaceLeft(); 294 } 295 296 [Benchmark] Baseline_WriteContext()297 public void Baseline_WriteContext() 298 { 299 var span = new Span<byte>(outputBuffer); 300 WriteContext.Initialize(ref span, out WriteContext ctx); 301 ctx.state.position = outputBuffer.Length; 302 ctx.Flush(); 303 ctx.CheckNoSpaceLeft(); 304 } 305 306 [Benchmark] WriteRawFloat_CodedOutputStream()307 public void WriteRawFloat_CodedOutputStream() 308 { 309 var cos = new CodedOutputStream(outputBuffer); 310 foreach (var value in floatValues) 311 { 312 cos.WriteFloat(value); 313 } 314 cos.Flush(); 315 cos.CheckNoSpaceLeft(); 316 } 317 318 [Benchmark] WriteRawFloat_WriteContext()319 public void WriteRawFloat_WriteContext() 320 { 321 var span = new Span<byte>(outputBuffer); 322 WriteContext.Initialize(ref span, out WriteContext ctx); 323 foreach (var value in floatValues) 324 { 325 ctx.WriteFloat(value); 326 } 327 ctx.Flush(); 328 ctx.CheckNoSpaceLeft(); 329 } 330 331 [Benchmark] WriteRawDouble_CodedOutputStream()332 public void WriteRawDouble_CodedOutputStream() 333 { 334 var cos = new CodedOutputStream(outputBuffer); 335 foreach (var value in doubleValues) 336 { 337 cos.WriteDouble(value); 338 } 339 cos.Flush(); 340 cos.CheckNoSpaceLeft(); 341 } 342 343 [Benchmark] WriteRawDouble_WriteContext()344 public void WriteRawDouble_WriteContext() 345 { 346 var span = new Span<byte>(outputBuffer); 347 WriteContext.Initialize(ref span, out WriteContext ctx); 348 foreach (var value in doubleValues) 349 { 350 ctx.WriteDouble(value); 351 } 352 ctx.Flush(); 353 ctx.CheckNoSpaceLeft(); 354 } 355 356 [Benchmark] 357 [ArgumentsSource(nameof(StringEncodedSizes))] WriteString_CodedOutputStream(int encodedSize)358 public void WriteString_CodedOutputStream(int encodedSize) 359 { 360 var values = stringValues[encodedSize]; 361 var cos = new CodedOutputStream(outputBuffer); 362 foreach (var value in values) 363 { 364 cos.WriteString(value); 365 } 366 cos.Flush(); 367 cos.CheckNoSpaceLeft(); 368 } 369 370 [Benchmark] 371 [ArgumentsSource(nameof(StringEncodedSizes))] WriteString_WriteContext(int encodedSize)372 public void WriteString_WriteContext(int encodedSize) 373 { 374 var values = stringValues[encodedSize]; 375 var span = new Span<byte>(outputBuffer); 376 WriteContext.Initialize(ref span, out WriteContext ctx); 377 foreach (var value in values) 378 { 379 ctx.WriteString(value); 380 } 381 ctx.Flush(); 382 ctx.CheckNoSpaceLeft(); 383 } 384 385 [Benchmark] 386 [ArgumentsSource(nameof(NonAsciiStringEncodedSizes))] WriteNonAsciiString_CodedOutputStream(int encodedSize)387 public void WriteNonAsciiString_CodedOutputStream(int encodedSize) 388 { 389 var values = nonAsciiStringValues[encodedSize]; 390 var cos = new CodedOutputStream(outputBuffer); 391 foreach (var value in values) 392 { 393 cos.WriteString(value); 394 } 395 cos.Flush(); 396 cos.CheckNoSpaceLeft(); 397 } 398 399 [Benchmark] 400 [ArgumentsSource(nameof(NonAsciiStringEncodedSizes))] WriteNonAsciiString_WriteContext(int encodedSize)401 public void WriteNonAsciiString_WriteContext(int encodedSize) 402 { 403 var values = nonAsciiStringValues[encodedSize]; 404 var span = new Span<byte>(outputBuffer); 405 WriteContext.Initialize(ref span, out WriteContext ctx); 406 foreach (var value in values) 407 { 408 ctx.WriteString(value); 409 } 410 ctx.Flush(); 411 ctx.CheckNoSpaceLeft(); 412 } 413 414 [Benchmark] 415 [ArgumentsSource(nameof(StringEncodedSizes))] WriteBytes_CodedOutputStream(int encodedSize)416 public void WriteBytes_CodedOutputStream(int encodedSize) 417 { 418 var values = byteStringValues[encodedSize]; 419 var cos = new CodedOutputStream(outputBuffer); 420 foreach (var value in values) 421 { 422 cos.WriteBytes(value); 423 } 424 cos.Flush(); 425 cos.CheckNoSpaceLeft(); 426 } 427 428 [Benchmark] 429 [ArgumentsSource(nameof(StringEncodedSizes))] WriteBytes_WriteContext(int encodedSize)430 public void WriteBytes_WriteContext(int encodedSize) 431 { 432 var values = byteStringValues[encodedSize]; 433 var span = new Span<byte>(outputBuffer); 434 WriteContext.Initialize(ref span, out WriteContext ctx); 435 foreach (var value in values) 436 { 437 ctx.WriteBytes(value); 438 } 439 ctx.Flush(); 440 ctx.CheckNoSpaceLeft(); 441 } 442 CreateRandomVarints32(Random random, int valueCount, int encodedSize)443 private static uint[] CreateRandomVarints32(Random random, int valueCount, int encodedSize) 444 { 445 var result = new uint[valueCount]; 446 for (int i = 0; i < valueCount; i++) 447 { 448 result[i] = (uint) ParseRawPrimitivesBenchmark.RandomUnsignedVarint(random, encodedSize, true); 449 } 450 return result; 451 } 452 CreateRandomVarints64(Random random, int valueCount, int encodedSize)453 private static ulong[] CreateRandomVarints64(Random random, int valueCount, int encodedSize) 454 { 455 var result = new ulong[valueCount]; 456 for (int i = 0; i < valueCount; i++) 457 { 458 result[i] = ParseRawPrimitivesBenchmark.RandomUnsignedVarint(random, encodedSize, false); 459 } 460 return result; 461 } 462 CreateRandomFloats(Random random, int valueCount)463 private static float[] CreateRandomFloats(Random random, int valueCount) 464 { 465 var result = new float[valueCount]; 466 for (int i = 0; i < valueCount; i++) 467 { 468 result[i] = (float)random.NextDouble(); 469 } 470 return result; 471 } 472 CreateRandomDoubles(Random random, int valueCount)473 private static double[] CreateRandomDoubles(Random random, int valueCount) 474 { 475 var result = new double[valueCount]; 476 for (int i = 0; i < valueCount; i++) 477 { 478 result[i] = random.NextDouble(); 479 } 480 return result; 481 } 482 CreateStrings(int valueCount, int encodedSize)483 private static string[] CreateStrings(int valueCount, int encodedSize) 484 { 485 var str = ParseRawPrimitivesBenchmark.CreateStringWithEncodedSize(encodedSize); 486 487 var result = new string[valueCount]; 488 for (int i = 0; i < valueCount; i++) 489 { 490 result[i] = str; 491 } 492 return result; 493 } 494 CreateNonAsciiStrings(int valueCount, int encodedSize)495 private static string[] CreateNonAsciiStrings(int valueCount, int encodedSize) 496 { 497 var str = ParseRawPrimitivesBenchmark.CreateNonAsciiStringWithEncodedSize(encodedSize); 498 499 var result = new string[valueCount]; 500 for (int i = 0; i < valueCount; i++) 501 { 502 result[i] = str; 503 } 504 return result; 505 } 506 CreateByteStrings(int valueCount, int encodedSize)507 private static ByteString[] CreateByteStrings(int valueCount, int encodedSize) 508 { 509 var str = ParseRawPrimitivesBenchmark.CreateStringWithEncodedSize(encodedSize); 510 511 var result = new ByteString[valueCount]; 512 for (int i = 0; i < valueCount; i++) 513 { 514 result[i] = ByteString.CopyFrom(Encoding.UTF8.GetBytes(str)); 515 } 516 return result; 517 } 518 } 519 } 520