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

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

  • 当我从VS内部运行程序时,它运行缓慢
  • 当我从内置的EXE运行程序时,它运行缓慢
  • 当我通过Performance Wizard / CPU Profiler运行程序时,它会运行Blazing Fast。
  • 当我从构建的EXE运行程序,然后启动VS并将探查器附加到任何其他进程时,我的程序加速了!

我希望程序能够快速运行!

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

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

更新1 – 8跟随

——————– UPDATE1:——————–

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

——————– UPDATE2:——————–

在虚拟机中运行该程序,程序仍然运行缓慢,并且从主机运行的探查器不会使它快速。

——————– UPDATE3:——————–

启动屏幕捕获到video也可以加快程序的速度!

——————– UPDATE4:——————–

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

——————– Update5:——————–

测试不同的机器:

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

– 在配备英特尔G41 Express的同事Core 2 Duo上,UI运行速度很快,但CPU使用率约为90%(这也不正常)

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

——————– Update6:——————–

一段代码显示我如何更新单个图形( graphFFTgraphFFT的封装,易于使用):

 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 points = new List(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 values) { IPointListEdit ip = zgcGraph.GraphPane.CurveList[lineTitle].Points as IPointListEdit; int tmp = Math.Min(ip.Count, values.Count); int i = 0; while(i  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  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(); } 

——————– Update7:——————–

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

我发现非常奇怪,考虑到截面长度的微小差异,性能与数学精度相差两次。

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

——————– Update8:——————–

不幸的是,几天之后,我无法重现这个问题…我的速度一直是可接受的(这仍然比我两周前在剖析器中的速度慢一点),这个速度不受影响两周前用于影响它的任何因素 – 分析器,video捕获或GPU驱动程序窗口。 我仍然没有解释导致它的原因……

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

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

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

调用Application.DoEvents()可能会在GUI内容未被禁用且用户同时第二次启动其他事件或相同事件时引起一系列令人头疼的问题,从而导致堆栈爬升本质上排队后面的第一个操作,但是我要离题了。

可能这个例子太具体了,我会尝试做一个更一般的例子。 如果你的线程正在填充由其他线程处理的缓冲区,请确保在循环中留下一些System.Threading.Thread.Sleep()松弛,以允许其他线程在检查缓冲区是否需要之前进行一些处理再次填补:

 public class WorkItem { // populate with something usefull } public static object WorkItemsSyncRoot = new object(); public static Queue workitems = new Queue(); 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); } } 

在某些情况下,连接探查器可能与睡眠function具有相同的效果。

我不确定我是否给出了有代表性的例子(很难想出一些简单的东西)但我想这一点很明确,将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); } } } 

我希望你能够正常工作,正如我之前评论过的那样,我没有机会编译或检查它,因此可能存在一些错误。 还有更多需要优化的地方,但与跳过帧的增强相比,优化应该是边际的,并且只有在我们有时间在下一个帧进入之前实际绘制帧时才收集数据。

如果你仔细研究iZotopevideo中的图形,你会发现它们也在跳帧,有时候会有些跳跃。 这一点都不错,这是你在前台线程的处理能力和后台工作者之间做出的权衡。

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

----------编辑答案备注--------

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

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

你没有看到另一个rootnode的原因是因为第3个下拉框选择了“[604] Mian Thread”,我之前没有注意到。

至于更流畅的图表,在仔细观察屏幕截图后,我对此有了第二个想法。 主线程清楚地收到了更多(双重)更新消息,并且CPU仍然有一些空间。

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

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

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

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

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

powercfg -energy duration 5

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

平台定时器分辨率:平台定时器分辨率默认平台定时器分辨率为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毫秒? ):

 public static class WinApi { /// TimeBeginPeriod(). See the Windows API documentation for details. [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); /// TimeEndPeriod(). See the Windows API documentation for details. [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()的参数匹配。

当我从未听过或看到类似的东西时; 我建议使用常识方法来注释代码部分/在函数顶部注入返回,直到找到产生副作用的逻辑。 你知道你的代码,可能有一个有根据的猜测从哪里开始砍。 除此之外,所有人都将其作为一项健全测试并开始添加块。 我常常惊讶于人们能够找到那些看似不可能跟踪的错误。 找到相关代码后,您将有更多线索来解决您的问题。