为啥我看到 WCF 的性能比 Remoting 慢?

Posted

技术标签:

【中文标题】为啥我看到 WCF 的性能比 Remoting 慢?【英文标题】:Why am I seeing slower performance here from WCF than Remoting?为什么我看到 WCF 的性能比 Remoting 慢? 【发布时间】:2010-11-20 16:31:48 【问题描述】:

有人告诉我,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 0ms", 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 0ms", 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

【问题讨论】:

这可能取决于您的配置,您不觉得吗?使用的绑定可能很重要,甚至可能是某些设置。 当然可以。这正是我在这里寻找的。我正在使用 NetNamedPipes,我知道它应该是最快的,并且我已经关闭了安全性,但除此之外,我不知所措。 对于应用内域工作,我建议使用某种形式的接口解析器来滚动您自己的解决方案。这就是我为这类问题所做的。使用 ReaderWriterLockSlim 进行锁定(或者您可以使用 Unity)正确滚动大约需要 2-3 个小时,并且由于没有序列化并且解决方案远没有那么复杂,因此会比 WCF 或远程处理带来巨大的性能提升。我了解 WCF 团队正在提出一个进程内频道,但它不会进入 C# 4.0。 这些是您在使用远程处理或 WCF 时必须处理的延迟吗?我实现了本教程...bloggingabout.net/blogs/dennis/archive/2007/04/20/… 并且在创建代理和调用远程方法时得到了约 500 毫秒的延迟。对于我想要使用它的东西来说,这延迟太大了。我应该走套接字路线,还是应该放弃使用远程过程调用来实现我的项目的希望? 史蒂夫,您能详细说明一下您提出的解决方案吗? ReaderWriterLockSlim 处理锁定方案,但是如何在没有 WCF 或 Remoting 的情况下跨 AppDomain 边界传递方法调用? 【参考方案1】:

笔记

大部分时间都花在设置和拆除 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 0ms", 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",
        ;
    


【讨论】:

所以,您的第一句话是“大部分时间都花在了设置和拆除 WCF 通道上”。是赢家。即使在调试模式下,即使有额外的行为,如果您不为每个测试重新创建通道,WCF 也会胜出(大约 25% 左右,给定足够的重复次数)。只是初始设置对 WCF 来说太昂贵了。 是的,总是在使用和不使用调试器的情况下运行性能测试。 现在将近四年后,我复制了 Zach 代码并将其粘贴到 Visual Studio 2012 环境中,运行 .net 4.5,结果完全不同。 WCF 测试在 300 毫秒内运行,远程处理在 170 毫秒内运行。也许远程处理毕竟没有死。 @RalphShillington 在我的机器上,如果没有调试器 (CTRL+F5),远程处理是 160 毫秒,而 Wcf 是 200 毫秒(x86 和 x64)。 VS 2013 + .NET 4.5【参考方案2】:

我发现当您在调试模式下使用 Visual Studio 时,使用 netnamedpipes 的 wcf 比远程处理慢。如果将其更改为发布模式,无论是否附加调试器,wcf 都与远程处理一样快。

【讨论】:

以上是关于为啥我看到 WCF 的性能比 Remoting 慢?的主要内容,如果未能解决你的问题,请参考以下文章

为啥我的 Spark 运行速度比纯 Python 慢?性能比较

MySQL 查询调优 - 为啥使用变量中的值比使用文字慢得多?

为啥 System.nanoTime() 比 System.currentTimeMillis() 慢(在性能上)?

Scala 性能:为啥这个 Scala 应用程序比同等的 Java 应用程序慢 30 倍?

.NET2.0 Remoting - 为啥需要注册 ClientChannel?

为啥我的基数排序 python 实现比快速排序慢?