首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >为什么我的C#程序在分析器中更快?

为什么我的C#程序在分析器中更快?
EN

Stack Overflow用户
提问于 2013-05-17 15:01:17
回答 5查看 3.9K关注 0票数 14

我有一个相对较大的系统(到目前为止大约有25000条线路)用于监测与无线电有关的设备.它使用最新版本的ZedGraph显示图表等。该程序是用C#在VS2010上用Win7编写的。问题是:

  • 当我从VS内部运行程序时,它运行得很慢。
  • 当我从构建的EXE运行程序时,它运行得很慢。
  • 当我通过性能向导/ CPU分析器运行程序时,它会快速运行。
  • 当我从构建的EXE运行程序,然后启动VS和附加一个分析器到任何其他进程,我的程序加速!

我想让程序一直运行得这么快!

解决方案中的每个项目都被设置为发布,调试托管代码被禁用,定义调试和跟踪常量被禁用,优化代码--我尝试过,警告级别--我尝试过,抑制了JIT --我尝试了,总之,我尝试了在StackOverflow上已经提出的所有解决方案--都没有工作。程序外部分析器速度慢,在分析器中速度快。我不认为问题在我的代码中,因为如果我将分析器附加到其他不相关的过程中,它会变得很快!

请帮帮我!我真的需要它在任何地方都这么快,因为它是一个业务关键的应用程序,性能问题是不能容忍的。

更新1-8跟随

--------------------Update1:--------------------

这个问题似乎与ZedGraph无关,因为在我用自己的基本绘图替换了ZedGraph之后,它仍然表现出来。

--------------------Update2:--------------------

在虚拟机中运行程序时,程序仍然运行缓慢,从主机运行分析器并不能使其运行得很快。

--------------------Update3:--------------------

启动屏幕截图视频也加快了程序的速度!

--------------------Update4:--------------------

如果我打开英特尔图形驱动程序设置窗口(这件事:new.jpg),只是不停地用光标悬停在按钮上,所以它们会发光等等,我的程序就会加速!不过,如果我运行GPUz或Kombustor,它不会加速,所以GPU上也不会出现下行现象--它将保持稳定的850 the。

--------------------Update5:--------------------

在不同机器上的测试:

-On我的核心i5-2400S与英特尔HD2000,UI运行缓慢,CPU使用率约为15%。

-On一个同事的核心2二重奏与英特尔的G41快车,UI运行很快,但使用的CPU是90%(这也是不正常的)

-On内核i5-2400S具有专用的Radeon X1650,UI运行速度快,CPU使用率约为50%。

--------------------Update6:--------------------

显示如何更新单个图的代码片段(graphFFT是对ZedGraphControl的封装,以便于使用):

代码语言:javascript
复制
public void LoopDataRefresh() //executes in a new thread
        {
            while (true)
            {
                while (!d.Connected)
                    Thread.Sleep(1000);
                if (IsDisposed)
                    return;
//... other graphs update here
                if (signalNewFFT && PanelFFT.Visible)
                {
                    signalNewFFT = false;
                    #region FFT
                    bool newRange = false;
                    if (graphFFT.MaxY != d.fftRangeYMax)
                    {
                        graphFFT.MaxY = d.fftRangeYMax;
                        newRange = true;
                    }
                    if (graphFFT.MinY != d.fftRangeYMin)
                    {
                        graphFFT.MinY = d.fftRangeYMin;
                        newRange = true;
                    }

                    List<PointF> points = new List<PointF>(2048);
                    int tempLength = 0;
                    short[] tempData = new short[2048];

                    int i = 0;

                    lock (d.fftDataLock)
                    {
                        tempLength = d.fftLength;
                        tempData = (short[])d.fftData.Clone();
                    }
                    foreach (short s in tempData)
                        points.Add(new PointF(i++, s));

                    graphFFT.SetLine("FFT", points);

                    if (newRange)
                        graphFFT.RefreshGraphComplete();
                    else if (PanelFFT.Visible)
                        graphFFT.RefreshGraph();

                    #endregion
                }
//... other graphs update here
                Thread.Sleep(5);
            }
        }

SetLine是:

