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
, eachGetResponse
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?
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.
HttpWebRequest
andHttpWebResponse
both implementIDisposable
, 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.As mentioned by James Ponting,
Solution:
Solution for me was:
Note that you have to set this before you create your HttpWebRequest objects. Didn't work for me if I set it afterwards.
Speed increase was huge because we create lots of small JSON requests. Probably 10x faster.
Can you make sure that
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:
just to make sure that reusing a connection will happen (it might happen without that, but just in case).
try setting keep alive to false (
request.KeepAlive = false;
)