Saturday, October 21, 2006 10:13 PM bart

Increasing NetworkStream performance using a BufferedStream - Demo + Figures

Introduction

Lately I've done some code review focusing on security and performance. One of my findings is presented in this blog post, concerning network performance when working with streams. This sample illustrates how to optimize network throughput when working with NetworkStream objects.

Basically, a NetworkStream is non-buffered by default. When dealing with small pieces of data at a time this is highly inefficient. It's much better to have some buffer that collects data for network submission. This can be accomplished using the BufferedStream class in the System.IO API.

A simple demo server: the byte sucker

For demo purposes I'm presenting you with a simple "byte sucker" server that listens on some port for an incoming TCP connection and sucks data from it till the socket is closed by the client. No multi-threading, just plain simple code:

using System;
using
System.IO;
using
System.Net;
using
System.Net.Sockets;

class
Srv
{
   public static void Main(string
[] args)
   {
      Console.WriteLine("Byte sucker network server"
);
      Console.WriteLine("--------------------------\n"
);

      int
port = -1;
      if (args.Length != 1 || !int.TryParse(args[0], out
port))
      {
         Console.WriteLine("Usage: srv.exe <port>"
);
         return
;
      }

      TcpListener srv =
new
TcpListener(IPAddress.Loopback, port);
      srv.Start();

      while (true
)
      {
         Console.Write("Listening... "
);

         TcpClient clnt = srv.AcceptTcpClient();
         NetworkStream ns = clnt.GetStream();

         Console
.WriteLine("Client connected.");
         Console.Write("Receiving data... "
);

         while
(ns.ReadByte() != -1) ;

         Console
.WriteLine("Finished.");
         Console
.WriteLine();
      }
   }
}

System;
using
System.IO;
using
System.Net;
using
System.Net.Sockets;

class
Srv
{
   public static void Main(string
[] args)
   {
      Console.WriteLine("Byte sucker network server"
);
      Console.WriteLine("--------------------------\n"
);

      int
port = -1;
      if (args.Length != 1 || !int.TryParse(args[0], out
port))
      {
         Console.WriteLine("Usage: srv.exe <port>"
);
         return
;
      }

      TcpListener srv =
new
TcpListener(IPAddress.Loopback, port);
      srv.Start();

      while (true
)
      {
         Console.Write("Listening... "
);

         TcpClient clnt = srv.AcceptTcpClient();
         NetworkStream ns = clnt.GetStream();

         Console
.WriteLine("Client connected.");
         Console.Write("Receiving data... "
);

         while
(ns.ReadByte() != -1) ;

         Console
.WriteLine("Finished.");
         Console
.WriteLine();
      }
   }
}

Optimizing a client

On to the real stuff: the client that submits data to the server. We'll assume a client that sends data on a byte-per-byte basis to the server. The straightforward way to do this is the following:

TcpClient clnt = new TcpClient("localhost", 1234);
NetworkStream ns = clnt.GetStream();
for (...; ...; ...)
   ns.WriteByte(...);

new TcpClient("localhost", 1234);
NetworkStream ns = clnt.GetStream();
for (...; ...; ...)
   ns.WriteByte(...);

A better way to do this relies on a buffer:

TcpClient clnt = new TcpClient("localhost", 1234);
NetworkStream ns = clnt.GetStream();
BufferedStream bs = new BufferedStream(ns);
for (...; ...; ...)
   bs.WriteByte(...);

The goal is to buffer data before passing it on to the underlying stream, in casu the NetworkStream.

Here's the full code:

using System;
using
System.Diagnostics;
using
System.IO;
using
System.Net.Sockets;

class
Buffer
{
   public static void Main(string
[] args)
   {
      Console.WriteLine("Buffered network traffic demo"
);
      Console.WriteLine("-----------------------------\n"
);

      int
port = -1;
      if (args.Length != 1 || !int.TryParse(args[0], out
port))
      {
         Console.WriteLine("Usage: buffer.exe <port>"
);
         return
;
      }

      Console.Write("Connecting on port {0}... "
, port);

      Stopwatch watch =
new
Stopwatch();

      TcpClient clnt =
new TcpClient("localhost"
, 1234);
      NetworkStream ns = clnt.GetStream();

      Console.WriteLine("Connected."
);
      Console
.WriteLine();
      Console.Write("Sending non-buffered data... "
);

      Random rand = new Random
();

      watch.Start();
      for (int
i = 0; i < 1000000; i++)
         ns.WriteByte((
byte
)rand.Next(255));
      watch.Stop();

      Console
.WriteLine("Done.");
      Console.WriteLine("Non-buffered: {0}"
, watch.Elapsed);

      Console
.WriteLine();
      Console.Write("Sending buffered data... "
);

      BufferedStream bs = new BufferedStream
(ns);

      watch.Reset();
      watch.Start();
      for (int
i = 0; i < 1000000; i++)
         bs.WriteByte((
byte
)rand.Next(255));
      watch.Stop();

      Console.WriteLine("Done."
);
      Console.WriteLine("Buffered: {0}"
, watch.Elapsed);

      ns.Close();
      clnt.Close();
   }
}

System;
using
System.Diagnostics;
using
System.IO;
using
System.Net.Sockets;

class
Buffer
{
   public static void Main(string
[] args)
   {
      Console.WriteLine("Buffered network traffic demo"
);
      Console.WriteLine("-----------------------------\n"
);

      int
port = -1;
      if (args.Length != 1 || !int.TryParse(args[0], out
port))
      {
         Console.WriteLine("Usage: buffer.exe <port>"
);
         return
;
      }

      Console.Write("Connecting on port {0}... "
, port);

      Stopwatch watch =
new
Stopwatch();

      TcpClient clnt =
new TcpClient("localhost"
, 1234);
      NetworkStream ns = clnt.GetStream();

      Console.WriteLine("Connected."
);
      Console
.WriteLine();
      Console.Write("Sending non-buffered data... "
);

      Random rand = new Random
();

      watch.Start();
      for (int
i = 0; i < 1000000; i++)
         ns.WriteByte((
byte
)rand.Next(255));
      watch.Stop();

      Console
.WriteLine("Done.");
      Console.WriteLine("Non-buffered: {0}"
, watch.Elapsed);

      Console
.WriteLine();
      Console.Write("Sending buffered data... "
);

      BufferedStream bs = new BufferedStream
(ns);

      watch.Reset();
      watch.Start();
      for (int
i = 0; i < 1000000; i++)
         bs.WriteByte((
byte
)rand.Next(255));
      watch.Stop();

      Console.WriteLine("Done."
);
      Console.WriteLine("Buffered: {0}"
, watch.Elapsed);

      ns.Close();
      clnt.Close();
   }
}

Test time

Compile both apps and run in two command prompts (start srv.exe first and then buffer.exe), or use "start" to launch the apps in a separate window. Needless to say the port parameter should be the same (e.g. 1234). The server output isn't of any particular interest, but the client output is:

Buffered network traffic demo
-----------------------------

Connecting on port 1234... Connected.
Sending non-buffered data... Done.
Non-buffered: 00:00:06.1217484

Sending buffered data... Done.
Buffered:     00:00:01.2173279

A factor 5 faster with buffering. Great isn't it? One little remark: you can specify an additional parameter to the BufferedStream's constructor, to indicate the buffer size in bytes. Play around with this setting to find out about the right balance for your app.

Keep it fast!

kick it on DotNetKicks.com

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under: ,

Comments

No Comments