Everything I'm told says that WCF should be at least as fast as remoting. I have a specific scenario here, however, where it isn't even close, and I'm wondering if someone can spot something obvious that I'm doing wrong. I'm looking into the possibility of replacing remoting with wcf for the in-process intra-appdomain communication heavy lifting. Here's the code:
[ServiceContract]
interface IWorkerObject
{
[OperationContract] Outcome DoWork(Input t);
}
[DataContract]
[Serializable]
class Input
{
[DataMember] public int TaskId { get; set; }
[DataMember] public int ParentTaskId { get; set; }
[DataMember] public DateTime DateCreated { get; set; }
[DataMember] public string TextData { get; set; }
[DataMember] public byte[] BinaryData { get; set; }
}
[DataContract]
[Serializable]
class Outcome
{
[DataMember] public string Result { get; set; }
[DataMember] public string TextData { get; set; }
[DataMember] public byte[] BinaryData { get; set; }
}
class Program
{
static void Main(string[] args)
{
run_rem_test();
run_wcf_test();
run_rem_test();
run_wcf_test();
}
static void run_rem_test()
{
var dom = AppDomain.CreateDomain("remoting domain", null);
var obj = dom.CreateInstanceFromAndUnwrap(System.Reflection.Assembly.GetExecutingAssembly().Location, typeof(WorkerObject).FullName) as IWorkerObject;
RunTest("remoting", obj);
AppDomain.Unload(dom);
}
static void run_wcf_test()
{
var dom = AppDomain.CreateDomain("wcf domain", null);
var dcnt = dom.CreateInstanceFromAndUnwrap(System.Reflection.Assembly.GetExecutingAssembly().Location, typeof(WorkerObject).FullName) as WorkerObject;
var fact = new ChannelFactory<IWorkerObject>(new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://localhost/the_channel");
var chan = fact.CreateChannel();
dcnt.OpenChannel();
RunTest("wcf", chan);
fact.Close();
dcnt.CloseChannel();
AppDomain.Unload(dom);
}
static void RunTest(string test, IWorkerObject dom)
{
var t = new Input()
{
TextData = new string('a', 8192),
BinaryData = null,
DateCreated = DateTime.Now,
TaskId = 12345,
ParentTaskId = 12344,
};
var sw = System.Diagnostics.Stopwatch.StartNew();
for( var i = 0; i < 1000; i++ )
dom.DoWork(t);
sw.Stop();
Console.WriteLine("{1} test run in {0}ms", sw.ElapsedMilliseconds, test);
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class WorkerObject : MarshalByRefObject, IWorkerObject
{
ServiceHost m_host;
public void OpenChannel()
{
m_host = new ServiceHost(this);
m_host.AddServiceEndpoint(typeof(IWorkerObject), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://localhost/the_channel");
m_host.Open();
}
public void CloseChannel()
{
m_host.Close();
}
public Outcome DoWork(Input t)
{
return new Outcome()
{
TextData = new string('b', 8192),
BinaryData = new byte[1024],
Result = "the result",
};
}
}
When I run this code I get numbers that look like this:
remoting test run in 386ms
wcf test run in 3467ms
remoting test run in 499ms
wcf test run in 1840ms
UPDATE: So it turns out that it's just the initial setup that is so costly for WCF (Thanks, Zach!). Because I was recreating the AppDomains in each test, I was paying that price over and over. Here's the updated code:
[ServiceContract]
interface IWorkerObject
{
[OperationContract] Outcome DoWork(Input t);
}
[DataContract]
[Serializable]
class Input
{
[DataMember] public int TaskId { get; set; }
[DataMember] public int ParentTaskId { get; set; }
[DataMember] public DateTime DateCreated { get; set; }
[DataMember] public string TextData { get; set; }
[DataMember] public byte[] BinaryData { get; set; }
}
[DataContract]
[Serializable]
class Outcome
{
[DataMember] public string Result { get; set; }
[DataMember] public string TextData { get; set; }
[DataMember] public byte[] BinaryData { get; set; }
}
class Program
{
static void Main(string[] args)
{
var rem_dom = AppDomain.CreateDomain("remoting domain", null);
var rem_obj = rem_dom.CreateInstanceFromAndUnwrap(System.Reflection.Assembly.GetExecutingAssembly().Location, typeof(WorkerObject).FullName) as IWorkerObject;
var wcf_dom = AppDomain.CreateDomain("wcf domain", null);
var mgr_obj = wcf_dom.CreateInstanceFromAndUnwrap(System.Reflection.Assembly.GetExecutingAssembly().Location, typeof(WorkerObject).FullName) as WorkerObject;
var fact = new ChannelFactory<IWorkerObject>(new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://localhost/the_channel");
var wcf_obj = fact.CreateChannel();
var rem_tot = 0L;
var wcf_tot = 0L;
mgr_obj.OpenChannel();
for( var i = 0; i < 10; i++ )
{
rem_tot += RunTest("remoting", i, rem_obj);
wcf_tot += RunTest("wcf", i, wcf_obj);
}
fact.Close();
mgr_obj.CloseChannel();
AppDomain.Unload(rem_dom);
AppDomain.Unload(wcf_dom);
Console.WriteLine();
Console.WriteLine("remoting total: {0}", rem_tot);
Console.WriteLine("wcf total: {0}", wcf_tot);
}
static long RunTest(string test, int iter, IWorkerObject dom)
{
var t = new Input()
{
TextData = new string('a', 8192),
BinaryData = null,
DateCreated = DateTime.Now,
TaskId = 12345,
ParentTaskId = 12344,
};
var sw = System.Diagnostics.Stopwatch.StartNew();
for( var i = 0; i < 1000; i++ )
dom.DoWork(t);
sw.Stop();
Console.WriteLine("{1,-8} {2,2} test run in {0}ms", sw.ElapsedMilliseconds, test, iter);
return sw.ElapsedMilliseconds;
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
class WorkerObject : MarshalByRefObject, IWorkerObject
{
ServiceHost m_host;
public void OpenChannel()
{
m_host = new ServiceHost(typeof(WorkerObject));
m_host.AddServiceEndpoint(typeof(IWorkerObject), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://localhost/the_channel");
m_host.Open();
}
public void CloseChannel()
{
m_host.Close();
}
public Outcome DoWork(Input t)
{
return new Outcome()
{
TextData = new string('b', 8192),
BinaryData = new byte[1024],
Result = "the result",
};
}
}
This code gives numbers like this:
remoting 0 test run in 377ms
wcf 0 test run in 2255ms
remoting 1 test run in 488ms
wcf 1 test run in 353ms
remoting 2 test run in 507ms
wcf 2 test run in 355ms
remoting 3 test run in 495ms
wcf 3 test run in 351ms
remoting 4 test run in 484ms
wcf 4 test run in 344ms
remoting 5 test run in 484ms
wcf 5 test run in 354ms
remoting 6 test run in 483ms
wcf 6 test run in 346ms
remoting 7 test run in 491ms
wcf 7 test run in 347ms
remoting 8 test run in 485ms
wcf 8 test run in 358ms
remoting 9 test run in 494ms
wcf 9 test run in 338ms
remoting total: 4788
wcf total: 5401
See Question&Answers more detail:
os