Почему я вижу медленную производительность здесь из WCF, чем Remoting?

Все, что мне сказано, говорит о том, что 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",
        };
    }

}

Когда я запускаю этот код, я получаю числа, которые выглядят так:

remoting test run in 386ms
wcf test run in 3467ms
remoting test run in 499ms
wcf test run in 1840ms

UPDATE: Оказывается, это просто первоначальная настройка, которая так дорого стоит для WCF (спасибо, Zach!). Поскольку я воссоздавал AppDomains в каждом тесте, я платил эту цену снова и снова. Здесь обновленный код:

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

}

Этот код дает такие номера:

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

Ответ 1

Примечания

По большей части время тратится на настройку и разрывание канала WCF. Остальная львиная доля времени, похоже, потребляется за счет работы в отладчике. Имейте в виду, что это действительно неформальный тип тестирования..:)

Я смог воспроизвести ваши номера, поэтому я начал оттуда. Это показало, что номера WCF были примерно на 3xs больше, чем удаленных номеров.

Кэшируя канал factory (и соответствующие удаленные объекты), числа опущены, так что WCF был только 2x больше удаленных номеров.

Попробовав еще дюжину других настроек, единственной, которая, казалось, сбрила любое реальное измеримое время с номеров, было очистить все поведение отладочной службы (добавлено по умолчанию) - это привело к примерно ~ 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",
        };
    }

}

Ответ 2

Я понял, что wcf, использующий netnamedpipes, медленнее, чем удаленный, когда вы используете визуальную студию в режиме отладки. Если вы измените его в режиме выпуска, с или без отладчика присоедините wcf так же быстро, как удалять.