4

My performance measurements of synchronous WCF calls from within a Silverlight application showed I can make 7 calls/s on a localhost connection, which is very slow. Can this be speeded up, or is this normal?

This is my test code:

const UInt32 nrCalls = 100; ICalculator calculator = new CalculatorClient(); // took over from the MSDN calculator example for (double i = 0; i < nrCalls; ++i) { var call = calculator.BeginSubtract(i + 1, 1, null, null); call.AsyncWaitHandle.WaitOne(); double result = calculator.EndSubtract(call); } 

Remarks:

  • CPU load is almost 0%. Apparently, the WCF module is waiting for something.
  • I tested this both on Firefox 3.6 and Internet Explorer 7.
  • I'm using Silverlight v3.0
  • For comparison: I've once written an IPC library in C++ and a similar test yielded some 4000 calls/s. That was without the HTTP packet wrapper, but I don't expect that to slow things down much. It's just that 7 calls/s is so incredibly slow.

Update: I've ported the client side from Silverlight to .NET, and this solved the performance problem. In that test, synchronous calls are made at 140 calls/s (instead of 7 calls/s), and asynchronous calls at 200 calls/s (instead of 16 calls/s). Apparently, the slowness is inherent to the Silverlight platform. I'll have to learn to live with it.

2
  • 1
    don't do sync calls. There's a reason why it is not available OOTB in Silverlight, and discouraged in WCF-proper! Commented Mar 9, 2010 at 8:05
  • @Brian: By taking two snapshots of DateTime.Now. Commented Mar 9, 2010 at 9:48

2 Answers 2

3

Not a lot. You run the problem of making 100 http calls, that just takes time. You arelady do not blow it by generating a new client every time.... so, sorry.

In general, this is a bad example (or: a good one showing bad practice). Services should always be coarse grained to avoid the call overhead. A service should have some "weight" in what it does, generally.

A calculator may accept an array of operations, so all 100 calculations can be sent at once, for example.

Keep that in mind when when designing your own interfaces.

Sign up to request clarification or add additional context in comments.

1 Comment

"100 http calls just take time": Agreed, but 7 calls/s is so incredibly slow. I've written an IPC library in C++ and a similar test yielded some 4000 calls/s. That was without the HTTP packet wrapper, but I don't expect that to slow things down much.
1

Use async calls and run them in parallel.

The internet is only so fast. As it is, you send a request, wait for an eternity for (1) the message to reach the server, (2) the server to respond, (3) the response to travel back. (1) and (3) take time; there are things like 'miles to the server' and 'the speed of light' in play, maybe. And then you send the next request and do the same waiting game again. And again. In a loop.

Hence async calls and parallel requests = win.

(Hint: if you use F#, async can be very easy, as in the sample below.)

open System open System.Diagnostics open System.ServiceModel let binding = new BasicHttpBinding() let address = "http://YOURSERVERMACHINENAME:11111/Blah" #if SERVER [<ServiceContract>] type IMyContract = [<OperationContract>] abstract member Subtract : x:int * y:int -> int type MyService() = interface IMyContract with member this.Subtract(x,y) = x-y let host = new ServiceHost(typeof<MyService>, new Uri(address)) host.AddServiceEndpoint(typeof<IMyContract>, binding, address) |> ignore let smb = new Description.ServiceMetadataBehavior() smb.HttpGetEnabled <- true host.Description.Behaviors.Add(smb) host.Open() Console.WriteLine("service is open") #else [<ServiceContract(Name="IMyContract")>] type IMyClientContract = [<OperationContract>] abstract member Subtract : x:int * y:int -> int [<OperationContract(AsyncPattern=true)>] abstract member BeginSubtract : x:int * y:int * c:AsyncCallback * o:obj -> IAsyncResult abstract member EndSubtract : r:IAsyncResult -> int let client = ChannelFactory<IMyClientContract>.CreateChannel(binding, new EndpointAddress(address)) let MAX = 30 let syncSw = Stopwatch.StartNew() [1..MAX] |> Seq.iter (fun i -> let r = client.Subtract(i,1) Console.WriteLine(r)) Console.WriteLine("sync took {0}ms", syncSw.ElapsedMilliseconds) let AsyncSubtract(x,y) = Async.FromBeginEnd(x, y, client.BeginSubtract, client.EndSubtract) let asyncSw = Stopwatch.StartNew() [1..MAX] |> Seq.map (fun i -> async { let! r = AsyncSubtract(i,1) Console.WriteLine(r)}) |> Async.Parallel |> Async.RunSynchronously |> ignore Console.WriteLine("async took {0}ms", asyncSw.ElapsedMilliseconds) #endif Console.WriteLine("press a key to quit") Console.ReadKey() 

10 Comments

"The internet is only so fast": Hm. I'm only using a loopback connection (127.0.0.1), so it isn't the network that is causing the large delay.
Darn. I spent like 15 mins trying to figure out how to disable the firewall just so I could try that sample across two machines before posting it. :) I'd still try them in parallel, even if it means you just call BeginSubtract in the loop and store the results in an array of waithandles, and then WaitAll the handles, and then call EndSubtract again in a loop, to see how much difference that makes.
"15 mins": I first felt a little guilty, but it was in my original question :-)
"I'd still try them in parallel...": I tried this already. In this "semi-sync" scenario, I get 16 calls/s. In the asynchronous scenario (with callbacks) I get the same performance. All this is very slow.
Hmmm, any difference with non-silverlight client? Anything funky with data/message contracts (e.g. hitting JIT/NGEN issues that msdn.microsoft.com/en-us/library/bk3w6240(VS.80).aspx might fix in non-SL case)? Might be worth making one call, sleeping for a bit, and then making the calls in a loop, to ensure no other 'startup time' costs being measured?
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.