Lowyの声明を読む際のキーワードは、「スループットのために<em>」です。Lowyは、ConcurrencyMode.Single WCFを使用すると、サービスインスタンスへのシリアル化を強制するために、盲目的にロックを実装することを指摘しています。ロックは高価であり、PerCallは、2番目のスレッドが同じサービスインスタンスを呼び出そうとしないことをすでに保証しているため、これは必要ありません。
動作に関して:
ConcurrencyModeは、PerCallサービスインスタンスでは重要ではありません。
パフォーマンスの観点から:
ConcurrencyMode.MultipleであるPerCallサービスは、ConcurrencyMode.Singleが使用している(不要な)スレッドロックを作成および取得しないため、わずかに高速になるはずです。
PerCallサービスのシングルとマルチプルのパフォーマンスへの影響を測定できるかどうかを確認するための簡単なベンチマークプログラムを作成しました。ベンチマークには意味のある違いはありませんでした。
自分で実行してみたい場合は、以下のコードを貼り付けました。
私が試したテストケース:
- 600スレッドがサービスを500回呼び出す
- サービスを1000回呼び出す200スレッド
- サービスを10000回呼び出す8つのスレッド
- 1つのスレッドが10000回サービスを呼び出す
これは、Service2008R2を実行している4CPUVMで実行しました。1スレッドの場合を除いて、すべてCPUに制約がありました。
結果:すべての実行は、互いに約5%以内でした。
ConcurrencyMode.Multipleの方が速い場合がありました。ConcurrencyMode.Singleの方が速い場合がありました。たぶん、適切な統計分析が勝者を選ぶかもしれません。私の意見では、それらは問題にならないほど十分に近いです。
典型的な出力は次のとおりです。
net.pipe:// localhost/baseでシングルサービスを開始しています...Type= SingleService ThreadCount = 600 ThreadCallCount = 500
ランタイム:45156759ティック 12615ミリ秒
net.pipe:// localhost/baseで
複数のサービスを開始しています...Type= MultipleService ThreadCount = 600 ThreadCallCount = 500ランタイム:48731273ティック 13613ミリ秒
net.pipe:// localhost/baseでシングルサービスを開始しています...Type= SingleService ThreadCount = 600 ThreadCallCount = 500
ランタイム:48701509ティック 13605ミリ秒
net.pipe:// localhost/baseで
複数のサービスを開始しています...Type= MultipleService ThreadCount = 600 ThreadCallCount = 500ランタイム:48590336ティック 13574ミリ秒
ベンチマークコード:
通常の警告:これは、本番環境での使用に適さないショートカットを使用するベンチマークコードです。
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace WCFTest
{
[ServiceContract]
public interface ISimple
{
[OperationContract()]
void Put();
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Single)]
public class SingleService : ISimple
{
public void Put()
{
//Console.WriteLine("put got " + i);
return;
}
}
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerCall, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class MultipleService : ISimple
{
public void Put()
{
//Console.WriteLine("put got " + i);
return;
}
}
public class ThreadParms
{
public int ManagedThreadId { get; set; }
public ServiceEndpoint ServiceEndpoint { get; set; }
}
public class BenchmarkService
{
public readonly int ThreadCount;
public readonly int ThreadCallCount;
public readonly Type ServiceType;
int _completed = 0;
System.Diagnostics.Stopwatch _stopWatch;
EventWaitHandle _waitHandle;
bool _done;
public BenchmarkService(Type serviceType, int threadCount, int threadCallCount)
{
this.ServiceType = serviceType;
this.ThreadCount = threadCount;
this.ThreadCallCount = threadCallCount;
_done = false;
}
public void Run(string baseAddress)
{
if (_done)
throw new InvalidOperationException("Can't run twice");
ServiceHost host = new ServiceHost(ServiceType, new Uri(baseAddress));
host.Open();
Console.WriteLine("Starting " + ServiceType.Name + " on " + baseAddress + "...");
_waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset);
_completed = 0;
_stopWatch = System.Diagnostics.Stopwatch.StartNew();
ServiceEndpoint endpoint = host.Description.Endpoints.Find(typeof(ISimple));
for (int i = 1; i <= ThreadCount; i++)
{
// ServiceEndpoint is NOT thread safe. Make a copy for each thread.
ServiceEndpoint temp = new ServiceEndpoint(endpoint.Contract, endpoint.Binding, endpoint.Address);
ThreadPool.QueueUserWorkItem(new WaitCallback(CallServiceManyTimes),
new ThreadParms() { ManagedThreadId = i, ServiceEndpoint = temp });
}
_waitHandle.WaitOne();
host.Shutdown();
_done = true;
//Console.WriteLine("All DONE.");
Console.WriteLine(" Type=" + ServiceType.Name + " ThreadCount=" + ThreadCount + " ThreadCallCount=" + ThreadCallCount);
Console.WriteLine(" runtime: " + _stopWatch.ElapsedTicks + " ticks " + _stopWatch.ElapsedMilliseconds + " msec");
}
public void CallServiceManyTimes(object threadParams)
{
ThreadParms p = (ThreadParms)threadParams;
ChannelFactory<ISimple> factory = new ChannelFactory<ISimple>(p.ServiceEndpoint);
ISimple proxy = factory.CreateChannel();
for (int i = 1; i < ThreadCallCount; i++)
{
proxy.Put();
}
((ICommunicationObject)proxy).Shutdown();
factory.Shutdown();
int currentCompleted = Interlocked.Increment(ref _completed);
if (currentCompleted == ThreadCount)
{
_stopWatch.Stop();
_waitHandle.Set();
}
}
}
class Program
{
static void Main(string[] args)
{
BenchmarkService benchmark;
int threadCount = 600;
int threadCalls = 500;
string baseAddress = "net.pipe://localhost/base";
for (int i = 0; i <= 4; i++)
{
benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
}
baseAddress = "http://localhost/base";
for (int i = 0; i <= 4; i++)
{
benchmark = new BenchmarkService(typeof(SingleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
benchmark = new BenchmarkService(typeof(MultipleService), threadCount, threadCalls);
benchmark.Run(baseAddress);
}
Console.WriteLine("Press ENTER to close.");
Console.ReadLine();
}
}
public static class Extensions
{
static public void Shutdown(this ICommunicationObject obj)
{
try
{
if (obj != null)
obj.Close();
}
catch (Exception ex)
{
Console.WriteLine("Shutdown exception: {0}", ex.Message);
obj.Abort();
}
}
}
}