#region Copyright notice and license | |
// Protocol Buffers - Google's data interchange format | |
// Copyright 2008 Google Inc. All rights reserved. | |
// http://github.com/jskeet/dotnet-protobufs/ | |
// Original C++/Java/Python code: | |
// http://code.google.com/p/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 System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Threading; | |
using Google.ProtocolBuffers.TestProtos; | |
namespace Google.ProtocolBuffers.ProtoBench | |
{ | |
/// <summary> | |
/// Simple benchmarking of arbitrary messages. | |
/// </summary> | |
public sealed class Program | |
{ | |
private static TimeSpan MinSampleTime = TimeSpan.FromSeconds(2); | |
private static TimeSpan TargetTime = TimeSpan.FromSeconds(30); | |
private static bool Verbose = false, FastTest = false; | |
// Avoid a .NET 3.5 dependency | |
private delegate void Action(); | |
private delegate void BenchmarkTest(string name, long dataSize, Action action); | |
private static BenchmarkTest RunBenchmark; | |
[STAThread] | |
public static int Main(string[] args) | |
{ | |
List<string> temp = new List<string>(args); | |
Verbose = temp.Remove("/verbose") || temp.Remove("-verbose"); | |
if (true == (FastTest = (temp.Remove("/fast") || temp.Remove("-fast")))) | |
TargetTime = TimeSpan.FromSeconds(10); | |
RunBenchmark = BenchmarkV1; | |
if (temp.Remove("/v2") || temp.Remove("-v2")) | |
{ | |
Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.RealTime; | |
RunBenchmark = BenchmarkV2; | |
} | |
if (temp.Remove("/all") || temp.Remove("-all")) | |
{ | |
if(FastTest) | |
TargetTime = TimeSpan.FromSeconds(5); | |
foreach (KeyValuePair<string, string> item in MakeTests()) | |
{ | |
temp.Add(item.Key); | |
temp.Add(item.Value); | |
} | |
} | |
args = temp.ToArray(); | |
if (args.Length < 2 || (args.Length%2) != 0) | |
{ | |
Console.Error.WriteLine("Usage: ProtoBench [/fast] <descriptor type name> <input data>"); | |
Console.Error.WriteLine("The descriptor type name is the fully-qualified message name,"); | |
Console.Error.WriteLine("including assembly - e.g. Google.ProtocolBuffers.BenchmarkProtos.Message1,ProtoBench"); | |
Console.Error.WriteLine("(You can specify multiple pairs of descriptor type name and input data.)"); | |
return 1; | |
} | |
bool success = true; | |
for (int i = 0; i < args.Length; i += 2) | |
{ | |
success &= RunTest(args[i], args[i + 1], null); | |
} | |
return success ? 0 : 1; | |
} | |
/// <summary> | |
/// Runs a single test. Error messages are displayed to Console.Error, and the return value indicates | |
/// general success/failure. | |
/// </summary> | |
public static bool RunTest(string typeName, string file, byte[] inputData) | |
{ | |
Console.WriteLine("Benchmarking {0} with file {1}", typeName, file); | |
IMessage defaultMessage; | |
try | |
{ | |
defaultMessage = MessageUtil.GetDefaultMessage(typeName); | |
} | |
catch (ArgumentException e) | |
{ | |
Console.Error.WriteLine(e.Message); | |
return false; | |
} | |
try | |
{ | |
ExtensionRegistry registry = ExtensionRegistry.Empty; | |
inputData = inputData ?? File.ReadAllBytes(file); | |
MemoryStream inputStream = new MemoryStream(inputData); | |
ByteString inputString = ByteString.CopyFrom(inputData); | |
IMessage sampleMessage = | |
defaultMessage.WeakCreateBuilderForType().WeakMergeFrom(inputString, registry).WeakBuild(); | |
if(!FastTest) RunBenchmark("Serialize to byte string", inputData.Length, () => sampleMessage.ToByteString()); | |
RunBenchmark("Serialize to byte array", inputData.Length, () => sampleMessage.ToByteArray()); | |
if (!FastTest) RunBenchmark("Serialize to memory stream", inputData.Length, | |
() => sampleMessage.WriteTo(new MemoryStream())); | |
if (!FastTest) RunBenchmark("Deserialize from byte string", inputData.Length, | |
() => defaultMessage.WeakCreateBuilderForType() | |
.WeakMergeFrom(inputString, registry) | |
.WeakBuild() | |
); | |
RunBenchmark("Deserialize from byte array", inputData.Length, | |
() => defaultMessage.WeakCreateBuilderForType() | |
.WeakMergeFrom(CodedInputStream.CreateInstance(inputData), registry) | |
.WeakBuild() | |
); | |
if (!FastTest) RunBenchmark("Deserialize from memory stream", inputData.Length, | |
() => { | |
inputStream.Position = 0; | |
defaultMessage.WeakCreateBuilderForType().WeakMergeFrom( | |
CodedInputStream.CreateInstance(inputStream), registry) | |
.WeakBuild(); | |
}); | |
Console.WriteLine(); | |
return true; | |
} | |
catch (Exception e) | |
{ | |
Console.Error.WriteLine("Error: {0}", e.Message); | |
Console.Error.WriteLine(); | |
Console.Error.WriteLine("Detailed exception information: {0}", e); | |
return false; | |
} | |
} | |
private static void BenchmarkV2(string name, long dataSize, Action action) | |
{ | |
Thread.BeginThreadAffinity(); | |
TimeSpan elapsed = TimeSpan.Zero; | |
long runs = 0; | |
long totalCount = 0; | |
double best = double.MinValue, worst = double.MaxValue; | |
action(); | |
// Run it progressively more times until we've got a reasonable sample | |
int iterations = 100; | |
elapsed = TimeAction(action, iterations); | |
while (elapsed.TotalMilliseconds < 1000) | |
{ | |
elapsed += TimeAction(action, iterations); | |
iterations *= 2; | |
} | |
TimeSpan target = TimeSpan.FromSeconds(1); | |
elapsed = TimeAction(action, iterations); | |
iterations = (int)((target.Ticks * iterations) / (double)elapsed.Ticks); | |
elapsed = TimeAction(action, iterations); | |
iterations = (int)((target.Ticks * iterations) / (double)elapsed.Ticks); | |
elapsed = TimeAction(action, iterations); | |
iterations = (int)((target.Ticks * iterations) / (double)elapsed.Ticks); | |
double first = (iterations * dataSize) / (elapsed.TotalSeconds * 1024 * 1024); | |
if (Verbose) Console.WriteLine("Round ---: Count = {1,6}, Bps = {2,8:f3}", 0, iterations, first); | |
elapsed = TimeSpan.Zero; | |
int max = (int)TargetTime.TotalSeconds; | |
while (runs < max) | |
{ | |
TimeSpan cycle = TimeAction(action, iterations); | |
// Accumulate and scale for next cycle. | |
double bps = (iterations * dataSize) / (cycle.TotalSeconds * 1024 * 1024); | |
if (Verbose) Console.WriteLine("Round {1,3}: Count = {2,6}, Bps = {3,8:f3}", | |
0, runs, iterations, bps); | |
best = Math.Max(best, bps); | |
worst = Math.Min(worst, bps); | |
runs++; | |
elapsed += cycle; | |
totalCount += iterations; | |
iterations = (int) ((target.Ticks*totalCount)/(double) elapsed.Ticks); | |
} | |
Thread.EndThreadAffinity(); | |
Console.WriteLine("{1}: averages {2} per {3:f3}s for {4} runs; avg: {5:f3}mbps; best: {6:f3}mbps; worst: {7:f3}mbps", | |
0, name, totalCount / runs, elapsed.TotalSeconds / runs, runs, | |
(totalCount * dataSize) / (elapsed.TotalSeconds * 1024 * 1024), best, worst); | |
} | |
private static void BenchmarkV1(string name, long dataSize, Action action) | |
{ | |
// Make sure it's JITted | |
action(); | |
// Run it progressively more times until we've got a reasonable sample | |
int iterations = 1; | |
TimeSpan elapsed = TimeAction(action, iterations); | |
while (elapsed < MinSampleTime) | |
{ | |
iterations *= 2; | |
elapsed = TimeAction(action, iterations); | |
} | |
// Upscale the sample to the target time. Do this in floating point arithmetic | |
// to avoid overflow issues. | |
iterations = (int) ((TargetTime.Ticks/(double) elapsed.Ticks)*iterations); | |
elapsed = TimeAction(action, iterations); | |
Console.WriteLine("{0}: {1} iterations in {2:f3}s; {3:f3}MB/s", | |
name, iterations, elapsed.TotalSeconds, | |
(iterations*dataSize)/(elapsed.TotalSeconds*1024*1024)); | |
} | |
private static TimeSpan TimeAction(Action action, int iterations) | |
{ | |
GC.Collect(); | |
GC.GetTotalMemory(true); | |
GC.WaitForPendingFinalizers(); | |
Stopwatch sw = Stopwatch.StartNew(); | |
for (int i = 0; i < iterations; i++) | |
{ | |
action(); | |
} | |
sw.Stop(); | |
return sw.Elapsed; | |
} | |
private static IEnumerable<KeyValuePair<string, string>> MakeTests() | |
{ | |
//Aggregate Tests | |
yield return MakeWorkItem("all-types", MakeTestAllTypes()); | |
yield return MakeWorkItem("repeated-100", MakeRepeatedTestAllTypes(100)); | |
yield return MakeWorkItem("packed-100", MakeTestPackedTypes(100)); | |
//Discrete Tests | |
foreach (KeyValuePair<string, Action<TestAllTypes.Builder>> item in MakeTestAllTypes()) | |
yield return MakeWorkItem(item.Key, new[] { item }); | |
foreach (KeyValuePair<string, Action<TestAllTypes.Builder>> item in MakeRepeatedTestAllTypes(100)) | |
yield return MakeWorkItem(item.Key, new[] { item }); | |
foreach (KeyValuePair<string, Action<TestPackedTypes.Builder>> item in MakeTestPackedTypes(100)) | |
yield return MakeWorkItem(item.Key, new[] { item }); | |
} | |
private static IEnumerable<KeyValuePair<string, Action<TestAllTypes.Builder>>> MakeTestAllTypes() | |
{ | |
// Many of the raw type serializers below perform poorly due to the numerous fields defined | |
// in TestAllTypes. | |
//single values | |
yield return MakeItem<TestAllTypes.Builder>("int32", 1, x => x.SetOptionalInt32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("int64", 1, x => x.SetOptionalInt64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("uint32", 1, x => x.SetOptionalUint32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("uint64", 1, x => x.SetOptionalUint64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sint32", 1, x => x.SetOptionalSint32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sint64", 1, x => x.SetOptionalSint64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("fixed32", 1, x => x.SetOptionalFixed32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("fixed64", 1, x => x.SetOptionalFixed64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sfixed32", 1, x => x.SetOptionalSfixed32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("sfixed64", 1, x => x.SetOptionalSfixed64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("float", 1, x => x.SetOptionalFloat(1001.1001f)); | |
yield return MakeItem<TestAllTypes.Builder>("double", 1, x => x.SetOptionalDouble(1001.1001)); | |
yield return MakeItem<TestAllTypes.Builder>("bool", 1, x => x.SetOptionalBool(true)); | |
yield return MakeItem<TestAllTypes.Builder>("string", 1, x => x.SetOptionalString("this is a string value")); | |
yield return MakeItem<TestAllTypes.Builder>("bytes", 1, x => x.SetOptionalBytes(ByteString.CopyFromUtf8("this is an array of bytes"))); | |
yield return MakeItem<TestAllTypes.Builder>("group", 1, x => x.SetOptionalGroup(new TestAllTypes.Types.OptionalGroup.Builder().SetA(1001))); | |
yield return MakeItem<TestAllTypes.Builder>("message", 1, x => x.SetOptionalNestedMessage(new TestAllTypes.Types.NestedMessage.Builder().SetBb(1001))); | |
yield return MakeItem<TestAllTypes.Builder>("enum", 1, x => x.SetOptionalNestedEnum(TestAllTypes.Types.NestedEnum.FOO)); | |
} | |
private static IEnumerable<KeyValuePair<string, Action<TestAllTypes.Builder>>> MakeRepeatedTestAllTypes(int size) | |
{ | |
//repeated values | |
yield return MakeItem<TestAllTypes.Builder>("repeated-int32", size, x => x.AddRepeatedInt32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-int64", size, x => x.AddRepeatedInt64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-uint32", size, x => x.AddRepeatedUint32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-uint64", size, x => x.AddRepeatedUint64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sint32", size, x => x.AddRepeatedSint32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sint64", size, x => x.AddRepeatedSint64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-fixed32", size, x => x.AddRepeatedFixed32(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-fixed64", size, x => x.AddRepeatedFixed64(1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sfixed32", size, x => x.AddRepeatedSfixed32(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-sfixed64", size, x => x.AddRepeatedSfixed64(-1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-float", size, x => x.AddRepeatedFloat(1001.1001f)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-double", size, x => x.AddRepeatedDouble(1001.1001)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-bool", size, x => x.AddRepeatedBool(true)); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-string", size, x => x.AddRepeatedString("this is a string value")); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-bytes", size, x => x.AddRepeatedBytes(ByteString.CopyFromUtf8("this is an array of bytes"))); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-group", size, x => x.AddRepeatedGroup(new TestAllTypes.Types.RepeatedGroup.Builder().SetA(1001))); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-message", size, x => x.AddRepeatedNestedMessage(new TestAllTypes.Types.NestedMessage.Builder().SetBb(1001))); | |
yield return MakeItem<TestAllTypes.Builder>("repeated-enum", size, x => x.AddRepeatedNestedEnum(TestAllTypes.Types.NestedEnum.FOO)); | |
} | |
private static IEnumerable<KeyValuePair<string, Action<TestPackedTypes.Builder>>> MakeTestPackedTypes(int size) | |
{ | |
//packed values | |
yield return MakeItem<TestPackedTypes.Builder>("packed-int32", size, x => x.AddPackedInt32(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-int64", size, x => x.AddPackedInt64(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-uint32", size, x => x.AddPackedUint32(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-uint64", size, x => x.AddPackedUint64(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sint32", size, x => x.AddPackedSint32(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sint64", size, x => x.AddPackedSint64(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-fixed32", size, x => x.AddPackedFixed32(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-fixed64", size, x => x.AddPackedFixed64(1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sfixed32", size, x => x.AddPackedSfixed32(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-sfixed64", size, x => x.AddPackedSfixed64(-1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-float", size, x => x.AddPackedFloat(1001.1001f)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-double", size, x => x.AddPackedDouble(1001.1001)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-bool", size, x => x.AddPackedBool(true)); | |
yield return MakeItem<TestPackedTypes.Builder>("packed-enum", size, x => x.AddPackedEnum(ForeignEnum.FOREIGN_FOO)); | |
} | |
private static KeyValuePair<string, Action<T>> MakeItem<T>(string name, int repeated, Action<T> build) where T : IBuilderLite, new() | |
{ | |
if (repeated == 1) | |
return new KeyValuePair<string, Action<T>>(name, build); | |
return new KeyValuePair<string, Action<T>>( | |
String.Format("{0}[{1}]", name, repeated), | |
x => | |
{ | |
for (int i = 0; i < repeated; i++) | |
build(x); | |
} | |
); | |
} | |
private static KeyValuePair<string, string> MakeWorkItem<T>(string name, IEnumerable<KeyValuePair<string, Action<T>>> builders) where T : IBuilderLite, new() | |
{ | |
T builder = new T(); | |
foreach (KeyValuePair<string, Action<T>> item in builders) | |
item.Value(builder); | |
IMessageLite msg = builder.WeakBuild(); | |
string fname = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "unittest_" + name + ".dat"); | |
File.WriteAllBytes(fname, msg.ToByteArray()); | |
return new KeyValuePair<string, string>(String.Format("{0},{1}", msg.GetType().FullName, msg.GetType().Assembly.GetName().Name), fname); | |
} | |
} | |
} |