Dan*_*iel 14 .net c# performance profiler zedgraph
我有一个相对较大的系统(到目前为止约25000行)用于监控无线电相关设备.它使用最新版本的ZedGraph显示图表等.该程序使用Win7上的VS2010上的C#进行编码.问题是:
我希望程序能够快速运行!
解决方案中的每个项目都设置为RELEASE,Debug非托管代码为DISABLED,定义DEBUG和TRACE常量为DISABLED,优化代码 - 我尝试了,警告级别 - 我试过,抑制JIT - 我试过,或者我试过所有StackOverflow上已经提出的解决方案 - 没有成功.程序在分析器外部很慢,在分析器中很快.我不认为问题出在我的代码中,因为如果我将探查器附加到其他不相关的进程中它会变得很快!
请帮忙!我真的需要它在各地快速,因为它是一个关键业务应用程序和性能问题是不能容忍的...
更新1 - 8跟随
-------------------- UPDATE1:--------------------
问题似乎与ZedGraph无关,因为在我用自己的基本绘图替换ZedGraph之后它仍然存在.
-------------------- UPDATE2:--------------------
在虚拟机中运行该程序,该程序仍然运行缓慢,并且从主机运行的探查器不会使它快速.
-------------------- UPDATE3:--------------------
启动屏幕捕获到视频也可以加快程序的速度!
-------------------- UPDATE4:--------------------
如果我打开英特尔图形驱动程序设置窗口(这个东西:http://www.intel.com/support/graphics/sb/img/resolution_new.jpg),只是不断地将光标悬停在按钮上,这样它们就会发光,等等,我的节目加快了!如果我运行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:--------------------
一段代码,显示我如何更新单个图形(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);
}
}
Run Code Online (Sandbox Code Playgroud)
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);
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
Run Code Online (Sandbox Code Playgroud)
.
-------------------- Update7:--------------------
只需通过ANTS探查器运行它.它告诉我,ZedGraph程序快速时的刷新计数恰好是慢速时的两倍.以下是截图:

我觉得非常奇怪,考虑到截面长度的微小差异,性能与数学精度相差两次.
此外,我更新了GPU驱动程序,但没有帮助.
-------------------- Update8:--------------------
不幸的是,几天之后,我无法重现这个问题......我的速度一直是可接受的(这仍然比我两周前在剖析器中的速度慢一点),这个速度不受影响两周前用于影响它的任何因素 - 分析器,视频捕获或GPU驱动程序窗口.我仍然没有解释导致它的原因......
小智 8
Luaan 在上面的评论中发布了解决方案,这是系统范围的计时器分辨率。默认分辨率为 15.6 毫秒,分析器将分辨率设置为 1 毫秒。
我遇到了完全相同的问题,执行速度非常慢,打开分析器时会加快速度。问题在我的 PC 上消失了,但在其他 PC 上似乎是随机出现的。我们还注意到在 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 ms(10,000 个单位的 100nS),后面是要求提高分辨率的程序列表。
可以在此处找到此信息以及更多详细信息:https : //randomascii.wordpress.com/2013/07/08/windows-timer-resolution-megawatts-wasted/
这是一些增加计时器分辨率的代码(最初发布为这个问题的答案:如何将计时器分辨率从 C# 设置为 1 毫秒?):
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);
}
Run Code Online (Sandbox Code Playgroud)
像这样使用它来提高分辨率:WinApi.TimeBeginPeriod(1);
并像这样返回默认值:WinApi.TimeEndPeriod(1);
传递给 TimeEndPeriod() 的参数必须与传递给 TimeBeginPeriod() 的参数匹配。
在某些情况下,减慢线程可以显着加快其他线程的速度,通常是在一个线程轮询或频繁锁定某些公共资源时.
例如(这是一个Windows窗体示例)当主线程在紧密循环中检查整体进度而不是使用计时器时,例如:
private void SomeWork() {
// start the worker thread here
while(!PollDone()) {
progressBar1.Value = PollProgress();
Application.DoEvents(); // keep the GUI responisive
}
}
Run Code Online (Sandbox Code Playgroud)
减慢速度可以提高性能:
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
}
}
Run Code Online (Sandbox Code Playgroud)
正确地做,应该避免一起使用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;
}
Run Code Online (Sandbox Code Playgroud)
Application.DoEvents()当GUI内容没有被禁用并且用户同时开始其他事件或同一事件时,调用可能会导致分配令人头疼的问题,导致堆栈爬升本质上排队新的后面的第一个动作,但我要去无关.
可能这个例子太具体了,我会尝试做一个更一般的例子.如果你的线程正在填充由其他线程处理的缓冲区,请确保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 */ ));
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
工作线程很难从队列中获取任何东西,因为它经常被填充线程锁定.添加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);
}
}
Run Code Online (Sandbox Code Playgroud)
在某些情况下,连接分析器可以与睡眠功能具有相同的效果.
我不确定我是否给出了代表性的例子(很难想出一些简单的东西)但我想这一点很清楚,将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.
}
}
Run Code Online (Sandbox Code Playgroud)
这是优化的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);
}
}
}
Run Code Online (Sandbox Code Playgroud)
我希望你能够正常工作,正如我之前评论过的那样,我没有机会编译或检查它,因此可能存在一些错误.还有更多需要优化的地方,但与跳过帧的增强相比,优化应该是边际的,只有当我们有时间在下一个帧进入之前实际绘制帧时才收集数据.
如果您仔细研究iZotope视频中的图形,您会发现它们也在跳帧,有时候会有点跳跃.这一点都不错,这是你在前台线程的处理能力和后台工作者之间做出的权衡.
如果您真的希望绘图在单独的线程中完成,则必须将图形绘制到位图(调用Draw()并传递位图设备上下文).然后将位图传递给主线程并让它更新.这样,您确实会失去IDE中设计器和属性网格的便利性,但您可以利用其他空置的处理器内核.
----------编辑答案备注--------
是的,有一种方法可以说出什么叫什么.看看你的第一个屏幕截图,你已经选择了"调用树"图.每个下一行都跳了一下(这是一个树视图,而不仅仅是一个列表!).在调用图中,每个树节点表示由其父树节点(方法)调用的方法.
在第一张图片中,WndProc被称为约1800次,它处理了872条消息,其中62条被触发ZedGraphControl.OnPaint()(这反过来占主线程总时间的53%).
你没有看到另一个rootnode的原因是因为第3个下拉框选择了"[604] Mian Thread",我之前没有注意到.
至于更流畅的图表,在仔细观察屏幕截图后,我现在有了第二个想法.主线程明显收到更多(双)更新消息,CPU仍有一些空间.
看起来线程在不同的时间是不同步和同步的,更新消息到达的时间太晚(当WndProc完成并进入休眠状态一段时间),然后突然及时一段时间.我对蚂蚁不是很熟悉,但是它有一个并排的线程时间表,包括睡眠时间吗?你应该能够看到这种观点中发生了什么.微软线程视图工具将派上用场:

| 归档时间: |
|
| 查看次数: |
2371 次 |
| 最近记录: |