代码语言:javascript
复制
public void SetLine(String lineTitle, List<PointF> values)
    {
        IPointListEdit ip = zgcGraph.GraphPane.CurveList[lineTitle].Points as IPointListEdit;
        int tmp = Math.Min(ip.Count, values.Count);
        int i = 0;
        while(i < tmp)
        {
            if (values[i].X > peakX)
                peakX = values[i].X;
            if (values[i].Y > peakY)
                peakY = values[i].Y;
            ip[i].X = values[i].X;
            ip[i].Y = values[i].Y;
            i++;
        }
        while(ip.Count < values.Count)
        {
            if (values[i].X > peakX)
                peakX = values[i].X;
            if (values[i].Y > peakY)
                peakY = values[i].Y;
            ip.Add(values[i].X, values[i].Y);
            i++;
        }
        while(values.Count > ip.Count)
        {
            ip.RemoveAt(ip.Count - 1);
        }
    }

RefreshGraph是:

代码语言:javascript
复制
public void RefreshGraph()
    {
        if (!explicidX && autoScrollFlag)
        {
            zgcGraph.GraphPane.XAxis.Scale.Max = Math.Max(peakX + grace.X, rangeX);
            zgcGraph.GraphPane.XAxis.Scale.Min = zgcGraph.GraphPane.XAxis.Scale.Max - rangeX;
        }
        if (!explicidY)
        {
            zgcGraph.GraphPane.YAxis.Scale.Max = Math.Max(peakY + grace.Y, maxY);
            zgcGraph.GraphPane.YAxis.Scale.Min = minY;
        }
        zgcGraph.Refresh();
    }

--------------------Update7:--------------------

只是查了一下蚂蚁分析器。它告诉我,当程序快的时候,ZedGraph刷新的计数要比慢的时候高两倍。以下是截图:

我觉得很奇怪,考虑到截面长度的微小差异,在数学精度上,性能相差两倍。

另外,我更新了GPU驱动程序,这没有帮助。

--------------------Update8:--------------------

不幸的是,有几天,我无法重现这个问题.我得到了稳定的可接受的速度(这看起来比两周前我在分析器中的速度慢一点),它不受两周前影响它的任何因素的影响--分析器、视频捕捉或GPU驱动窗口。我仍然无法解释是什么导致了它..。

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2016-07-15 19:48:38

Luaan在上面的评论中发布了解决方案,这是系统范围内的计时器分辨率。默认分辨率为15.6ms,分析器将分辨率设置为1ms。

我也遇到了同样的问题,执行速度非常慢,当分析器打开时会加快速度。这个问题在我的个人电脑上消失了,但在其他PC上似乎是随机出现的。我们还注意到,在Chrome中运行一个Join Me窗口时,这个问题消失了。

我的应用程序通过CAN总线传输文件。该应用程序加载一条包含8个字节数据的CAN消息,并将其发送并等待确认。当计时器设置为15.6ms时,每次往返只需15.6ms,整个文件传输大约需要14分钟。当计时器设置为1ms时,往返时间会变化,但最低可达4ms,整个传输时间将下降到不到2分钟。

您可以验证您的系统定时器解析,并通过以管理员身份打开命令提示符并输入:

powercfg -energy duration 5

输出文件将包含以下内容:

平台定时器解析:平台定时器解析默认平台计时器分辨率为15.6ms (15625000 is ),系统空闲时应使用。如果计时器分辨率提高,处理器电源管理技术可能不会有效。由于多媒体回放或图形动画,计时器分辨率可以增加。当前计时器分辨率(100单位) 10000最大定时器周期(100单位) 156001

我目前的分辨率是1ms (10,000单位100 is ),然后是要求增加分辨率的程序列表。

这些信息以及更多的细节可以在这里找到:https://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/

下面是一些提高计时器分辨率的代码(最初发布的是这个问题的答案:如何将定时器分辨率从C#设置为1ms?):

代码语言:javascript
复制
public static class WinApi
{
    /// <summary>TimeBeginPeriod(). See the Windows API documentation for details.</summary>

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
    [DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)]

    public static extern uint TimeBeginPeriod(uint uMilliseconds);

    /// <summary>TimeEndPeriod(). See the Windows API documentation for details.</summary>

    [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Interoperability", "CA1401:PInvokesShouldNotBeVisible"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2118:ReviewSuppressUnmanagedCodeSecurityUsage"), SuppressUnmanagedCodeSecurity]
    [DllImport("winmm.dll", EntryPoint = "timeEndPeriod", SetLastError = true)]

    public static extern uint TimeEndPeriod(uint uMilliseconds);
}

