14

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

  • 当我从 VS 中运行程序时,它运行缓慢
  • 当我从内置的 EXE 运行程序时,它运行缓慢
  • 当我通过性能向导/CPU Profiler 运行程序时,它运行得非常快。
  • 当我从构建的 EXE 运行程序,然后启动 VS 并将分析器附加到任何其他进程时,我的程序加速了!

我希望程序总是运行得那么快!

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

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

更新 1 - 8 跟随

--------------------更新1:--------

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

--------------------更新2:--------

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

--------------------更新3:--------

开始截屏到视频也加快了程序的速度!

--------------------更新4:--------

如果我打开英特尔图形驱动程序设置窗口(这个东西: http: //www.intel.com/support/graphics/sb/img/resolution_new.jpg),只是不断地将光标悬停在按钮上,所以它们会发光,等等,我的程序加速了!如果我运行 GPUz 或 Kombustor,它不会加速,所以 GPU 上没有降频 - 它保持稳定的 850Mhz。

--------------------更新5:--------

不同机器上的测试:

- 在配备 Intel HD2000 的 Core i5-2400S 上,UI 运行缓慢,CPU 使用率约为 15%。

- 在同事的 Core 2 Duo 和 Intel G41 Express 上,UI 运行速度很快,但 CPU 使用率约为 90%(这也不正常)

- 在配备专用 Radeon X1650 的 Core i5-2400S 上,UI 运行速度极快,CPU 使用率约为 50%。

--------------------更新6:--------

一段代码显示了我如何更新单个图表(为了便于使用,对其进行graphFFT了封装):ZedGraphControl

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是:

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是:

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();
    }

.

--------------------更新7:--------

只需通过 ANTS 分析器运行它。它告诉我,ZedGraph程序快速时的刷新计数是慢速的两倍。以下是截图: ANTS 慢时截图 蚂蚁快的截图

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

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

--------------------更新8:--------

不幸的是,几天来,我无法重现这个问题......我得到了恒定的可接受速度(这仍然比我两周前在分析器中的速度慢一些),这不受两周前曾经影响它的任何因素 - 分析器、视频捕获或 GPU 驱动程序窗口。我仍然没有解释是什么原因造成的......

4

5 回答 5

9

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

我遇到了完全相同的问题,执行速度非常慢,打开探查器时会加快速度。这个问题在我的电脑上消失了,但似乎随机出现在其他电脑上。我们还注意到在 Chrome 中运行“加入我”窗口时问题消失了。

我的应用程序通过 CAN 总线传输文件。该应用程序加载一个带有 8 个字节数据的 CAN 消息,传输它并等待确认。将计时器设置为 15.6 毫秒,每次往返正好用 15.6 毫秒,整个文件传输大约需要 14 分钟。计时器设置为 1 毫秒,往返时间会有所不同,但会低至 4 毫秒,整个传输时间将降至不到两分钟。

您可以通过以管理员身份打开命令提示符并输入以下内容来验证您的系统计时器分辨率以及找出哪个程序提高了分辨率:

powercfg -energy duration 5

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

Platform Timer Resolution:Platform Timer Resolution 默认的平台计时器分辨率为 15.6ms (15625000ns),应在系统空闲时使用。如果定时器分辨率增加,处理器电源管理技术可能无效。由于多媒体播放或图形动画,计时器分辨率可能会增加。当前定时器分辨率(100ns 单位) 10000 最大定时器周期(100ns 单位) 156001

我当前的分辨率是 1 毫秒(10,000 个 100nS 单位),后面是要求提高分辨率的程序列表。

可以在此处找到此信息以及更多详细信息:https ://randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/

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

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() 的参数相匹配。

于 2016-07-15T19:48:38.863 回答
5

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

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

private void SomeWork() {
  // start the worker thread here
  while(!PollDone()) {
    progressBar1.Value = PollProgress();
    Application.DoEvents(); // keep the GUI responisive
  }
}

