views:

642

answers:

5

Hello,

I recently tried running a .NET application on 64bit versions of Windows and was surprised to notice that all my HttpWebRequest.GetResponse() calls to web services on my local network were talking huge (around 500ms) time to complete. Here's a few info regarding my test setup:

  • .NET 3.5
  • Windows Vista Home Premium 32bit, Windows Vista Business 64bit and Windows Server 2008 64bit.

The test code is a slightly modified version of the example described in MSDN's article on HttpWebRequest.GetResponse. The only modifications I performed were just a loop so that I could time 10 consequtive calls, and Basic Authentication, since the web service I was targeting needed authentication:

using System;
using System.Diagnostics;
using System.Net;
using System.Text;
using System.IO;

public class Program
{
    // Specify the URL to receive the request.
    public static void Main (string[] args)
    {
        CredentialCache crCache = null;
        Stopwatch s = new Stopwatch();
        for (int i = 0; i < 10; i++)
        {
            s.Reset();
            s.Start();
            HttpWebRequest request = 
                (HttpWebRequest)WebRequest.Create (args[0]);

            // Set some reasonable limits on resources used by this request
            request.MaximumAutomaticRedirections = 4;
            request.MaximumResponseHeadersLength = 4;
            // Set credentials to use for this request.
            if (crCache == null)
            {
                crCache = new CredentialCache();
                crCache.Add(new Uri(args[0]), "Basic",
                        new NetworkCredential("user", "password"));
            }
            request.Credentials = crCache;
            request.PreAuthenticate = true;

            HttpWebResponse response = (HttpWebResponse)request.GetResponse();

            Console.WriteLine("Content length is {0}", response.ContentLength);
            Console.WriteLine("Content type is {0}", response.ContentType);

            // Get the stream associated with the response.
            Stream receiveStream = response.GetResponseStream();

            // Pipes the stream to a higher level stream reader with the required encoding format. 
            StreamReader readStream = new StreamReader(receiveStream, Encoding.UTF8);

            Console.WriteLine("Response stream received.");
            //Console.WriteLine (readStream.ReadToEnd ());
            response.Close();
            readStream.Close();
            s.Stop();
            Console.WriteLine("Request took: " + s.ElapsedMilliseconds);
        }
    }
}

I compiled the program targeting x86 for Windows Vista Home Premium and x64 for the 64bit Windows machines. The three machines were connected on the same network switch together with the machine hosting the web service. Here are the results I got:

  • Windows Vista Home Premium 32bit, x86 assembly: The first call to GetResponse() was completed in around 150ms, while all consecutive calls took around 10ms.
  • Windows Vista Business 64bit, Server 2008 64 bit, x86 and x64 assemblies: The first call took around 1000ms, while each consecutive call completed in 500ms. If I disable HttpWebRequest.PreAuthenticate, each GetResponse completes in 1000ms (which is quite reasonable since in this case each request triggers two separate HTTP requests, one ending up with Unauthorized and one the gets the proper response).

Does anybody have a clue of the reason I get such long GetResponse delays on the 64 bit versions of Windows?

Edit

Additional information for the problem:

  • I measured the response times (via firebug as suggested) when the request is performed with Firefox 3.5 and the request-response delay is identical for all machines, i.e. the problem is not reproduced.
  • I did further packet analysis with Wireshark and came up with the following results:

32bit: The tcp conversation is as follows:

  • host->server New web request HTTP GET/HTTP/1.1
  • server->host Two TCP segments (~5ms delta)
  • host->server Tcp Ack (acknowledges both segments) (~10us delta)
  • server->host HTTP/1.1 200 OK (~800us delta)
  • host->server New web request HTTP GET/HTTP/1.1 & piggybacked TCP ack for previous segment (HTTP/1.1 200 OK) (~10ms delta)

64 bit: The tcp conversation is as follows:

  • host->server New web request HTTP GET/HTTP/1.1
  • server->host Two TCP segments (~5ms delta, same as 32bit)
  • host->server Tcp Ack (acknowledges both segments) (~10us delta, same as 32 bit)
  • server->host HTTP/1.1 200 OK (~800us delta, same as 32 bit)
  • host->server TCP ack for previous frame (HTTP/1.1 200 OK) (!!! 96ms)
  • host->server New web request HTTP GET/HTTP/1.1 (!!! 309ms)

