2008-08-31 CORBAからWCFへ(性能比較)
■[CORBA][WCF][C#]CORBAからWCFへ(性能比較編)

ちょっと古いけど、興味深い記事を発見した。
Windows Communication Foundation (WCF) と既存の分散通信テクノロジのパフォーマンスの比較
今では.NET 3.5 SP1がリリースされてパフォーマンスも改善されているだろうから、どこまで参考になるのか分からないけど、この記事によると、既存のWebサービスや.NETリモーティングと比較して、WCFのパフォーマンスは高いらしい。
では、CORBAと比較するとどうなのだろう?
というわけで、ちょっと実験してみた。
ただし、元記事のようにネットワークのボトルネックやCPUの負荷率の影響などは考慮していないし、こういう実験方法のセオリーもあまり知らないので、信頼性はあまりないかも。
実験方法
- 通信方式は以下の4つ
- 各通信方式で100バイトと1Mバイトのデータを送信して、その通信速度の平均値を出す。
- プロセス間の通信とPC間の通信でやってみた。
- WCFではホスティング方法によってパフォーマンスに影響がでるみたいだけど、今回は通常のコンソールアプリケーションとした。
- CORBA実装としては、IIOP.NETを利用した。
- PCは、Core2 Duo 2.66GHz メモリ4GBのWindows Vistaマシンと、Let's note W5(Windows XP)。
- ネットワークは100BASE
- 結果の縦軸は通信速度(KB/s)なので値の大きい方がパフォーマンスが良いということ。ただし対数表示になっているので注意。
異なるプロセス間での通信
まずは、1台のPC上でサーバとクライアントを動かした場合の結果。
| 通信方式 | 100B | 1MB |
|---|---|---|
| WSHttp(WCF) | 174 | 31480 |
| NetTcp(WCF) | 444 | 251610 |
| IIOP.NET(CORBA) | 2583 | 249840 |
| NamedPipe(WCF) | 397 | 516017 |
ここで面白い結果がでた。
1MBのデータの送受信の場合は、名前付きパイプ(NetNamedPipeBinding)がNetTcpBindingやIIOP.NETの2倍くらい早いんだけど、100Bのデータのときは、逆転してIIOP.NETの方が早くなっている。
データサイズが小さいと、メッセージ変換(シリアライズ/デシリアライズ、マーシャリング/アンマーシャリング)の回数が増えるから、その影響が出ているのだろう。
CORBAでは送信するバイト列をそのままパケットにコピーしているだけで、WCFでは一旦XMLに変換しているため、こんな差が出たのかなと予想してみる。
異なるPC間での通信
続いて、デスクトップPC側にサーバプログラムを起動して、ノートPCからクライアントプログラムを起動してみた。
| 通信方式 | 100B | 1MB |
|---|---|---|
| WSHttp(WCF) | 111 | 13290 |
| NetTcp(WCF) | 1315 | 82168 |
| IIOP.NET(CORBA) | 777 | 80219 |
プロセス間通信とは異なり、NetTcpBindingがIIOP.NETを逆転した。
これはどういうことだろう?
IIOP.NETがプロセス間通信のときに上手く最適化しているのか、NetTcpBindingのネットワーク通信の最適化が効いているのか。
利用したコード
- IIOP.NETのIDL
typedef sequence<octet> OctetSeq; interface IRemoteObject { OctetSeq GetBytes(in long numBytes); };
- IIOP.NETのサーバ
using System; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using System.Threading; using Ch.Elca.Iiop; namespace IiopServer { class RemoteObjectImpl : MarshalByRefObject, IRemoteObject { public byte[] GetBytes(int numBytes) { return new byte[numBytes]; } } class Program { static void Main(string[] args) { int port = 8087; IiopChannel channel = new IiopChannel(port); ChannelServices.RegisterChannel(channel, false); RemoteObjectImpl ro = new RemoteObjectImpl(); string uri = "RemoteObject"; RemotingServices.Marshal(ro, uri); Console.WriteLine("サーバの起動完了しました。"); Thread.Sleep(Timeout.Infinite); } } }
- IIOP.NETのクライアント
using System; using System.Diagnostics; using System.Runtime.Remoting; using System.Runtime.Remoting.Channels; using Ch.Elca.Iiop; namespace IiopClient { class Program { static void Main(string[] args) { string hostname = "localhost"; int port = 8087; Console.WriteLine("開始するには、何かキーを押してください。"); Console.ReadLine(); IiopClientChannel channel = new IiopClientChannel(); ChannelServices.RegisterChannel(channel, false); IRemoteObject client = (IRemoteObject)RemotingServices.Connect(typeof(IRemoteObject), "iiop://" + hostname + ":" + port + "/RemoteObject"); CheckPerformance(client, 1000000, 100); Console.ReadLine(); } static void CheckPerformance(IRemoteObject client, int datasize, int num) { // 試し呼び client.GetBytes(datasize); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < num; i++) { client.GetBytes(datasize); } stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); } } }
using System; using System.ServiceModel; using System.Threading; namespace WcfServer { class Program { [ServiceContract] public interface IRemoteObject { [OperationContract] byte[] GetBytes(int numBytes); } [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)] public class RemoteObjectImpl : IRemoteObject { public byte[] GetBytes(int numBytes) { return new byte[numBytes]; } } static void Main(string[] args) { string hostname = "localhost"; CreateWSHttpServer(hostname); CreateNetTcpServer(hostname); CreateNetNamedPipeServer(); Console.WriteLine("サーバの起動完了しました。"); Thread.Sleep(Timeout.Infinite); } static void CreateWSHttpServer(string hostname) { WSHttpBinding binding = new WSHttpBinding(); binding.MaxReceivedMessageSize = 2000000; binding.ReaderQuotas.MaxArrayLength = 2000000; Uri uri = new Uri("http://" + hostname + ":8000/RemoteObject"); ServiceHost host = new ServiceHost(typeof(RemoteObjectImpl)); host.AddServiceEndpoint(typeof(IRemoteObject), binding, uri); host.Open(); } static void CreateNetTcpServer(string hostname) { NetTcpBinding binding = new NetTcpBinding(); binding.MaxReceivedMessageSize = 2000000; binding.ReaderQuotas.MaxArrayLength = 2000000; Uri uri = new Uri("net.tcp://" + hostname + ":8001/RemoteObject"); ServiceHost host = new ServiceHost(typeof(RemoteObjectImpl)); host.AddServiceEndpoint(typeof(IRemoteObject), binding, uri); host.Open(); } static void CreateNetNamedPipeServer() { NetNamedPipeBinding binding = new NetNamedPipeBinding(); binding.MaxReceivedMessageSize = 2000000; binding.ReaderQuotas.MaxArrayLength = 2000000; Uri uri = new Uri("net.pipe://localhost/RemoteObject"); ServiceHost host = new ServiceHost(typeof(RemoteObjectImpl)); host.AddServiceEndpoint(typeof(IRemoteObject), binding, uri); host.Open(); } } }
using System; using System.Collections.Generic; using System.Diagnostics; using System.ServiceModel; namespace WcfClient { class Program { [ServiceContract] public interface IRemoteObject { [OperationContract] byte[] GetBytes(int numBytes); } static void Main(string[] args) { Console.WriteLine("開始するには、何かキーを押してください。"); Console.ReadLine(); string hostname = "localhost"; List<IRemoteObject> clients = new List<IRemoteObject>(){ CreateWSHttpClient(hostname), CreateNetTcpClient(hostname), CreateNetNamedPipeClient() }; clients.ForEach(client => CheckPerformance(client, 1000000, 100)); Console.ReadLine(); } static void CheckPerformance(IRemoteObject client, int datasize, int num) { // 試し呼び client.GetBytes(datasize); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < num; i++) { client.GetBytes(datasize); } stopwatch.Stop(); Console.WriteLine(stopwatch.ElapsedMilliseconds); } } }
(おまけ)WCFで大きなサイズのデータを送信する場合
WCFで65536バイト以上のデータを送受信しようとするとエラーが発生するので、以下のようにバッファサイズを大きくしてやる必要がある。
binding.MaxReceivedMessageSize = 2000000; binding.ReaderQuotas.MaxArrayLength = 2000000;
なお、ReaderQuotasにアクセスするためには、System.Runtime.Serializationのアセンブリを参照に追加する必要がある。(一部のプロパティだけ別アセンブリってことができるのか・・・)


