Wednesday, 23 May 2012

WCF Bindings

Carrying on from an excellent MSDN blog post by Rick Rainey comparing the performance of various WCF bindings here is an example including NetNamePipes and ProtoBuf-Net:

Test results

Original blog post:  http://blogs.msdn.com/b/rickrain/archive/2012/05/20/which-wcf-binding-is-best.aspx

protobuf-net

Is available via Nuget or its Google project hosting: http://code.google.com/p/protobuf-net/

AddressAccessDeniedException

If you come across the following exception:

HTTP could not register URL http://+:9001/. Your process does not have access rights to this namespace (see http://go.microsoft.com/fwlink/?LinkId=70353 for details).

Either run Visual Studio using “Run as Administrator” or visit the following link for a more complete resolution:

http://blogs.msdn.com/b/amitlale/archive/2007/01/29/addressaccessdeniedexception-cause-and-solution.aspx

Source

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.Diagnostics;
using ProtoBuf;
using ProtoBuf.ServiceModel;

namespace BindingTest
{
[ServiceContract]
public interface ITestService
{
[OperationContract]
void Execute(TestData data);
}

[DataContract]
[ProtoContract]
public class TestData
{
[DataMember]
[ProtoMember(1)]
public List<string> Data { get; set; }
}

public class TestService : ITestService
{
public void Execute(TestData data)
{
// empty service implementation
}
}

class Program
{
static void Main(string[] args)
{
var bindingElements = new BindingElement[] {
new BinaryMessageEncodingBindingElement(),
new ReliableSessionBindingElement(true),
new HttpTransportBindingElement()
};

var bindings = new Binding[]
{
new WSHttpBinding() { MaxReceivedMessageSize = Int32.MaxValue },
new NetTcpBinding
{
Name = "NetTcpBinding (SecurityMode.Message)",
Security = { Mode = SecurityMode.Message },
MaxReceivedMessageSize = Int32.MaxValue
},
new CustomBinding(bindingElements)
{
Name = "CustomBinding (HTTP/Binary/ReliableSession(ordered)/NoSecurity"
},
new NetTcpBinding(),
new NetNamedPipeBinding(NetNamedPipeSecurityMode.Transport),
new NetNamedPipeBinding(NetNamedPipeSecurityMode.None)
};

var payloadSize = 15000;
var iterations = 5;

OutputTitle(payloadSize, iterations);

// Compare all the bindings in the bindings array.
CompareBindings(bindings, payloadSize, iterations, false);

// Compare all the bindings using protobuf-net
CompareBindings(bindings, payloadSize, iterations, true);

OutputNote();
}

public static void OutputTitle(int payloadSize, int iterations)
{
Console.WriteLine("Test parameters: {0} byte payload, {1} iterations",
payloadSize.ToString("N0"),
iterations);
Console.WriteLine();
}

private static void OutputNote()
{
Console.WriteLine();
Console.WriteLine("NOTE: NetNamedPipe is only used for same machine inter process communication");
Console.WriteLine();
}

public static string GenerateRandomStringData(int stringSize)
{
var r = new Random();
var sb = new StringBuilder(stringSize);
while (sb.Length < stringSize)
sb.Append((char)(r.Next(0, 256)));
return sb.ToString();
}

public static TestData GenerateTestData(int payloadSize)
{
const int itemDataSize = 4096;

var data = new TestData {Data = new List<string>()};

int numDataItems = payloadSize / itemDataSize;

for (int item = 0; item < numDataItems; item++)
data.Data.Add(GenerateRandomStringData(itemDataSize));

data.Data.Add(GenerateRandomStringData(payloadSize - (numDataItems * itemDataSize)));

return data;
}

public static void CompareBindings(Binding[] bindings, int payloadSize, int iterations, bool useProtoBuf)
{
Console.WriteLine("Using {0} serialisation", useProtoBuf ? "protobuf" : "normal");
var data = GenerateTestData(payloadSize);

foreach (var b in bindings)
{
try
{
// Run the test on this binding.
Stopwatch result = TestBinding(b, data, iterations, useProtoBuf);

// Output the results.
Console.Write(b.Name);
//Console.WriteLine(string.Format("Payload Size: {0}, Iterations: {1}", payloadSize, iterations));
//Console.WriteLine(" Binding Elements:");
//foreach (BindingElement be in b.CreateBindingElements())
// Console.WriteLine(" {0}", be.GetType());
Console.WriteLine(", time: {0} ms", result.ElapsedMilliseconds);
}
catch (Exception e)
{
Console.WriteLine("EXCEPTION: {0}", e.Message);
}
}
Console.WriteLine();
}

public static Stopwatch TestBinding(Binding binding, TestData data, int iterations, bool useProtoBuf)
{
var address = GetAddress(binding);

// Start the host using the binding provided
var host = new ServiceHost(typeof(TestService), new Uri(address));
var endpoint = host.AddServiceEndpoint(typeof(ITestService), binding, "");

if (useProtoBuf)
endpoint.Behaviors.Add(new ProtoEndpointBehavior());

host.Open();

var sw = new Stopwatch();
sw.Restart();

// Create a client proxy using the binding provided
var cf = new ChannelFactory<ITestService>(binding, address);
ITestService proxy = cf.CreateChannel();

// Call the service
for (int count = 0; count < iterations; count++)
proxy.Execute(data);

// Close the client proxy and host.
cf.Close();
sw.Stop();
host.Close();

return sw;
}

private static string GetAddress(Binding binding)
{
var machine = "localhost";
var port = (binding.Scheme == "net.pipe")
? ""
: ":9001";

return string.Concat(binding.Scheme, "://", machine, port, "/", Guid.NewGuid().ToString());
}
}
}

Download:  http://stevenhollidge.com/blog-source-code/BindingTestSolution.zip

2 comments:

  1. Hi Steven,
    Thanks for your post. I'm also trying to evaluate the performance of protobuf for WCF. I added a line in the Execute() to display the number of elements in data.Data, but I found that data is null in the case of protobuf binding. Am I missing something?

    Thanks in advance,
    JLouie

    ReplyDelete