The 500ms I get on the 64 bit machines is mostly occured in the last two steps. Note that this is definitely not related to the TCP stack (since everything works ok with firefox). The reason we get different TCP Ack pattern in the last two steps (piggybacked in 32 bit, while separate TCP Ack frame in 64 bit) is that the new Web Request is delayed for 309+96ms in the 64bit case (so the TCP stack outputs a separate Ack frame, it can not wait for the application layer).

So, it seems like:

  • The problem is caused by the delta time between the completion of a web request and the issuing of a new one.
  • The problem has something to do with the .NET Framework? (Definitely not TCP related).
  • The problems occurs on stock Microsoft code taken from MSDN (MS can't be wrong right?).

Any clues?

A: 

HttpWebRequest and HttpWebResponse both implement IDisposable, but I don't see you disposing these objects. I do see you Closing the response and response stream, but in my experience if you don't dispose of all these objects, you can get very long delays that otherwise seem random.

Andrew Arnott
Calling response.Close is equivalent to ((IDisposable) response).Dispose (), and he is doing it.
Gonzalo
I'll have to agree with Gonzalo. In fact I'm using dispose in the project that originally triggered the problem. The reason I chose not to wrap the IDisposable HttpWebResponse with a `using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())` is just so that I could stick as close as possible to the official MSDN example for `GetResponse`.
ignorantfool
I agree that Close() is equivalent. But you're not calling it or Dispose on the request object -- only the response object. But this was only an observation. I don't think this would explain different behavior you're seeing between 32-bit and 64-bit.
Andrew Arnott
A: 

Can you make sure that

<system.net><settings><servicePointManager useNagleAlgorithm="false"/>

and try again?

If it still fails, is there a content-length header? Same value in both cases? And, last, but not least, are there extra bytes in the response? Oh, and you should enable the line:

//Console.WriteLine (readStream.ReadToEnd ());

just to make sure that reusing a connection will happen (it might happen without that, but just in case).

Gonzalo
I added the settings in the configuration file, but no luck, the problem remains. Content-length is 2488 on all cases, content type text/html. Response is: 271bytes (on wire first segment) + 1514bytes (on wire, second segment) + 1082bytes (on wire HTTP/1.1 200OK). The reason I commented out the readStream.ReadToEnd() was that I just wanted to measure GetResponse() timings
ignorantfool
That might explain the delay. If you don't read the response until the end and the entire response didn't fit in the buffer used by the underlying classes to read from the socket, reusing the connection is not possible. You might want to either use the HEAD method or read the entire response.
Gonzalo
Can you add the stopwatch around the call to GetResponse(), and see how much time it takes for each time through the loop?Also, what is the content-length of the data that you are downloading? If it is huge, then HttpWebRequest will try to drain the data from the network before issuing the next request. However, if you are saying that the content-length < 4k, then it does not explain the 500ms delay on the second request.
feroze
Have you tried disablign the nagle algorithm progamatically? The delay you're describing is very likely to be the nagle algorithm, try ServicePointManager.UseNagleAlgorithm = false; ServicePointManager.DefaultConnectionLimit = 5; ServicePointManager.Expect100Continue = false;
locster
Are you sure you are doing an apples to apples comparison? You are not doing the test on the same machine. You should actually take the 32 bit executable on the 64 bit machine, install the 32bit clr on it, and run the test there. Try that and report back the perf difference in those two scenarios.
feroze
A: 

Also the following MS KB might be useful for this situation. http://support.microsoft.com/kb/823764

[UPDATE 9/2/10]

Elaborating on my comment above, you should do this test on the same machine. As I mentioned I would run the 32bit executable on the 64it machine and see what measurements I get.

The fact that the app is a lot slower on the 64bit machine could have to do with the machine configuration itself -maybe there is a buggy network card, a bad network driver, network issues etc?

So, first I would do an apples to apples comparison between the 32bit and 64bit application running on the same 64bit machine. This will show if the problem is due to machine configuration, or something else.

feroze
Can you post the wireshark trace on dropbox or a public share so that we can take a look? Your scenario is very simple, you are issuing just a simple GET request, so you should not be seeing these delays.
feroze
+6  A: 

Just for anyone else that comes across this thread and is having issues.

The cause of the delay on 64bit systems is the WebClient waiting for Windows to return the proxy value.

Write your code like this to overcome this issue.

WebClient wc = New WebClient;
wc.Proxy = null;

That will eliminate the delay seen by some users (including myself :))

Glad I could finally give something back to the community that has helped me so much :)

James Ponting
A: 

try setting keep alive to false (request.KeepAlive = false;)

James Hollingworth