良心与思想

偶的代码生涯
随笔 - 32, 评论 - 243, 引用 - 31

导航

关于

最近在学习windbg

标签

每月存档

最新留言

广告

用perfmon简单分析GDI+性能和代码的一点小改进

Paint事件被触发了多少次?比较简单的方式,我们自己做一个perfmon能用的counter。看代码:

 1        private void button1_Click(object sender, EventArgs e)
 2        {
 3            
 4            if (!PerformanceCounterCategory.Exists("GDI+ Monitor"))
 5            {
 6                CounterCreationDataCollection ccdc = new CounterCreationDataCollection();
 7
 8                CounterCreationData totalPaint = new CounterCreationData();
 9                totalPaint.CounterName = "# operator executed";
10                totalPaint.CounterHelp = "Counts of OnPaint events called";
11                totalPaint.CounterType = PerformanceCounterType.NumberOfItems32;
12
13                ccdc.Add(totalPaint);
14
15                PerformanceCounterCategory.Create("GDI+ Monitor""Some counters for GDI+ objects", PerformanceCounterCategoryType.MultiInstance, ccdc);
16            }

17        }

好,点一下button1之后,我们就可以在perfmon中看到GDI+ Monitor这个category了,然后只有一个counter,就是# operator executed

然后在winform中增加这么一段:
           paintcall = new PerformanceCounter();
            paintcall.CategoryName 
= "GDI+ Monitor";
            paintcall.CounterName 
= "# operator executed";
            paintcall.MachineName 
= ".";
            paintcall.ReadOnly 
= false;

好了,代码里面可以用paintcall这个变量了。我们现在的需求是监视Paint被触发了多少次,那么可以在代码中这么写:
 1            Graphics g2 = e.Graphics;
 2
 3            Bitmap bmp = new Bitmap(this.Width, this.Height);
 4            Graphics g = Graphics.FromImage(bmp);
 5
 6            paintcall.Increment();
 7
 8            Rectangle r = new Rectangle(00this.Width, this.Height);
 9            g.FillRectangle(new LinearGradientBrush(r, Color.Red, Color.Blue, LinearGradientMode.BackwardDiagonal), r);
10
11            g2.DrawImage(bmp, new Point(00));
12
13            g.Dispose();
14            g = null;
15
16            bmp.Dispose();
17            bmp = null;

看上面第6行,这句会把paint call增加一。当然,其他方法有很多,这里不写了。
好,跑一下perfmon,然后把我们新增加的counter add上,嗯,可以看到当窗口无效的时候,计数器就增加了。

有一个小的细节,当窗口稍微改动大小的时候,你会发现Paint被调用了多次,这个实在很郁闷。so,稍微做点手脚:
 1        private bool resize = false;
 2        private void Form1_ResizeEnd(object sender, EventArgs e)
 3        {
 4            resize = false;
 5            this.Invalidate();
 6        }

 7
 8        private void Form1_ResizeBegin(object sender, EventArgs e)
 9        {
10            resize = true;
11        }

然后我们修改一下Paint事件的代码如下:
 1        private void Form1_Paint(object sender, PaintEventArgs e)
 2        {
 3            if (true == resize) return;
 4
 5            Graphics g2 = e.Graphics;
 6
 7            Bitmap bmp = new Bitmap(this.Width, this.Height);
 8            Graphics g = Graphics.FromImage(bmp);
 9
10            paintcall.Increment();
11
12            Rectangle r = new Rectangle(00this.Width, this.Height);
13            g.FillRectangle(new LinearGradientBrush(r, Color.Red, Color.Blue, LinearGradientMode.BackwardDiagonal), r);
14
15            g2.DrawImage(bmp, new Point(00));
16
17            g.Dispose();
18            g = null;
19
20            bmp.Dispose();
21            bmp = null;
22        }

注意第三行,我们判断,如果在resize过程中,那么就直接返回。这样,虽然界面像白板一样,但是却少了很多Paint操作,从性能上会好不少。

posted on 2007-11-13 16:54:00 by juqiang  评论(4) 阅读(5062)

GDI+的leak

GDI+自身是否有leak,我们不去管,现在说的是.NET代码中的处理。
首先看我这个简单的helper

using System;
using System.Diagnostics;
using System.Text;
using System.Runtime.InteropServices;

public class MemoryReport{
    [DllImport(
"user32.dll", CharSet=CharSet.Auto)]
    
public static extern long GetGuiResources(IntPtr hProcess, long flag);

    
public static string Write(){
        Process p 
= Process.GetCurrentProcess();
        ing hcount 
= p.HandleCount;
        
long psize = p.PrivateMemorySize64;
        
long vsize = p.VirtualMemorySize64;
        
long workset = p.WorkingSet64;
        
long gcsize = GC.GetTotalMemory(false);
        
int gdiobjs = (int)(GetGdiResources(p.Handle,0));
        
int userobjs = (int)(GetGdiResources(p.Handle,1));

        
return String.Format("Handle count:{0:N0},Private Bytes:{1:N0}K, Virtual Bytes:{2:N0}K, Working Set:{3:N0}K, GC Heap Size:{4:N0}K, GDI Objects:{5:N0}, User Objects:{6:N0}", hcount, psize>>10, vsize>>10, workset>>10, gcsize>>10, gdiobjs, userobjs);
}

        
    }

现在我们做一个winform程序,放一个button,在click里面写如下测试代码:
for(int i=0;i<1000;i++){
    Bitmap b 
= new Bitmap("c:\\1.bif");
    IntPtr ip 
= b.GetHbitmap();
    Bitmap b2 
= Bitmap.FromHbitmap(ip);
}


MessageBox.Show(MemoryReport.Write());