这样使用它可以提高分辨率:WinApi.TimeBeginPeriod(1);

如下所示,返回默认值:WinApi.TimeEndPeriod(1);

传递给TimeEndPeriod()的参数必须与传递给TimeBeginPeriod()的参数匹配。

票数 9
EN

Stack Overflow用户

发布于 2013-05-19 15:53:14

在有些情况下,减缓线程可以显著加快其他线程的速度,通常是当一个线程频繁轮询或锁定某些公共资源时。

例如,当主线程在一个紧密的循环中而不是使用计时器检查总体进度时(这是一个windows窗体示例):

代码语言:javascript
复制
private void SomeWork() {
  // start the worker thread here
  while(!PollDone()) {
    progressBar1.Value = PollProgress();
    Application.DoEvents(); // keep the GUI responisive
  }
}

放慢速度可以提高性能:

代码语言:javascript
复制
private void SomeWork() {
  // start the worker thread here
  while(!PollDone()) {
    progressBar1.Value = PollProgress();
    System.Threading.Thread.Sleep(300); // give the polled thread some time to work instead of responding to your poll
    Application.DoEvents(); // keep the GUI responisive
  }
}

正确地执行此操作时,应该避免同时使用DoEvents调用:

代码语言:javascript
复制
private Timer tim = new Timer(){ Interval=300 };

private void SomeWork() {
  // start the worker thread here
  tim.Tick += tim_Tick;
  tim.Start();
}

private void  tim_Tick(object sender, EventArgs e){
  tim.Enabled = false; // prevent timer messages from piling up
  if(PollDone()){
    tim.Tick -= tim_Tick;
    return;
  }
  progressBar1.Value = PollProgress();
  tim.Enabled = true;
}

当GUI内容没有被禁用,用户第二次同时启动其他事件或同一事件时,调用Application.DoEvents()可能会导致分配问题,从而导致堆栈爬升,而堆栈爬升自然会使新事件后面的第一个动作排队,但我将不再讨论这个话题。

该示例可能过于特定于winforms,我将尝试制作一个更一般的示例。如果有一个正在填充由其他线程处理的缓冲区的线程,请确保在循环中留下一些System.Threading.Thread.Sleep()间隙,以便其他线程在检查缓冲区是否需要再次填充之前进行一些处理:

代码语言:javascript
复制
public class WorkItem { 
  // populate with something usefull
}

public static object WorkItemsSyncRoot = new object();
public static Queue<WorkItem> workitems = new Queue<WorkItem>();

public void FillBuffer() {
  while(!done) {
    lock(WorkItemsSyncRoot) {
      if(workitems.Count < 30) {
        workitems.Enqueue(new WorkItem(/* load a file or something */ ));
      }
    }
  }
}

由于工作线程一直被填充线程锁定,因此很难从队列中获取任何内容。添加睡眠()(锁外)可以显著加快其他线程:

代码语言:javascript
复制
public void FillBuffer() {
  while(!done) {
    lock(WorkItemsSyncRoot) {
      if(workitems.Count < 30) {
        workitems.Enqueue(new WorkItem(/* load a file or something */ ));
      }
    }
    System.Threading.Thread.Sleep(50);
  }
}

在某些情况下,连接分析器可能会产生与睡眠功能相同的效果。

我不确定我是否给出了一些有代表性的例子(很难想出一些简单的东西),但我想要点是很清楚的,把睡眠()放在正确的位置可以帮助改进其他线程的流程。

我会完全删除那个LoopDataRefresh()线程。相反,在窗口中放置至少20帧的计时器(如果没有跳过,则为每秒50帧):

