在负载下减少 NetNamedPipe 的 CPU 使用

Posted

技术标签:

【中文标题】在负载下减少 NetNamedPipe 的 CPU 使用【英文标题】:reducing the CPU use of NetNamedPipe when under load 【发布时间】:2019-09-09 03:00:04 【问题描述】:

我有一个 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 的使用。也许我错过了什么?

我意识到,很可能,我可能不得不做一些更激烈的事情。我可能需要以“捆绑”的形式发送更少、更大的消息。或者我可能需要使用不同的进程间通信方式。任何有关调查内容的建议将不胜感激。

我的测试程序代码如下。它针对 .NET Framework 4.7.2。我一直在 Windows 10 上运行。

程序.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("\r0\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();
        
    

【问题讨论】:

【参考方案1】:

这就是我最终解决这个问题的方法。

在修复之前,在上述问题的示例代码中,我反复调用SayHello,这样做的开销消耗了大量的 CPU。

修复后,我通过单个Stream 获得相同的数据。我怀疑设置流的 CPU 开销大致相同,但流只需要设置一次。总体 CPU 使用率要低得多。

WCF 命名管道支持流,因此我不必放弃使用命名管道。

您可以阅读有关流式传输 here 的信息,或者如果该链接失效,请将 TransferMode.Streaming 放入您最喜欢的搜索引擎。

我的流需要是“无限的”,这样它才能永远推送数据,所以我需要创建一个自定义 Stream。 This answer on Stack Overflow 帮助指导我。

我还有一些粗糙的地方需要解决,但是 CPU 使用问题(即这个问题的症结所在)似乎已经通过这种方法解决了。

【讨论】:

以上是关于在负载下减少 NetNamedPipe 的 CPU 使用的主要内容,如果未能解决你的问题,请参考以下文章

高负载下gpu内存一直0.几

django web应用runserver模式下cpu占用高解决办法

使用 WCF NetNamedPipe 与客户端通信的 Windows 服务

一文读懂|Linux 进程管理之CFS负载均衡

Linux系统下怎么查看应用CPU、内存、负载?

spark shuffle:分区原理及相关的疑问