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