首页 > 解决方案 > 在负载下减少 NetNamedPipe 的 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 和其他地方的想法调整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("\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();
        }
    }
}

标签: c#wcfipcnamed-pipes

解决方案


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

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

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

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

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

我的流需要是“无限的”,这样它才能永远推送数据,所以我需要自定义一个Stream. Stack Overflow 上的这个答案帮助指导了我。

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


推荐阅读