代码语言:javascript
复制
private void tim_Tick(object sender, EventArgs e) {
  tim.Enabled = false; // skip frames that come while we're still drawing
  if(IsDisposed) {
    tim.Tick -= tim_Tick;
    return;
  }

  // Your code follows, I've tried to optimize it here and there, but no guarantee that it compiles or works, not tested at all

  if(signalNewFFT && PanelFFT.Visible) {
    signalNewFFT = false;

    #region FFT
    bool newRange = false;
    if(graphFFT.MaxY != d.fftRangeYMax) {
      graphFFT.MaxY = d.fftRangeYMax;
      newRange = true;
    }
    if(graphFFT.MinY != d.fftRangeYMin) {
      graphFFT.MinY = d.fftRangeYMin;
      newRange = true;
    }

    int tempLength = 0;
    short[] tempData;

    int i = 0;

    lock(d.fftDataLock) {
      tempLength = d.fftLength;
      tempData = (short[])d.fftData.Clone();
    }

    graphFFT.SetLine("FFT", tempData);

    if(newRange) graphFFT.RefreshGraphComplete();
    else if(PanelFFT.Visible) graphFFT.RefreshGraph();
    #endregion

    // End of your code

    tim.Enabled = true; // Drawing is done, allow new frames to come in.
  }
}

下面是优化的SetLine(),它不再获取点的列表,而是获取原始数据:

代码语言:javascript
复制
public class GraphFFT {
    public void SetLine(String lineTitle, short[] values) {
      IPointListEdit ip = zgcGraph.GraphPane.CurveList[lineTitle].Points as IPointListEdit;
      int tmp = Math.Min(ip.Count, values.Length);
      int i = 0;
      peakX = values.Length;

      while(i < tmp) {
        if(values[i] > peakY) peakY = values[i];
        ip[i].X = i;
        ip[i].Y = values[i];
        i++;
      }
      while(ip.Count < values.Count) {
        if(values[i] > peakY) peakY = values[i];
        ip.Add(i, values[i]);
        i++;
      }
      while(values.Count > ip.Count) {
        ip.RemoveAt(ip.Count - 1);
      }
    }
  }

我希望你能做到这一点,就像我之前评论的那样,我没有机会编译或检查它,所以那里可能会有一些bug。还有更多需要优化的地方,但与跳过帧相比,优化应该是微不足道的,只有当我们有时间在下一个框架开始之前实际绘制框架时,才能收集数据。

如果你仔细研究iZotope视频中的图表,你会发现它们也跳过了帧,有时还有点不稳定。这一点也不错,这是前台线程的处理能力和后台工作人员之间的权衡。

如果您真的希望在一个单独的线程中完成绘图,则必须将该图形绘制到位图(调用drawing ()并传递位图设备上下文)。然后将位图传递到主线程并使其更新。这样,您确实失去了IDE中的设计器和属性网格的便利性,但是您可以使用其他空置的处理器核心。

-编辑答复

是的,有一种方法可以说什么叫什么。看你的第一个屏幕截图,你已经选择了“呼叫树”图。每一行都有一点跳跃(这是一个树视图,而不仅仅是一个列表!)在调用图中,每个树节点表示由其父树节点(方法)调用的方法。

在第一个映像中,WndProc被调用了大约1800次,它处理了872条消息,其中62条触发了ZedGraphControl.OnPaint() (依次占主线程总时间的53% )。

您没有看到另一个根节点的原因,是因为第三个下拉框选择了"604 Mian Thread“,这一点我以前没有注意到。

至于更流畅的图表,我对此有第二想法,现在看了更仔细的屏幕截图。主线程显然收到了更多(双)更新消息,CPU仍然有一些空间。

看起来线程在不同的时间出现了不同步和不同步的情况,更新消息到达的太晚了(当WndProc完成并休眠了一段时间),然后突然及时到达了一段时间。我对蚂蚁不是很熟悉,但是它是否有一个并行的时间线,包括睡眠时间?你应该能在这样的视野里看到正在发生的事情。Microsofts 螺纹视图工具会在这方面派上用场:

票数 5
EN

Stack Overflow用户

发布于 2013-05-20 03:12:08

当我从未听说或看到类似的东西时,我会推荐一种常识方法,在函数顶部注释掉代码/注入返回,直到找到产生副作用的逻辑为止。您知道您的代码,并可能有一个受过教育的猜测,从哪里开始切割。否则,大部分都是作为一个健全的测试,并开始添加块回来。我经常感到惊讶的是,人们能如此快地找到那些看似不可能的bug来追踪。一旦您找到相关代码,您将有更多的线索来解决您的问题。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/16612236

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档