Windows Communication Foundation (WCF) アプリケーションのパフォーマンスは、多くの異なる要素に依存します。アプリケーション設計、インスタンス化モード、同時実行モード、トランスポート、バインディング設定、および調整設定はすべてアプリケーションのパフォーマンスに影響を与えます。InstanceContextMode 設定および ConcurrencyMode 設定は相互に対話し、WCF アプリケーションのパフォーマンスに多大な影響を与えます。ここでは、これらの 2 つの設定が WCF サービスのパフォーマンスに与える影響について説明します。これらの 2 つの設定の WCF への影響を説明する前に、これらの設定の機能について簡単に説明します。
インスタンス コンテキスト モード
InstanceContextMode は、クライアントの呼び出しへの応答でサービス インスタンスの割り当てを制御します。InstanceContextMode は、次の値に設定できます。
Single – すべてのクライアントの呼び出しに対して 1 つのサービス インスタンスが割り当てられます。
PerCall – 各クライアントの呼び出しに対して 1 つのサービス インスタンスが割り当てられます。
PerSession – 各クライアント セッションに対して 1 つのサービス インスタンスが割り当てられます。
InstanceContextMode の既定値は PerSession です。
InstanceContextMode の詳細については、次を参照してください。
InstanceContextMode 列挙体
同時実行モード
ConcurrencyMode では、どのような方法でサービス内において同時に複数のスレッドを許可するかを制御します。ConcurrencyMode は、次のいずれかの値に設定できます。
Single – 1 スレッドのみが一度にサービスに入れます。
Reentrant – 1 スレッドのみが一度にサービスに入れますが、コールバックが許可されます。
Multiple – 同時に複数のスレッドがサービスに入れます。
ConcurrencyMode の既定値は Single です。開発者が、既定の同期化を考える必要がないように、この値が既定に選択されています。ConcurrencyMode の詳細については、次を参照してください。 ConcurrencyMode
インスタンス コンテキスト モードおよび同時実行モードとパフォーマンス
InstanceContextMode 設定および ConcurrencyMode 設定は、相互に影響を与える場合があります。したがって、これらの設定がパフォーマンスに与える影響を考慮する場合に、これらの両方の設定を一緒に考慮する必要があります。たとえば、InstanceContextMode が PerCall に設定されている場合、ConcurrencyMode の設定は無視されます。これは、各クライアントの呼び出しが、新しいサービス インスタンスにルートされるため、1 つのサービス インスタンスで同時に実行されているのは 1 つのスレッドのみとなることに起因します。InstanceContextMode が Single に設定されている場合、1 つのサービス インスタンスのみが作成されるため、ConcurrencyMode がアプリケーションのスループットに影響を与える可能性があります。
次に示すように、InstanceContextMode を Single に設定するにはさまざまな理由があります。
サービス インスタンスの作成に多大な処理が必要とされる場合。このサービスのインスタンス作成を 1 つのインスタンスのみにすることで、多数のクライアントがサービスにアクセスするときに必要となる処理量を低減することができます。
サービス インスタンスが多数のオブジェクトを作成する可能性がある場合。ConcurrencyMode を Single に設定することで、ガベージ コレクション コストを低減することができます。これは、サービスが各呼び出しに対してオブジェクトを作成および破棄する必要がなくなることに起因します。
ConcurrencyMode を Single に設定すると、複数のクライアント間でサービス インスタンスを共有できます。
ConcurrencyMode が Single に設定され、InstanceContextMode が Single に設定されている場合、一度に通過できるクライアントの呼び出しは 1 つのみです。多数のクライアントがある場合、この設定によって重大なボトルネックが発生する場合があります。
サービスが、スレッド間で共有されるデータで処理の大部分を実行しており、複数のスレッドがデータに逐次アクセスする必要がある場合、ConcurrencyMode を Multiple に設定することでパフォーマンスが改善することはまれです。極端な場合は、サービスのパフォーマンスが低下する可能性があります。
InstanceContextMode を Single に設定し、ConcurrencyMode を Reentrant に設定した場合のパフォーマンス特性は、ConcurrencyMode を Single に設定した場合と似たものになります。これは、同時にサービスに入れるクライアント スレッドが 1 つのみであることに起因します。
InstanceContextMode が PerCall に設定されている場合、各クライアントの呼び出しに対して新しいサービス インスタンスが作成され、クライアントの呼び出しが完了するたびにそのインスタンスが破棄されます。サービス インスタンスは呼び出しの実行中のみに使用できるため、呼び出しの完了時にサービス インスタンスがアクセスするすべてのリソースが解放されます。各呼び出しに新しいサービス インスタンスが割り当てられるため多少のオーバーヘッドが存在します。また、各クライアントの呼び出しがそれぞれ固有のサービス インスタンスを持つため、同期化の問題は発生しません。多数のクライアントの呼び出しが行われると、多数のサービス インスタンスが作成されます。したがって、サービス インスタンスが、機能のために必要となるリソースのみを割り当てるようにすることが重要です。
ConcurrencyMode が Multiple に設定されている場合、複数のクライアントの呼び出しを通過させることができます。ただし、開発者には、共有データへのすべてのアクセスを手動で同期化する責任があります。つまり、一度に 1 つのスレッドのみが共有データにアクセスするように、共有データにアクセスするすべての呼び出しをシリアル化する必要があります。このシリアル化によって、ConcurrencyMode を Multiple に設定する意味がなくなります。
InstanceContextMode が PerCall に設定されると、ConcurrencyMode 設定は、スループットに影響を与えなくなります。各クライアントの呼び出しが、それぞれ固有のサービス インスタンスを持つため、各サービス インスタンスは 1 つのスレッド呼び出しのみを持つことになります。 InstanceContextMode が PerSession に設定されると、各セッションにそれぞれ固有のサービス インスタンスが割り当てられます。セッションの詳細については、「セッションの使用.PerSession を使用する場合には、セッションをサポートするバインディングを使用する必要があります。システムに用意されているバインディングのうちセッションをサポートするものを次の表に示します。既定のセッションの設定は、括弧内に示されています。
| バインディング | セッション (既定) |
|---|---|
BasicHttpBinding |
(なし) |
WSHttpBinding |
なし、信頼できるセッション、(セキュリティ保護されたセッション) |
WSDualHttpBinding |
(信頼できるセッション)、セキュリティ保護されたセッション |
WSFederationHttpBinding |
(なし)、信頼できるセッション、セキュリティ保護されたセッション |
NetTcpBinding |
(トランスポート)、信頼できるセッション、セキュリティ保護されたセッション |
NetNamedPipeBinding |
なし、(トランスポート) |
NetMsmqBinding |
(なし)、トランスポート |
NetPeerTcpBinding |
(なし) |
MsmqIntegrationBinding |
(なし) |
BasicHttpContextBinding |
(なし) |
NetTcpContextBinding |
(トランスポート)、信頼できるセッション、セキュリティ保護されたセッション |
WSHttpContextBinding |
なし、信頼できるセッション、(セキュリティ保護されたセッション) |
システムに用意されているセッションをサポートするバインディングの詳細については、「システム標準のバインディング」および「システムが提供するバインディングの構成」を参照してください。PerSession を使用している場合、既定では、各クライアント プロキシに専用のサービス インスタンスが割り当てられます。プロキシからのすべての呼び出しは、同じサービス インスタンスによって処理されます。単一のクライアントが 2 つの異なるプロキシを作成する場合もあります。この場合、2 つのサービス インスタンスが作成されます。2 つのプロキシで同じセッションを共有できます。詳細については「InstanceContextSharing」のサンプルを参照してください。
InstanceContextMode が PerSession に設定され、ConcurrencyMode が Single に設定されている場合、各プロキシに固有のサービス インスタンスが作成されます。単一のプロキシからサービス インスタンスへのすべての同時呼び出しはシリアル化されます。これは、サービス インスタンスに一度に許可されるスレッドは 1 つのみであることに起因します。一度にサービスにアクセスできるスレッドは 1 つのみであるため、同じプロキシに対して行われる他のすべての呼び出しはそのスレッドが終了するまでブロックされます。呼び出しを実行する多数のクライアントがある場合、これによってボトルネックが発生します。
InstanceContextMode が PerSession に設定され、ConcurrencyMode が Multiple に設定されている場合、各プロキシは固有のサービス インスタンスを持ち、複数の同時呼び出しはサービス インスタンスにアクセスできます。この場合、開発者は、共有データへのすべてのアクセスを手動で同期化する必要があります。繰り返しになりますが、これによって共有データへのアクセスのシリアル化が発生し、ConcurrencyMode が Single に設定されている場合に同じプロキシに対して複数の呼び出しが行われたときと同様のパフォーマンスの低下が発生する可能性があります。サービスが呼び出しを同時に処理できるのは、単一のクライアントがその要求を非同期で実行している場合、または複数のクライアントがセッションを共有している場合のみであることに注意してください。
軽量のサービス インスタンスでは、Single を使用する場合と PerCall を使用する場合のスループットの差異は大きくありません (約 8%)。
パフォーマンス データとテスト
このドキュメントで説明されているパフォーマンス データを収集するために使用されたコンピューターの構成は、2 つの Ethernet ネットワーク インターフェイス (1 Gbps) で接続された 1 台のサーバーと 4 台のクライアント コンピューターです。サーバーは、クアッド プロセッサ AMD 64 2.2 GHz の Windows Server 2008 を実行中のコンピューター、各クライアント コンピューターは、サーバーと同じオペレーティング システムを実行中のデュアル プロセッサ AMD 2.2GHz x86 コンピューターです。システムの CPU 利用状況はほぼ 100% で推移します。
4 台のクライアント コンピューターは、WCF サービスへの呼び出しを実行するために使用されました。サービスに対して実行された各呼び出しは、実行中のテストに応じて 1 ~ 100 個のオブジェクトを渡します。各オブジェクトは、注文、購入者に関する情報、および購入されたアイテムで構成されています。
テストでは、WCF テクノロジのサーバー スループットに焦点が当てられました。このスループットは、1 秒間に完了したオペレーション数として定義されています。1 つのオペレーションには、クライアントがサービスに要求を送信し、サービスから応答を受信することが含まれます。このテストで、サービスは応答を生成するために、最小限 (ごくわずか) の量の処理を実行します。 ここで理解するべき重要な点は、ビジネス ロジックによって、うまく構成されている SOA ソリューション内でのサービスのコストが支配されるという点です。サービスでのビジネス ロジック処理を無視した場合、メッセージング インフラストラクチャのコストのみが測定されます。テストの中には、サービスを実装するクラスによる追加の処理が必要なものもありました。これらのテストでは、素数のセットを生成するためにサービス クラスのコンストラクター内にロジックの一部が配置されました。各テストは、3 回実行され、平均のスループットが計算されました。
パフォーマンス データを生成するために使用された WCF サービスによって実装されたインターフェイスを次のコードに示します。
[ServiceContract]
public interface ITestContract
{
[OperationContract]
Order[] GetOrders(int NumOrders);
}
[DataContract]
public class OrderLine
{
[DataMember]
public int ItemID;
[DataMember]
public int Quantity;
}
[DataContract]
public class Order
{
public Order()
{
}
[DataMember]
public int CustomerID;
[DataMember]
public string ShippingAddress1;
[DataMember]
public string ShippingAddress2;
[DataMember]
public string ShippingCity;
[DataMember]
public string ShippingState;
[DataMember]
public string ShippingZip;
[DataMember]
public string ShippingCountry;
[DataMember]
public string ShipType;
[DataMember]
public OrderLine[] orderItems;
[DataMember]
public string CreditCardType;
[DataMember]
public string CreditCardNumber;
[DataMember]
public DateTime CreditCardExpiration;
[DataMember]
public string CreditCardName;
}
}
サービスの実装を次のコードに示します。
[ServiceBehavior]
public class LightWeightService : ITestContract
{
static Order[] orders;
int upperLimit = 10000;
static bool CtorWork = false;
public LightWeightService()
{
if (CtorWork)
DoSomething();
}
void DoSomething()
{
// Some work done here
}
public static void Initialize(int messageSize)
{
orders = new Order[messageSize];
for (int i = 0; i < messageSize; i++)
{
Order order = new Order();
OrderLine[] lines = new OrderLine[2];
lines[0] = new OrderLine();
lines[0].ItemID = 1;
lines[0].Quantity = 10;
lines[1] = new OrderLine();
lines[1].ItemID = 2;
lines[1].Quantity = 5;
order.orderItems = lines;
order.CustomerID = 100;
order.ShippingAddress1 = "012345678901234567890123456789";
order.ShippingAddress2 = "012345678901234567890123456789";
order.ShippingCity = "0123456789";
order.ShippingState = "0123456789012345";
order.ShippingZip = "12345-1234";
order.ShippingCountry = "United States";
order.ShipType = "UPS";
order.CreditCardType = "VISA";
order.CreditCardNumber = "0123456789012345";
order.CreditCardExpiration = DateTime.UtcNow;
order.CreditCardName = "01234567890123456789";
orders[i] = order;
}
}
public Order[] GetOrders(int NumOrders)
{
return orders;
}
}
サービスは、コンソール アプリケーション内で自己ホストされました。次のコードはサービスをホストする方法を示しています。
public static void Main(string[] args)
{
LightWeightService.Initialize(100);
WSHttpBinding binding = new WSHttpBinding(SecurityMode.None, false);
((WSHttpBinding)binding).MaxReceivedMessageSize = 2 * 1024 * 1024;
((WSHttpBinding)binding).ReaderQuotas.MaxArrayLength = 2 * 1024 * 1024;
ServiceHost serviceHost = new ServiceHost(typeof(LightWeightService));
ServiceBehaviorAttribute sba = serviceHost.Description.Behaviors.Find<ServiceBehaviorAttribute>();
if (sba == null)
{
sba = new ServiceBehaviorAttribute();
}
sba.ConcurrencyMode = ConcurrencyMode.Single;
sba.InstanceContextMode = InstanceContextMode.Single;
serviceHost.AddServiceEndpoint(typeof(ITestContract), binding, "https://localhost:8000/PerfTest");
serviceHost.Open();
Console.WriteLine("Service is running...");
Console.WriteLine("Press Enter to terminate service");
Console.ReadLine();
}
また、クライアント アプリケーションもコンソール アプリケーションでした。次のコードは、クライアントを実装する方法を示しています。
static void Main(string[] args)
{
WSHttpBinding binding;
ITestContract proxy;
ChannelFactory<ITestContract> factory;
binding = new WSHttpBinding(SecurityMode.None, false);
factory = new ChannelFactory<ITestContract>(binding, new EndpointAddress("https://localhost:8000/PerfTest"));
proxy = factory.CreateChannel();
proxy.GetOrders(100);
}
InstanceContextMode が Single に、または PerCall と ConcurrencyMode が Single に設定されている場合に、サービスのスループットにオブジェクト数が影響を与える様子を次のグラフに示します。
InstanceContextMode と ConcurrencyMode のさまざまな設定の組み合わせがスループットに与える影響を次のグラフに示します。