Cụm từ khóa trong việc đọc tuyên bố của Lowy là “vì lợi ích của thông lượng”. Lowy đang chỉ ra rằng khi sử dụng ConcurrencyMode.Single WCF sẽ thực hiện một cách mù quáng một khóa để thực thi tuần tự hóa đối với cá thể dịch vụ. Ổ khóa đắt tiền và điều này là không cần thiết vì PerCall đã đảm bảo rằng luồng thứ hai sẽ không bao giờ cố gắng gọi cùng một cá thể dịch vụ.
Về mặt hành vi: Đồng thờiMã không quan trọng đối với trường hợp dịch vụ PerCall.
Xét về hiệu suất: dịch vụ Một PerCall đó là ConcurrencyMode.Multiple nên hơi nhanh hơn bởi vì nó không tạo ra và mua lại (không cần thiết) khóa chủ đề đó ConcurrencyMode.Single đang sử dụng.
Tôi đã viết chương trình điểm chuẩn nhanh để xem liệu tôi có thể đo lường tác động hiệu suất của Đơn so với nhiều cho dịch vụ PerCall: Điểm chuẩn không có sự khác biệt có ý nghĩa.
Tôi dán vào mã bên dưới nếu bạn muốn tự mình chạy.
trường hợp thử nghiệm tôi đã cố gắng:
- 600 chủ đề kêu gọi một dịch vụ 500 lần
- 200 chủ đề kêu gọi một dịch vụ 1000 lần
- 8 chủ đề kêu gọi một dịch vụ 10000 lần
- 1 thread gọi một dịch vụ 10000 lần
Tôi chạy nó trên một CPU 4 CPU chạy dịch vụ 2008 R2. Tất cả trừ trường hợp 1 luồng là CPU bị ràng buộc.
Kết quả: Tất cả các lần chạy đều nằm trong khoảng 5% của mỗi khác. Đôi khi ConcurrencyMode.Multiple nhanh hơn. Đôi khi ConcurrencyMode.Single nhanh hơn. Có lẽ một phân tích thống kê thích hợp có thể chọn một người chiến thắng. Theo tôi, chúng đủ gần để không quan trọng.
Dưới đây là một đầu ra tiêu biểu:
Bắt đầu Độc Dịch vụ trên net.pipe: // localhost/cơ sở ... Loại = SingleService THREADCOUNT = 600 = 500 ThreadCallCount runtime: 45.156.759 ve 12.615 msec
Bắt đầu Nhiều Dịch vụ trên net.pipe: // localhost/cơ sở ... Loại = MultipleService THREADCOUNT = 600 = 500 ThreadCallCount r untime: 48731273 ticks 13.613 msec
Bắt đầu Độc Dịch vụ trên net.pipe: // localhost/cơ sở ... Loại = SingleService THREADCOUNT = 600 = 500 ThreadCallCount runtime: 48.701.509 ve 13.605 msec
Bắt đầu Nhiều Dịch vụ trên net.pipe: // localhost/cơ sở ... Loại = MultipleService THREADCOUNT = 600 = 500 ThreadCallCount runtime: 48.590.336 ve 13.574 msec
Benchmark Code:
báo trước thông thường: Đây là mã chuẩn mà có vết cắt ngắn mà không phải là thích hợp cho việc sử dụng sản xuất.
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();
}
}
}
}
Câu hỏi hay. Bạn đã cân nhắc việc xây dựng ứng dụng này trong ứng dụng giao diện điều khiển và thử nghiệm nó để xem? –