放慢速度可以提高性能:

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 调用:

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()可能会导致头疼,导致堆栈爬升,这自然会将第一个动作排在新动作后面,但我要去题外话。

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

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 */ ));
      }
    }
  }
}

工作线程将很难从队列中获取任何东西,因为它一直被填充线程锁定。添加 Sleep() (在锁之外)可以显着加快其他线程:

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);
  }
}

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

我不确定我是否给出了具有代表性的示例(很难想出简单的东西),但我想重点很清楚,将 sleep() 放在正确的位置可以帮助改善其他线程的流程。

---------- Update7 后编辑 -------------

我会LoopDataRefresh()完全删除该线程。而是在窗口中放置一个计时器,间隔至少为 20(如果没有跳过,则为每秒 50 帧):

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() ,它不再需要点列表而是原始数据:

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);
      }
    }
  }

我希望你能正常工作,正如我之前评论的那样,我没有机会编译或检查它,所以那里可能存在一些错误。那里还有更多需要优化的地方,但与跳帧的提升相比,优化应该是微不足道的,并且只有在我们有时间在下一个帧出现之前实际绘制帧时才收集数据。

如果您在iZotope仔细研究视频中的图表,您会注意到它们也在跳帧,有时有点跳动。这一点也不坏,这是您在前台线程和后台工作人员的处理能力之间做出的权衡。

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

---------- 编辑对评论的回答--------

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

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

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

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

看起来线程在不同的时间不同步和不同步,更新消息到达太晚(当 WndProc 完成并进入睡眠一段时间时),然后突然及时一段时间。我对 Ants 不是很熟悉,但它是否有一个并行的线程时间线,包括睡眠时间?您应该能够在这样的视图中看到发生了什么。微软的线程视图工具会派上用场: 在此处输入图像描述

于 2013-05-19T15:53:14.573 回答
0

当我从未听说过或见过类似的事情时;我建议在函数顶部注释掉代码部分/注入返回的常识方法,直到找到产生副作用的逻辑。你知道你的代码,并且可能有一个有根据的猜测从哪里开始切割。其他大部分都作为理智测试并开始添加块。我经常惊讶于发现那些看似不可能跟踪的错误的速度有多快。一旦找到相关代码,您将有更多线索来解决您的问题。

于 2013-05-20T03:12:08.197 回答
0

有一系列潜在的原因。在不说明完整性的情况下,您可以通过以下方式搜索实际原因:

  • 环境变量:另一个答案中的计时器问题只是一个例子。可能对路径和其他变量进行了修改,新变量可以由探查器设置。将当前环境变量写入文件并比较两种配置。尝试查找可疑条目,将它们一一(或组合)取消设置,直到在两种情况下都获得相同的行为。

  • 处理器频率。这很容易在笔记本电脑上发生。节能系统可能会将处理器的频率设置为较低的值以节省能源。某些应用程序可能会“唤醒”系统,从而增加频率。通过性能监视器(permon)检查这一点。

  • 如果应用程序运行得比可能的慢,那么肯定存在一些低效的资源利用。使用分析器对此进行调查!您可以将探查器附加到(慢速)运行的进程,以查看哪些资源未充分利用/过度利用。大多数情况下,执行速度过慢有两大类原因:内存受限和计算受限执行。两者都可以更深入地了解引发放缓的原因。

但是,如果您的应用程序通过附加到分析器实际上改变了它的效率,您仍然可以使用您最喜欢的监视器应用程序来查看哪些性能指标确实发生了变化。同样,perfmon 是你的朋友。

于 2019-10-06T11:02:37.297 回答
-1

如果你有一个抛出大量异常的方法,它可以在调试模式下运行缓慢,在 CPU 分析模式下运行速度快。

如此处详述DebuggerNonUserCode使用该属性可以提高调试性能。例如:

[DebuggerNonUserCode]
public static bool IsArchive(string filename)
{
    bool result = false;
    try
    {
        //this calls an external library, which throws an exception if the file is not an archive
        result = ExternalLibrary.IsArchive(filename);
    }
    catch
    {

    }
    return result;
}
于 2019-05-19T11:28:10.553 回答