观察每次的结果,Private Bytes/ Virtual Bytes/ Working Set基本是一个上涨的走向。但是我们感兴趣的是这几个地方:
1、Handle count:这个值一般会波动变化,在这里例子里面,你把程序运行起来后,用taskmgr来观察Handle Count一栏(默认的没有,需要你自己手工添加这个column),一般是100以下。然后点一下按钮,handle count会增长1000左右,再点几次,会在1000上下波动,不会继续增长。
2、GDI Objects:这个值每次会增加1000
3、你连续点10次这个button,嘣!程序crash了。。。如果看dump里面的异常,会是什么bitmap的一个构造方法的parameter不正确。
4、GC Heap Size很小很小,我这里是2M。但是virtual size很大。

对于1,为什么这样,我不清楚;对于2,原因在于GetHbitmap返回的是一个Unmanged resource,GC不会回收(即使你使用了GC.Collect()这个值也不会下降的);对于3,OS默认的每个process的GDI objects上限为10000个,我们代码中是循环了1000次,所以如果你点了10次button,程序就会完蛋。对于4,说明leak的资源是unmanged resource,so,gc heap看起来很乖。

那么,如何修复上面的问题2?既然是unmanged resource,我们就要从unmanged找起。
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DeleteObject(IntPtr hobj);
for(int i=0;i<1000;i++){
    Bitmap b 
= new Bitmap("c:\\1.bif");
    IntPtr ip 
= b.GetHbitmap();
    Bitmap b2 
= Bitmap.FromHbitmap(ip);

     DeleteObject(ip);
}


MessageBox.Show(MemoryReport.Write());

嗯,再运行一次,好了!GDI objects稳定了,再也没有变化过。
不过,我们修改一下循环计数器,到5000吧,然后观察Handle count,波动的比较厉害,内存相关的三组数值也稍有变化。好,我们再修改一次程序
[DllImport("gdi32.dll", CharSet=CharSet.Auto)]
public static extern IntPtr DeleteObject(IntPtr hobj);
for(int i=0;i<1000;i++){
    Bitmap b 
= new Bitmap("c:\\1.bif");
    IntPtr ip 
= b.GetHbitmap();
    Bitmap b2 
= Bitmap.FromHbitmap(ip);

     b.Dispose();
     b2.Dispose();

     DeleteObject(ip);
}


MessageBox.Show(MemoryReport.Write());

重新run一次,嗯,这个世界终于清静了,handle count/gdi resource/ mem size都很平稳。

so,总结一下,对于类似上面的、可能被反复调用的type,如GDI+ obj,可以考虑使用完毕后立刻Dispose,这样可以被GC提早回收。对于返回一个IntPtr的方法,要仔细看,是不是需要再call win32里面对应的Delete方法。

对于绝大多数GDI+ obj,我们只需要DeleteObject即可,但是对于icon,我记着是另外一个函数,有兴趣的可以在msdn上查一下。

posted on 2007-11-10 10:08:00 by juqiang  评论(11) 阅读(4857)

Windbg入门及提高,我的评价(广告续)

1、这本书对于初学者没有太大用处
2、这本书对于眼中只有架构、自己不写程序的、鄙视代码的人没有用处
3、这本书对于非微软的人用处不算太大,你不知道ms内部的数据结构,你没有private symbols。
4、这本书对于微软的人用处不算太大,搞debug的就那么几号人

5、这本书对于在客户现场被骂的狗血喷头的、自己即使架了.NET IDE也不知道如何找出问题的人很有用处

 

如果你是第五种人,疯狂购买吧!

posted on 2007-11-09 11:13:00 by juqiang  评论(4) 阅读(4550)

windbg入门及提高(广告)

此文是偶的偶像和哥们的,转过来,替他做一下宣传。偶会买10本,9本送人,有要的,现在报名。
(原来公司大量的COM+和.NET的case,都是熊老大做的)
 
Windows 高效排错
《Windows 高效排错》 可以在CSDN读书频道预览了
读书频道的排版有些问题,看起来不是很舒服。如果想看PDF的,可以在这里下载
纸板书籍估计在11月中下旬面世 
现在在China-pub, dearbook等网上书店已经有介绍,地址分别
您的看法非常重要。如果你看过这本书的PDF,了解本书的内容和潜在读者,请您在上面的链接中留下你的观点,便于其它不了解这本书的人选择。
如果您通过这本书介绍的方法解决过实际的问题,非常希望您能够分享您的经验。您可以发邮件到eparg@msn.com。如果您的分享能够帮助其它读者,我非常希望能够送书给您作为答谢。
如果您对书中所述内容有疑惑,也欢迎您写信来讨论。我会尽快回复。如果我们之间的讨论对其他读者有帮助,我也会放到网上,同时送书给您作为答谢。
===
上面是官腔了。我个人想说的话其实是:
1) 书前言里面说,"2007年年初我不再做技术支持。希望这本书能帮我记录下这一段美好的经历",其中最让我难忘的就是跟xiao,elan和leo站在水房门口讨论case的日子
2) 常来我blog踩的同学们,你们要书的话把地址留给我。等我拿到了我给大家发。(估计要等一段时间。出版社就给我6本。剩下的我争取去批发)
3) 跟我一起做过case的,如果你需要的话也把地址留给我
4) 多卖一本书,我可以多拿3块钱不到。整本书的稿费还不够在上海买半个车牌,或者买个厕所。我一点也不关心字面上的销量。我之所以花那么多时间来做这个亏本生意,目的只有一个,就是希望跟喜欢调试的人分享其中的过程,让真正需要这方面文章的人有所参考。所以如果你身边有这本书的潜在读者,劳烦你推荐一下。

posted on 2007-11-08 10:54:00 by juqiang  评论(27) 阅读(5139)

Powered by: Joycode.MVC引擎 0.5.2.0