title: WCF performance, latency and scalability
body: <p>I'm trying to port a simple async TCP server in F# to C# 4. The server receives a connection, reads a single request and streams back a sequence of responses before closing the connection.</p>
  
  <p>Async in C# 4 looks tedious and error prone so I thought I'd try using WCF instead. This server is not unlikely to see 1,000 simultaneous requests in the wild so I think both throughput and latency are of interest.</p>
  
  <p>I've written a minimal duplex WCF web service and console client in C#. Although I'm using WCF instead of raw sockets, this is already 175 lines of code compared to 80 lines for the original. But I'm more concerned about the performance and scalability:</p>
  
  <ul>
  <li>Latency is 154&#215; worse with WCF.</li>
  <li>Throughput is 54&#215; worse with WCF.</li>
  <li>TCP handles 1,000 simultaneous connections easily but WCF chokes on just 20.</li>
  </ul>
  
  <p>Firstly, I'm using the default settings for everything so I'm wondering if there is anything I can tweak to improve these performance figures?</p>
  
  <p>Secondly, I'm wondering if anyone is using WCF for this kind of thing or if it is the wrong tool for the job?</p>
  
  <p>Here's my WCF server in C#:</p>
  
  <p><code>IService1.cs</code></p>
  
  <pre><code>[DataContract]
  public class Stock
  {
    [DataMember]
    public DateTime FirstDealDate { get; set; }
    [DataMember]
    public DateTime LastDealDate { get; set; }
    [DataMember]
    public DateTime StartDate { get; set; }
    [DataMember]
    public DateTime EndDate { get; set; }
    [DataMember]
    public decimal Open { get; set; }
    [DataMember]
    public decimal High { get; set; }
    [DataMember]
    public decimal Low { get; set; }
    [DataMember]
    public decimal Close { get; set; }
    [DataMember]
    public decimal VolumeWeightedPrice { get; set; }
    [DataMember]
    public decimal TotalQuantity { get; set; }
  }
  
  [ServiceContract(CallbackContract = typeof(IPutStock))]
  public interface IStock
  {
    [OperationContract]
    void GetStocks();
  }
  
  public interface IPutStock
  {
    [OperationContract]
    void PutStock(Stock stock);
  }
  </code></pre>
  
  <p><code>Service1.svc</code></p>
  
  <pre><code>&lt;%@ ServiceHost Language="C#" Debug="true" Service="DuplexWcfService2.Stocks" CodeBehind="Service1.svc.cs" %&gt;
  </code></pre>
  
  <p><code>Service1.svc.cs</code></p>
  
  <pre><code> [ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple)]
   public class Stocks : IStock
   {
     IPutStock callback;
  
     #region IStock Members
     public void GetStocks()
     {
       callback = OperationContext.Current.GetCallbackChannel&lt;IPutStock&gt;();
       Stock st = null;
       st = new Stock
       {
         FirstDealDate = System.DateTime.Now,
         LastDealDate = System.DateTime.Now,
         StartDate = System.DateTime.Now,
         EndDate = System.DateTime.Now,
         Open = 495,
         High = 495,
         Low = 495,
         Close = 495,
         VolumeWeightedPrice = 495,
         TotalQuantity = 495
       };
       for (int i=0; i&lt;1000; ++i)
         callback.PutStock(st);
     }
     #endregion
   }
  </code></pre>
  
  <p><code>Web.config</code></p>
  
  <pre><code>&lt;?xml version="1.0"?&gt;
  &lt;configuration&gt;
    &lt;system.web&gt;
      &lt;compilation debug="true" targetFramework="4.0" /&gt;
    &lt;/system.web&gt;
    &lt;system.serviceModel&gt;
      &lt;services&gt;
        &lt;service name="DuplexWcfService2.Stocks"&gt;
          &lt;endpoint address="" binding="wsDualHttpBinding" contract="DuplexWcfService2.IStock"&gt;
            &lt;identity&gt;
              &lt;dns value="localhost"/&gt;
            &lt;/identity&gt;
          &lt;/endpoint&gt;
          &lt;endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/&gt;
        &lt;/service&gt;
      &lt;/services&gt;
      &lt;behaviors&gt;
        &lt;serviceBehaviors&gt;
          &lt;behavior&gt;
            &lt;serviceMetadata httpGetEnabled="true"/&gt;
            &lt;serviceDebug includeExceptionDetailInFaults="true"/&gt;
          &lt;/behavior&gt;
        &lt;/serviceBehaviors&gt;
      &lt;/behaviors&gt;
      &lt;serviceHostingEnvironment multipleSiteBindingsEnabled="true" /&gt;
    &lt;/system.serviceModel&gt;
    &lt;system.webServer&gt;
      &lt;modules runAllManagedModulesForAllRequests="true"/&gt;
    &lt;/system.webServer&gt;
  &lt;/configuration&gt;
  </code></pre>
  
  <p>Here's the C# WCF client:</p>
  
  <p><code>Program.cs</code></p>
  
  <pre><code> [CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, UseSynchronizationContext = false)]
   class Callback : DuplexWcfService2.IStockCallback
   {
     System.Diagnostics.Stopwatch timer;
     int n;
  
     public Callback(System.Diagnostics.Stopwatch t)
     {
       timer = t;
       n = 0;
     }
  
     public void PutStock(DuplexWcfService2.Stock st)
     {
       ++n;
       if (n == 1)
         Console.WriteLine("First result in " + this.timer.Elapsed.TotalSeconds + "s");
       if (n == 1000)
         Console.WriteLine("1,000 results in " + this.timer.Elapsed.TotalSeconds + "s");
     }
   }
  
   class Program
   {
     static void Test(int i)
     {
       var timer = System.Diagnostics.Stopwatch.StartNew();
       var ctx = new InstanceContext(new Callback(timer));
       var proxy = new DuplexWcfService2.StockClient(ctx);
       proxy.GetStocks();
       Console.WriteLine(i + " connected");
     }
  
     static void Main(string[] args)
     {
       for (int i=0; i&lt;10; ++i)
       {
         int j = i;
         new System.Threading.Thread(() =&gt; Test(j)).Start();
       }
     }
   }
  </code></pre>
  
  <p>Here's my async TCP client and server code in F#:</p>
  
  <pre><code>type AggregatedDeals =
    {
      FirstDealTime: System.DateTime
      LastDealTime: System.DateTime
      StartTime: System.DateTime
      EndTime: System.DateTime
      Open: decimal
      High: decimal
      Low: decimal
      Close: decimal
      VolumeWeightedPrice: decimal
      TotalQuantity: decimal
    }
  
  let read (stream: System.IO.Stream) = async {
    let! header = stream.AsyncRead 4
    let length = System.BitConverter.ToInt32(header, 0)
    let! body = stream.AsyncRead length
    let fmt = System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
    use stream = new System.IO.MemoryStream(body)
    return fmt.Deserialize(stream)
  }
  
  let write (stream: System.IO.Stream) value = async {
    let body =
      let fmt = System.Runtime.Serialization.Formatters.Binary.BinaryFormatter()
      use stream = new System.IO.MemoryStream()
      fmt.Serialize(stream, value)
      stream.ToArray()
    let header = System.BitConverter.GetBytes body.Length
    do! stream.AsyncWrite header
    do! stream.AsyncWrite body
  }
  
  let endPoint = System.Net.IPEndPoint(System.Net.IPAddress.Loopback, 4502)
  
  let server() = async {
    let listener = System.Net.Sockets.TcpListener(endPoint)
    listener.Start()
    while true do
      let client = listener.AcceptTcpClient()
      async {
        use stream = client.GetStream()
        let! _ = stream.AsyncRead 1
        for i in 1..1000 do
          let aggregatedDeals =
            {
              FirstDealTime = System.DateTime.Now
              LastDealTime = System.DateTime.Now
              StartTime = System.DateTime.Now
              EndTime = System.DateTime.Now
              Open = 1m
              High = 1m
              Low = 1m
              Close = 1m
              VolumeWeightedPrice = 1m
              TotalQuantity = 1m
            }
          do! write stream aggregatedDeals
      } |&gt; Async.Start
  }
  
  let client() = async {
    let timer = System.Diagnostics.Stopwatch.StartNew()
    use client = new System.Net.Sockets.TcpClient()
    client.Connect endPoint
    use stream = client.GetStream()
    do! stream.AsyncWrite [|0uy|]
    for i in 1..1000 do
      let! _ = read stream
      if i=1 then lock stdout (fun () -&gt;
        printfn "First result in %fs" timer.Elapsed.TotalSeconds)
    lock stdout (fun () -&gt;
      printfn "1,000 results in %fs" timer.Elapsed.TotalSeconds)
  }
  
  do
    server() |&gt; Async.Start
    seq { for i in 1..100 -&gt; client() }
    |&gt; Async.Parallel
    |&gt; Async.RunSynchronously
    |&gt; ignore
  </code></pre>