WCF Relative Binding Speeds

My latest infrastructure design consists of a few services spread over various servers and with many clients in various locations. Some of the clients will be on the same machine as the service, others will be on the same NT-based network, others will be WSE3 services, and even others will be PHP based. I wanted to provide the best solution for each, ergo my love for the Go-live of WCF! With WCF I can have the services expose multiple endpoints to allow a diverse number of clients each with their own maximum efficiency (remember, if you aren't measuring, then you aren't engineering!) PHP clients for example will get a BasicHttpBinding endpoint, while clients on the same machine as the service will use the NetNamedPipeBinding.

What do the speeds look like for each of these bindings? I went ahead and wrote a quick test harness to see the relative speeds of each binding. These are very simple tests, but they do demonstrate how the speeds of the bindings compare with each other. Without a ton of needless talk, let's get right into what's going on...

Here is the service configuration file...

  <system.serviceModel>
    <services>
      <service type="WcfDemo.DemoService">
        <endpoint
          address="WsDualHttpBinding"
          contract="WcfDemo.IDemoService"
          binding="wsDualHttpBinding"
          />
        <endpoint
          address="WsHttpBinding"
          contract="WcfDemo.IDemoService"
          binding="wsHttpBinding"
          />
        <endpoint
          address="BasicHttpBinding"
          contract="WcfDemo.IDemoService"
          binding="basicHttpBinding"></endpoint>
        <endpoint
          address="NetTcpBinding"
          contract="WcfDemo.IDemoService"
          binding="netTcpBinding"
          />
        <endpoint
          address="NetNamedPipeBinding"
          contract="WcfDemo.IDemoService"
          binding="netNamedPipeBinding"></endpoint>
      </service>
    </services>
  </system.serviceModel>

Here is the significant portion of the service code...

Uri netTcpAddress = new Uri("net.tcp://localhost:8080/");
Uri httpAddress = new Uri("http://localhost:8081/");
Uri netPipeAddress = new Uri("net.pipe://localhost/");
using (ServiceHost service = new ServiceHost(typeof(DemoService), new Uri[] { netTcpAddress, httpAddress, netPipeAddress })) {
        service.Open( );

        Console.WriteLine("Listening...");
        Console.ReadLine( );
}

Notice the netPipeAddress is using "net.pipe" as the address scheme and doesn't have a port. This is actually for the netNamedPipeBinding binding which is for inner process communication on the same machine, so there wouldn't be a port for that. The netTcpBinding binding does however have a port as it would really be out on the network somewhere. Also keep in mind that you add a URI for each address scheme, not each address.

Here is the client configuration file...

<system.serviceModel>
    <client>
      <endpoint
        address="http://localhost:8081/WsDualHttpBinding"
        binding="wsDualHttpBinding"
        name="WSDualHttpBinding"
        contract="IDemoService" />
      <endpoint
        address="http://localhost:8081/WsHttpBinding"
        binding="wsHttpBinding"
        name="WSHttpBinding"
        contract="IDemoService" />
      <endpoint
        address="http://localhost:8081/BasicHttpBinding"
        binding="basicHttpBinding"
        name="BasicHttpBinding"
        contract="IDemoService" />
      <endpoint
        address="net.tcp://localhost:8080/NetTcpBinding"
        binding="netTcpBinding"
        name="NetTcpBinding"
        contract="IDemoService" />
      <endpoint
        address="net.pipe://localhost/NetNamedPipeBinding"
        binding="netNamedPipeBinding"
        name="NetNamedPipeBinding"
        contract="IDemoService" />
    </client>
  </system.serviceModel>

Again notice there is no port on the netNamedPipeBinding binding and there is a port on the netTcpBinding. Also notice that each of the addresses end in the name of the endpoint specified by the server; the endpoints also have a name attribute to be accessible by the client-side code as seen in the next code block.

Here is how I start my client code...

DemoServiceClient wSDualHttpClient = new DemoServiceClient("WSDualHttpBinding");
DemoServiceClient wSHttpClient = new DemoServiceClient("WSHttpBinding");
DemoServiceClient basicHttpClient = new DemoServiceClient("BasicHttpBinding");
DemoServiceClient netTcpClient = new DemoServiceClient("NetTcpBinding");
DemoServiceClient netNamedPipeClient = new DemoServiceClient("NetNamedPipeBinding");

My actual benchmarking code consists of a simple timer that look like this...

Person person = new Person( );
DateTime a = DateTime.Now;
int i = 0;
while ((DateTime.Now - a).Seconds < 10) {
    wSDualHttpClient.RecordPerson(person);
    i++;
}
Console.WriteLine("WSDualHttpBinding: Processed  calls in 10 seconds", i);

That basically just means I want to run as many tests as possible in 10 seconds.

I actually ordered the tests based on my predictions of their relative speeds and it turns out my predictions were correct. Take a look for yourself...

WSDualHttpBinding: Processed 1602 calls in 10 seconds
WSHttpBinding: Processed 2531 calls in 10 seconds
BasicHttpBinding: Processed 17913 calls in 10 seconds
NetTcpBinding: Processed 39957 calls in 10 seconds
NetNamedPipeBinding: Processed 48255 calls in 10 seconds

This isn't really that surprising. The more complex web services protocols are slower than the most basic ones, which are in turn slower than a direct TCP connection, which in turn is slower than the named-pipes binding connection for use on the same machine.  So, if you are going to be doing something chatty, you may want to think twice before using SOAP.

That was actually an out-of-the-box benchmark; that is, I didn't customize the bindings in any way. In another test I turned off security for the WsDualHttpBinding and WsHttpBinding bindings.

First, I added the following binding sections to both the service and the client configuration files.

<bindings>
  <wsDualHttpBinding>
    <binding name="noSecurity">
      <security mode="None"></security>
    </binding>
  </wsDualHttpBinding>
  <wsHttpBinding>
    <binding name="noSecurity">
      <security mode="None"></security>
    </binding>
  </wsHttpBinding>
</bindings>

Then I altered the endpoints to point to the appropriate binding. Here is the service configuration alteration.

<endpoint
    address="WsDualHttpBinding"
    bindingConfiguration="noSecurity"
    contract="WcfDemo.IDemoService"
    binding="wsDualHttpBinding"
    />
<endpoint
    address="WsHttpBinding"
    bindingConfiguration="noSecurity"
    contract="WcfDemo.IDemoService"
    binding="wsHttpBinding"
    />

here is the client configuration alteration.

<endpoint
    address="http://localhost:8081/WsDualHttpBinding"
    binding="wsDualHttpBinding"
    name="WSDualHttpBinding"
    bindingConfiguration="noSecurity"
    contract="IDemoService" />
<endpoint
    address="http://localhost:8081/WsHttpBinding"
    binding="wsHttpBinding"
    name="WSHttpBinding"
    bindingConfiguration="noSecurity"
    contract="IDemoService" />

The results for both WSDualHttpBinding and WSHttpBinding were much faster!

WSDualHttpBinding: Processed 5773 calls in 10 seconds
WSHttpBinding: Processed 17257 calls in 10 seconds
BasicHttpBinding: Processed 19528 calls in 10 seconds
NetTcpBinding: Processed 39756 calls in 10 seconds
NetNamedPipeBinding: Processed 47457 calls in 10 seconds

So as you can see you can get really different levels of performance based on the different binding you use. The ability to have these various endpoints provides a great mechanism for my services to reach all kinds of different clients with great interoperability and maximized speed for network and local communication. Best of all, it's all free...no extra coding! All I had to do to add the new binding was add the endpoint to the service configuration file. WCF takes care of all the rest for you...

Supplemental material - Sample Projects