Program.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #region Copyright notice and license
  2. // Protocol Buffers - Google's data interchange format
  3. // Copyright 2008 Google Inc. All rights reserved.
  4. // http://github.com/jskeet/dotnet-protobufs/
  5. // Original C++/Java/Python code:
  6. // http://code.google.com/p/protobuf/
  7. //
  8. // Redistribution and use in source and binary forms, with or without
  9. // modification, are permitted provided that the following conditions are
  10. // met:
  11. //
  12. // * Redistributions of source code must retain the above copyright
  13. // notice, this list of conditions and the following disclaimer.
  14. // * Redistributions in binary form must reproduce the above
  15. // copyright notice, this list of conditions and the following disclaimer
  16. // in the documentation and/or other materials provided with the
  17. // distribution.
  18. // * Neither the name of Google Inc. nor the names of its
  19. // contributors may be used to endorse or promote products derived from
  20. // this software without specific prior written permission.
  21. //
  22. // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  23. // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  24. // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  25. // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  26. // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  27. // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  28. // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  29. // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  30. // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  31. // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  32. // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  33. #endregion
  34. using System;
  35. using System.Collections.Generic;
  36. using System.Diagnostics;
  37. using System.IO;
  38. using System.Threading;
  39. namespace Google.ProtocolBuffers.ProtoBench
  40. {
  41. /// <summary>
  42. /// Simple benchmarking of arbitrary messages.
  43. /// </summary>
  44. public sealed class Program
  45. {
  46. private static TimeSpan MinSampleTime = TimeSpan.FromSeconds(2);
  47. private static TimeSpan TargetTime = TimeSpan.FromSeconds(30);
  48. private static bool FastTest = false;
  49. private static bool Verbose = false;
  50. // Avoid a .NET 3.5 dependency
  51. private delegate void Action();
  52. private delegate void BenchmarkTest(string name, long dataSize, Action action);
  53. private static BenchmarkTest RunBenchmark;
  54. [STAThread]
  55. public static int Main(string[] args)
  56. {
  57. List<string> temp = new List<string>(args);
  58. FastTest = temp.Remove("/fast") || temp.Remove("-fast");
  59. Verbose = temp.Remove("/verbose") || temp.Remove("-verbose");
  60. RunBenchmark = BenchmarkV1;
  61. if (temp.Remove("/v2") || temp.Remove("-v2"))
  62. {
  63. string cpu = temp.Find(x => x.StartsWith("-cpu:"));
  64. int cpuIx = 1;
  65. if (cpu != null) cpuIx = 1 << Math.Max(0, int.Parse(cpu.Substring(5)));
  66. //pin the entire process to a single CPU
  67. Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(cpuIx);
  68. Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime;
  69. RunBenchmark = BenchmarkV2;
  70. }
  71. args = temp.ToArray();
  72. if (args.Length < 2 || (args.Length%2) != 0)
  73. {
  74. Console.Error.WriteLine("Usage: ProtoBench [/fast] <descriptor type name> <input data>");
  75. Console.Error.WriteLine("The descriptor type name is the fully-qualified message name,");
  76. Console.Error.WriteLine("including assembly - e.g. Google.ProtocolBuffers.BenchmarkProtos.Message1,ProtoBench");
  77. Console.Error.WriteLine("(You can specify multiple pairs of descriptor type name and input data.)");
  78. return 1;
  79. }
  80. bool success = true;
  81. for (int i = 0; i < args.Length; i += 2)
  82. {
  83. success &= RunTest(args[i], args[i + 1]);
  84. }
  85. return success ? 0 : 1;
  86. }
  87. /// <summary>
  88. /// Runs a single test. Error messages are displayed to Console.Error, and the return value indicates
  89. /// general success/failure.
  90. /// </summary>
  91. public static bool RunTest(string typeName, string file)
  92. {
  93. Console.WriteLine("Benchmarking {0} with file {1}", typeName, file);
  94. IMessage defaultMessage;
  95. try
  96. {
  97. defaultMessage = MessageUtil.GetDefaultMessage(typeName);
  98. }
  99. catch (ArgumentException e)
  100. {
  101. Console.Error.WriteLine(e.Message);
  102. return false;
  103. }
  104. try
  105. {
  106. byte[] inputData = File.ReadAllBytes(file);
  107. MemoryStream inputStream = new MemoryStream(inputData);
  108. ByteString inputString = ByteString.CopyFrom(inputData);
  109. IMessage sampleMessage =
  110. defaultMessage.WeakCreateBuilderForType().WeakMergeFrom(inputString).WeakBuild();
  111. if(!FastTest) RunBenchmark("Serialize to byte string", inputData.Length, () => sampleMessage.ToByteString());
  112. RunBenchmark("Serialize to byte array", inputData.Length, () => sampleMessage.ToByteArray());
  113. if (!FastTest) RunBenchmark("Serialize to memory stream", inputData.Length,
  114. () => sampleMessage.WriteTo(new MemoryStream()));
  115. if (!FastTest) RunBenchmark("Deserialize from byte string", inputData.Length,
  116. () => defaultMessage.WeakCreateBuilderForType()
  117. .WeakMergeFrom(inputString)
  118. .WeakBuild()
  119. );
  120. RunBenchmark("Deserialize from byte array", inputData.Length,
  121. () => defaultMessage.WeakCreateBuilderForType()
  122. .WeakMergeFrom(CodedInputStream.CreateInstance(inputData))
  123. .WeakBuild()
  124. );
  125. if (!FastTest) RunBenchmark("Deserialize from memory stream", inputData.Length,
  126. () => {
  127. inputStream.Position = 0;
  128. defaultMessage.WeakCreateBuilderForType().WeakMergeFrom(
  129. CodedInputStream.CreateInstance(inputStream))
  130. .WeakBuild();
  131. });
  132. Console.WriteLine();
  133. return true;
  134. }
  135. catch (Exception e)
  136. {
  137. Console.Error.WriteLine("Error: {0}", e.Message);
  138. Console.Error.WriteLine();
  139. Console.Error.WriteLine("Detailed exception information: {0}", e);
  140. return false;
  141. }
  142. }
  143. private static void BenchmarkV2(string name, long dataSize, Action action)
  144. {
  145. TimeSpan elapsed = TimeSpan.Zero;
  146. long runs = 0;
  147. long totalCount = 0;
  148. double best = double.MinValue, worst = double.MaxValue;
  149. action();
  150. // Run it progressively more times until we've got a reasonable sample
  151. int iterations = 100;
  152. elapsed = TimeAction(action, iterations);
  153. while (elapsed.TotalMilliseconds < 1000)
  154. {
  155. elapsed += TimeAction(action, iterations);
  156. iterations *= 2;
  157. }
  158. TimeSpan target = TimeSpan.FromSeconds(1);
  159. elapsed = TimeAction(action, iterations);
  160. iterations = (int)((target.Ticks * iterations) / (double)elapsed.Ticks);
  161. elapsed = TimeAction(action, iterations);
  162. iterations = (int)((target.Ticks * iterations) / (double)elapsed.Ticks);
  163. elapsed = TimeAction(action, iterations);
  164. iterations = (int)((target.Ticks * iterations) / (double)elapsed.Ticks);
  165. double first = (iterations * dataSize) / (elapsed.TotalSeconds * 1024 * 1024);
  166. if (Verbose) Console.WriteLine("Round ---: Count = {1,6}, Bps = {2,8:f3}", 0, iterations, first);
  167. elapsed = TimeSpan.Zero;
  168. int max = FastTest ? 10 : 30;
  169. while (runs < max)
  170. {
  171. TimeSpan cycle = TimeAction(action, iterations);
  172. // Accumulate and scale for next cycle.
  173. double bps = (iterations * dataSize) / (cycle.TotalSeconds * 1024 * 1024);
  174. if (Verbose) Console.WriteLine("Round {0,3}: Count = {1,6}, Bps = {2,8:f3}", runs, iterations, bps);
  175. best = Math.Max(best, bps);
  176. worst = Math.Min(worst, bps);
  177. runs++;
  178. elapsed += cycle;
  179. totalCount += iterations;
  180. iterations = (int) ((target.Ticks*totalCount)/(double) elapsed.Ticks);
  181. }
  182. Console.WriteLine("{0}: averages {1} per {2:f3}s for {3} runs; avg: {4:f3}mbps; best: {5:f3}mbps; worst: {6:f3}mbps",
  183. name, totalCount / runs, elapsed.TotalSeconds / runs, runs,
  184. (totalCount * dataSize) / (elapsed.TotalSeconds * 1024 * 1024), best, worst);
  185. }
  186. private static void BenchmarkV1(string name, long dataSize, Action action)
  187. {
  188. // Make sure it's JITted
  189. action();
  190. // Run it progressively more times until we've got a reasonable sample
  191. int iterations = 1;
  192. TimeSpan elapsed = TimeAction(action, iterations);
  193. while (elapsed < MinSampleTime)
  194. {
  195. iterations *= 2;
  196. elapsed = TimeAction(action, iterations);
  197. }
  198. // Upscale the sample to the target time. Do this in floating point arithmetic
  199. // to avoid overflow issues.
  200. iterations = (int) ((TargetTime.Ticks/(double) elapsed.Ticks)*iterations);
  201. elapsed = TimeAction(action, iterations);
  202. Console.WriteLine("{0}: {1} iterations in {2:f3}s; {3:f3}MB/s",
  203. name, iterations, elapsed.TotalSeconds,
  204. (iterations*dataSize)/(elapsed.TotalSeconds*1024*1024));
  205. }
  206. private static TimeSpan TimeAction(Action action, int iterations)
  207. {
  208. GC.Collect();
  209. GC.GetTotalMemory(true);
  210. GC.WaitForPendingFinalizers();
  211. Stopwatch sw = Stopwatch.StartNew();
  212. for (int i = 0; i < iterations; i++)
  213. {
  214. action();
  215. }
  216. sw.Stop();
  217. return sw.Elapsed;
  218. }
  219. }
  220. }