在负载下减少 CPU 使用 NetNamedPipe
reducing the CPU use of NetNamedPipe when under load
我有一个 Windows 服务,它使用 NetNamedPipe 与同一台机器上的其他进程进行通信。它工作正常,除了一个问题:CPU 使用率高。我可以做些什么来减少这种使用?
为了更好地理解这个问题,我制作了一个简单的测试程序,它通过命名管道与自身对话并跟踪它自己的 CPU 使用。当不经常使用命名管道时(每秒 1 次操作),CPU 使用率非常低。当频繁使用命名管道(每秒数千次操作)时,CPU 使用增加。
下面是一些演示该行为的示例输出。 (请注意,CPU 使用 Process
> % Processor Time
计数器,这不像您在任务管理器中看到的 CPU 使用那么简单。)
NetNamedPipe Passed: 31309 Failed: 0 Elapsed: 10.4 s Rate: 3000 Hz Process CPU: 30.0 %
NetNamedPipe Passed: 13 Failed: 0 Elapsed: 11.0 s Rate: 1 Hz Process CPU: 0.9 %
理想情况下,我想继续使用 NetNamedPipe,但做一些事情来减少 CPU 的使用。我已经尝试使用 Stack Overflow 和其他地方的想法调整 the optional settings of NetNamedPipeBinding,但无法减少 CPU 的使用。也许我遗漏了什么?
我意识到,很可能,我可能不得不做一些更激烈的事情。我可能需要在 "bundles" 中发送更少、更大的消息。或者我可能需要使用不同的进程间通信方式。任何有关调查内容的建议都将不胜感激。
我的测试程序代码如下。它以 .NET Framework 4.7.2 为目标。我已经 运行 Windows 10.
Program.cs
using System;
using System.Diagnostics;
using System.Threading;
namespace IpcExperiments
{
class Program
{
private static readonly string MyName = "Alice";
private static readonly string ProcessName = "IpcExperiments";
private static readonly double DesiredRate = 3000; // In Hz
// private static readonly double DesiredRate = Double.MaxValue; // Go as fast as possible!
private static PerformanceCounter ProcessCpu = null;
static void Main(string[] args)
{
ProcessCpu = new PerformanceCounter("Process", "% Processor Time", ProcessName);
Test(new Experiments.NetNamedPipe(), MyName, DesiredRate);
// Optionally, add other tests here.
Console.Write("\r ");
Console.WriteLine();
Console.WriteLine("All tests complete! Press Enter to finish.");
Console.ReadLine();
}
private static void Test(Experiments.IIpcExperiment experiment, string myName, double desiredRate = Double.MaxValue)
{
int i = 0;
int successes = 0;
int fails = 0;
double elapsed = 0;
double rate = 0;
double thisCpu = 0;
double avgCpu = 0;
double cpuCount = 0;
string matchingName = String.Format("Hello {0}!", myName);
string experimentName = experiment.GetExperimentName();
Console.Write("\rCreating {0}...", experimentName);
experiment.Setup();
DateTime startTime = DateTime.Now;
DateTime nextCpuRead = DateTime.MinValue;
while (!Console.KeyAvailable)
{
if (experiment.SayHello(myName).Equals(matchingName))
{
successes++;
}
else
{
fails++;
}
if (nextCpuRead < DateTime.Now)
{
thisCpu = ProcessCpu.NextValue();
if (cpuCount == 0)
{
avgCpu = thisCpu;
}
else
{
avgCpu = ((avgCpu * cpuCount) + thisCpu) / (cpuCount + 1);
}
cpuCount++;
nextCpuRead = DateTime.Now.AddSeconds(1);
}
elapsed = (DateTime.Now - startTime).TotalSeconds;
rate = ((double)i) / elapsed;
Console.Write("\r{0}\tPassed: {1}\tFailed: {2}\tElapsed: {3:0.0} s\tRate: {4:0} Hz\t Process CPU: {5:0.0} %"
, experimentName
, successes
, fails
, elapsed
, rate
, avgCpu);
while (rate > desiredRate && !Console.KeyAvailable)
{
Thread.Sleep(1);
elapsed = (DateTime.Now - startTime).TotalSeconds;
rate = ((double)i) / elapsed;
}
i++;
}
Console.ReadKey(true);
Console.WriteLine();
Console.Write("\rDisposing {0}...", experimentName);
experiment.Shutdown();
}
}
}
IIpcExperiment.cs
namespace IpcExperiments.Experiments
{
interface IIpcExperiment
{
string GetExperimentName();
void Setup();
void Shutdown();
string SayHello(string myName);
}
}
NetNamedPipe.cs
using System;
using System.ServiceModel;
namespace IpcExperiments.Experiments
{
[ServiceContract]
public interface INetNamedPipe
{
[OperationContract]
string SayHello(string myName);
}
public class IpcInterface : INetNamedPipe
{
public string SayHello(string myName)
{
return String.Format("Hello {0}!", myName);
}
}
public class NetNamedPipe : IIpcExperiment
{
private ServiceHost Host;
private INetNamedPipe Client;
public void Setup()
{
SetupHost();
SetupClient();
}
public void Shutdown()
{
Host.Close();
}
public string GetExperimentName()
{
return "NetNamedPipe";
}
public string SayHello(string myName)
{
return Client.SayHello(myName);
}
private void SetupHost()
{
Host = new ServiceHost(typeof(IpcInterface),
new Uri[]{
new Uri(@"net.pipe://localhost")
});
NetNamedPipeBinding nnpb = new NetNamedPipeBinding();
Host.AddServiceEndpoint(typeof(INetNamedPipe)
, nnpb
, "NetNamedPipeExample");
Host.Open();
}
private void SetupClient()
{
NetNamedPipeBinding nnpb = new NetNamedPipeBinding();
ChannelFactory<INetNamedPipe> pipeFactory =
new ChannelFactory<INetNamedPipe>(
nnpb,
new EndpointAddress(@"net.pipe://localhost/NetNamedPipeExample"));
Client = pipeFactory.CreateChannel();
}
}
}
最后我是这样解决的。
修复前,在上述问题的示例代码中,我多次调用SayHello
,这样做的开销消耗了大量CPU。
修复后,我通过单个 Stream
获得了相同的数据。我怀疑设置流的 CPU 开销大致相同,但流只需要设置一次 一次 。总体 CPU 使用率要低得多。
WCF 命名管道支持流,因此我不必放弃使用命名管道。
您可以阅读有关流式传输的内容 here,或者如果 link 死了,请将 TransferMode.Streaming
放入您最喜欢的搜索引擎。
我的流需要 "infinite" 才能永远推送数据,所以我需要自定义 Stream
。 This answer on Stack Overflow 帮助指导了我。
我还有一些问题需要解决,但是 CPU 使用问题(即这个问题的关键)似乎已经通过这种方法解决了。
我有一个 Windows 服务,它使用 NetNamedPipe 与同一台机器上的其他进程进行通信。它工作正常,除了一个问题:CPU 使用率高。我可以做些什么来减少这种使用?
为了更好地理解这个问题,我制作了一个简单的测试程序,它通过命名管道与自身对话并跟踪它自己的 CPU 使用。当不经常使用命名管道时(每秒 1 次操作),CPU 使用率非常低。当频繁使用命名管道(每秒数千次操作)时,CPU 使用增加。
下面是一些演示该行为的示例输出。 (请注意,CPU 使用 Process
> % Processor Time
计数器,这不像您在任务管理器中看到的 CPU 使用那么简单。)
NetNamedPipe Passed: 31309 Failed: 0 Elapsed: 10.4 s Rate: 3000 Hz Process CPU: 30.0 %
NetNamedPipe Passed: 13 Failed: 0 Elapsed: 11.0 s Rate: 1 Hz Process CPU: 0.9 %
理想情况下,我想继续使用 NetNamedPipe,但做一些事情来减少 CPU 的使用。我已经尝试使用 Stack Overflow 和其他地方的想法调整 the optional settings of NetNamedPipeBinding,但无法减少 CPU 的使用。也许我遗漏了什么?
我意识到,很可能,我可能不得不做一些更激烈的事情。我可能需要在 "bundles" 中发送更少、更大的消息。或者我可能需要使用不同的进程间通信方式。任何有关调查内容的建议都将不胜感激。
我的测试程序代码如下。它以 .NET Framework 4.7.2 为目标。我已经 运行 Windows 10.
Program.cs
using System;
using System.Diagnostics;
using System.Threading;
namespace IpcExperiments
{
class Program
{
private static readonly string MyName = "Alice";
private static readonly string ProcessName = "IpcExperiments";
private static readonly double DesiredRate = 3000; // In Hz
// private static readonly double DesiredRate = Double.MaxValue; // Go as fast as possible!
private static PerformanceCounter ProcessCpu = null;
static void Main(string[] args)
{
ProcessCpu = new PerformanceCounter("Process", "% Processor Time", ProcessName);
Test(new Experiments.NetNamedPipe(), MyName, DesiredRate);
// Optionally, add other tests here.
Console.Write("\r ");
Console.WriteLine();
Console.WriteLine("All tests complete! Press Enter to finish.");
Console.ReadLine();
}
private static void Test(Experiments.IIpcExperiment experiment, string myName, double desiredRate = Double.MaxValue)
{
int i = 0;
int successes = 0;
int fails = 0;
double elapsed = 0;
double rate = 0;
double thisCpu = 0;
double avgCpu = 0;
double cpuCount = 0;
string matchingName = String.Format("Hello {0}!", myName);
string experimentName = experiment.GetExperimentName();
Console.Write("\rCreating {0}...", experimentName);
experiment.Setup();
DateTime startTime = DateTime.Now;
DateTime nextCpuRead = DateTime.MinValue;
while (!Console.KeyAvailable)
{
if (experiment.SayHello(myName).Equals(matchingName))
{
successes++;
}
else
{
fails++;
}
if (nextCpuRead < DateTime.Now)
{
thisCpu = ProcessCpu.NextValue();
if (cpuCount == 0)
{
avgCpu = thisCpu;
}
else
{
avgCpu = ((avgCpu * cpuCount) + thisCpu) / (cpuCount + 1);
}
cpuCount++;
nextCpuRead = DateTime.Now.AddSeconds(1);
}
elapsed = (DateTime.Now - startTime).TotalSeconds;
rate = ((double)i) / elapsed;
Console.Write("\r{0}\tPassed: {1}\tFailed: {2}\tElapsed: {3:0.0} s\tRate: {4:0} Hz\t Process CPU: {5:0.0} %"
, experimentName
, successes
, fails
, elapsed
, rate
, avgCpu);
while (rate > desiredRate && !Console.KeyAvailable)
{
Thread.Sleep(1);
elapsed = (DateTime.Now - startTime).TotalSeconds;
rate = ((double)i) / elapsed;
}
i++;
}
Console.ReadKey(true);
Console.WriteLine();
Console.Write("\rDisposing {0}...", experimentName);
experiment.Shutdown();
}
}
}
IIpcExperiment.cs
namespace IpcExperiments.Experiments
{
interface IIpcExperiment
{
string GetExperimentName();
void Setup();
void Shutdown();
string SayHello(string myName);
}
}
NetNamedPipe.cs
using System;
using System.ServiceModel;
namespace IpcExperiments.Experiments
{
[ServiceContract]
public interface INetNamedPipe
{
[OperationContract]
string SayHello(string myName);
}
public class IpcInterface : INetNamedPipe
{
public string SayHello(string myName)
{
return String.Format("Hello {0}!", myName);
}
}
public class NetNamedPipe : IIpcExperiment
{
private ServiceHost Host;
private INetNamedPipe Client;
public void Setup()
{
SetupHost();
SetupClient();
}
public void Shutdown()
{
Host.Close();
}
public string GetExperimentName()
{
return "NetNamedPipe";
}
public string SayHello(string myName)
{
return Client.SayHello(myName);
}
private void SetupHost()
{
Host = new ServiceHost(typeof(IpcInterface),
new Uri[]{
new Uri(@"net.pipe://localhost")
});
NetNamedPipeBinding nnpb = new NetNamedPipeBinding();
Host.AddServiceEndpoint(typeof(INetNamedPipe)
, nnpb
, "NetNamedPipeExample");
Host.Open();
}
private void SetupClient()
{
NetNamedPipeBinding nnpb = new NetNamedPipeBinding();
ChannelFactory<INetNamedPipe> pipeFactory =
new ChannelFactory<INetNamedPipe>(
nnpb,
new EndpointAddress(@"net.pipe://localhost/NetNamedPipeExample"));
Client = pipeFactory.CreateChannel();
}
}
}
最后我是这样解决的。
修复前,在上述问题的示例代码中,我多次调用SayHello
,这样做的开销消耗了大量CPU。
修复后,我通过单个 Stream
获得了相同的数据。我怀疑设置流的 CPU 开销大致相同,但流只需要设置一次 一次 。总体 CPU 使用率要低得多。
WCF 命名管道支持流,因此我不必放弃使用命名管道。
您可以阅读有关流式传输的内容 here,或者如果 link 死了,请将 TransferMode.Streaming
放入您最喜欢的搜索引擎。
我的流需要 "infinite" 才能永远推送数据,所以我需要自定义 Stream
。 This answer on Stack Overflow 帮助指导了我。
我还有一些问题需要解决,但是 CPU 使用问题(即这个问题的关键)似乎已经通过这种方法解决了。