Dot-Net

為什麼我看到 WCF 的性能比 Remoting 慢?

  • June 30, 2015

我被告知的一切都表明 WCF 至少應該與遠端處理一樣快。然而,我在這裡有一個特定的場景,它甚至沒有接近,我想知道是否有人能發現我做錯的明顯事情。我正在研究用 wcf 替換遠端處理的可能性,以實現程序內應用程序域內通信的繁重工作。這是程式碼:

[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",
       };
   }

}

當我執行此程式碼時,我得到如下所示的數字:

遠端測試在 386 毫秒內執行
wcf 測試執行時間為 3467 毫秒
遠端測試在 499 毫秒內執行
wcf 測試執行時間為 1840 毫秒

更新:所以事實證明,對於 WCF 來說,這只是初始設置成本很高(謝謝,Zach!)。因為我在每次測試中都在重新創建 AppDomain,所以我一遍又一遍地付出這個代價。這是更新的程式碼:

[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",
       };
   }

}

這段程式碼給出了這樣的數字:

遠端 0 測試在 377 毫秒內執行
wcf 0 測試在 2255 毫秒內執行
遠端 1 次測試在 488 毫秒內執行
wcf 1 測試在 353 毫秒內執行
遠端處理 2 次測試在 507 毫秒內執行
wcf 2 測試在 355 毫秒內執行
遠端處理 3 次測試在 495 毫秒內執行
wcf 3 測試在 351 毫秒內執行
遠端 4 測試執行在 484 毫秒
wcf 4 測試在 344 毫秒內執行
遠端 5 次測試在 484 毫秒內執行
wcf 5 測試在 354 毫秒內執行
遠端 6 次測試在 483 毫秒內執行
wcf 6 測試在 346 毫秒內執行
遠端 7 測試在 491 毫秒內執行
wcf 7 測試在 347 毫秒內執行
遠端 8 次測試在 485 毫秒內執行
wcf 8 測試在 358 毫秒內執行
遠端 9 次測試在 494 毫秒內執行
wcf 9 測試在 338 毫秒內執行

遠端總:4788
wcf 總計:5401

筆記

在大多數情況下,時間都花在了設置和拆除 WCF 通道上。其他大部分時間似乎都被在調試器中執行所消耗。請記住,這真的是非正式的類型測試.. :)

我能夠複製你的數字,所以我從那裡開始。這表明 WCF 數量大約是遠端處理數量的 3 倍。

通過記憶體通道工廠(和相應的遠端處理對象),數字下降,因此 WCF僅比遠端處理數字大 2 倍。

在嘗試了十幾種不同的調整之後,唯一一個似乎可以減少任何真正可衡量的時間的調整是清除所有調試服務行為(預設添加)——這導致大約 100 毫秒。還不足以真正感到興奮。

然後,一時興起,我在調試器外部以發布配置執行程式碼。數字下降到大致相當於遠端數字,如果不是更好的話。額頭拍了拍桌子,說完成了。

簡而言之,您應該看到大致相同,使用 WCF 有可能獲得更好的性能並獲得更好的程式模型來啟動。

樣品執行

remoting 1 test run in 347ms    
wcf      1 test run in 1544ms    
remoting 2 test run in 493ms    
wcf      2 test run in 324ms
remoting 3 test run in 497ms
wcf      3 test run in 336ms
remoting 4 test run in 449ms
wcf      4 test run in 289ms
remoting 5 test run in 448ms
wcf      5 test run in 284ms
remoting 6 test run in 447ms
wcf      6 test run in 282ms
remoting 7 test run in 439ms
wcf      7 test run in 281ms
remoting 8 test run in 441ms
wcf      8 test run in 278ms
remoting 9 test run in 441ms
wcf      9 test run in 278ms
remoting 10 test run in 438ms
wcf      10 test run in 286ms

程式碼

注意 - 此程式碼已完全混蛋。我道歉。:)

using System;
using System.ServiceModel;
using System.Runtime.Serialization;



[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 AppDomain dom = AppDomain.CreateDomain("wcf domain", null);
   static WorkerObject dcnt = dom.CreateInstanceFromAndUnwrap(System.Reflection.Assembly.GetExecutingAssembly().Location, typeof(WorkerObject).FullName) as WorkerObject;
   static ChannelFactory<IWorkerObject> fact = new ChannelFactory<IWorkerObject>(new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://localhost/the_channel");
   static IWorkerObject chan = fact.CreateChannel();
   static AppDomain remdom = AppDomain.CreateDomain("remoting domain", null);
   static IWorkerObject remoteObject;

   static void Main(string[] args)
   {

       remoteObject = remdom.CreateInstanceFromAndUnwrap(System.Reflection.Assembly.GetExecutingAssembly().Location, typeof(WorkerObject).FullName) as IWorkerObject;


       dcnt.OpenChannel();


       int numberOfIterations = 10;

       for (int i = 1; i <= numberOfIterations; i++)
       {
           run_rem_test(i);
           run_wcf_test(i);
       }

       fact.Close();

       dcnt.CloseChannel();

       AppDomain.Unload(dom);
       AppDomain.Unload(remdom);



   }

   static void run_rem_test(int iteration)
   {

       RunTest("remoting " + iteration, remoteObject);


   }

   static void run_wcf_test(int iteration)
   {


       RunTest("wcf      " + iteration, chan);

   }

   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(typeof(WorkerObject));


       m_host.AddServiceEndpoint(typeof(IWorkerObject), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "net.pipe://localhost/the_channel");

       // cache our ServiceBehaviorAttribute, clear all other behaviors (mainly debug)
       // and add our ServiceBehavior back
       //
       var b = m_host.Description.Behaviors[0] as ServiceBehaviorAttribute;


       m_host.Description.Behaviors.Clear();

       m_host.Description.Behaviors.Add(b);



       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",
       };
   }

}

引用自:https://stackoverflow.com/questions/1295353