一次性能提升300%的优化实践

本文介绍了一种针对GDI字体渲染性能瓶颈的解决方案,通过使用SafeFontHandle类、HandleCollector类及弱引用缓存,有效避免了GDI资源泄露并显著提升了渲染性能。
性能 优化一般都是从性能瓶颈开始。项目中有这样一个 控件,它包含很多个Item,每个Item字体可能相同,也可能不同。且该控件经常在同一个Form上大量使用。正是这个控件在使用GDI画每个Item的文字时,出现了性能瓶颈。

IntPtr handle = font.ToHfont(); //性能瓶颈

//…

SafeNativeMethods.DeleteObject(handle);

  由于该控件在使用GDI画字时,通过调用Font.ToHfont()方法获得Font的Handle。而这个方法非常慢。并且控件在画每个Item时都被调用这个方法,Form中又有很多个这样的控件,因此调用次数相当可观。这就造成了这个性能瓶颈。
  
  由于操作系统是不允许GDI 的Handle个数大于9999的。如果大于9999个的话,程序就会崩掉。因此,我们绝对不能使程序中GDI的Handle个数与某些因素有线性增长关系。所有,一般都是在使用GDI画字时创建Handle,用完之后就删除掉。这样也可以防止GDI泄露。

  考虑到很多时候,Font都是相同的,如果能将Font创建的Handle 缓存起来,性能就会有很大的提升。但是,缓存的Handle不及时删除的话,如果Font不相同的太多,就有机会达到操作系统允许的最大个数,从而使程序崩溃。

以下是我的解决方案:
1,使用SafeFontHandle类来防止GDI泄露。SafeFontHandle派生自SafeHandleZeroOrMinusOneIsInvalid,而SafeHandleZeroOrMinusOneIsInvalid又派生自CriticalFinalizerObject。GC会对CriticalFinalizerObject做特别处理,保证所有关键终止代码都有机会执行。
  1. #region The SafeFontHandle class

  2.     internal sealed class SafeFontHandle : SafeHandleZeroOrMinusOneIsInvalid
  3.     {
  4.         private SafeFontHandle()
  5.             : base(true)
  6.         {
  7.         }

  8.         public SafeFontHandle(IntPtr preexistingHandle, bool ownsHandle)
  9.             : base(ownsHandle)
  10.         {
  11.             base.SetHandle(preexistingHandle);
  12.         }

  13.         protected override bool ReleaseHandle()
  14.         {
  15.             return SafeNativeMethods.DeleteNativeFontHandle(base.handle);
  16.         }
  17.     }

  18.     #endregion
复制代码
2,使用HandleCollector类防止Font的Handle超过操作系统最大限制。HandleCollector会跟踪Font的Handle,并在其达到指定阀值时强制执行垃圾回收。垃圾回收后,SafeFontHandle会释放Font的handle。
  1.     [SuppressUnmanagedCodeSecurity]
  2.     internal static class SafeNativeMethods
  3.     {
  4.         private static HandleCollector FontHandleCollector = new HandleCollector("GdiFontHandle", 500, 1000);

  5.         internal static IntPtr CreateNativeFontHandle(Font font)
  6.         {
  7.             IntPtr handle = font.ToHfont();
  8.             if (handle != IntPtr.Zero)
  9.             {
  10.                 FontHandleCollector.Add();
  11.             }
  12.             return handle;
  13.         }

  14.         internal static bool DeleteNativeFontHandle(IntPtr handle)
  15.         {
  16.             bool success = DeleteObject(handle);
  17.             if (success)
  18.             {
  19.                 FontHandleCollector.Remove();
  20.             }
  21.             return success;
  22.         }

  23.         [System.Runtime.InteropServices.DllImportAttribute("gdi32.dll")]
  24.         internal static extern bool DeleteObject(System.IntPtr gdiObject);
  25.     }
复制代码
3,使用弱引用缓存类WeakReferenceCachePool<TKey, TItem>来缓存SafeFontHandle,这样可以不影响SafeFontHandle被GC正常垃圾回收,从而释放Font的Handle。关于弱引用缓存类WeakReferenceCachePool<TKey, TItem>,可以参考《一个弱引用缓存类》这篇文章。
  1.     internal static class SafeFontHandleFactory
  2.     {
  3.         #region Instance Data

  4.         private static WeakReferenceCachePool<Font, SafeFontHandle> _cachePool = new WeakReferenceCachePool<Font, SafeFontHandle>();

  5.         #endregion

  6.         #region Methods

  7.         public static SafeFontHandle CreateSafeFontHandle(Font font)
  8.         {
  9.             if (font == null)
  10.             {
  11.                 throw new ArgumentNullException();
  12.             }

  13.             SafeFontHandle safeFontHandle = _cachePool[font];
  14.             if (safeFontHandle == null)
  15.             {
  16.                 IntPtr nativeHandle = SafeNativeMethods.CreateNativeFontHandle(font);
  17.                 safeFontHandle = new SafeFontHandle(nativeHandle, true);
  18.                 _cachePool[font] = safeFontHandle;
  19.             }
  20.             return safeFontHandle;
  21.         }

  22.         #endregion
  23.     }
复制代码
这样就成功的缓存了GDI的Handle,而且在使用完成后,GDI的Handle不会线性增长,只要有GC回收发生,GDI的Handle都会清零,或者总个数达到HandleCollector指定的阀值时,也会清零。利用GC垃圾回收 机制,在性能和内存占用之间自动平衡。

性能 测试如下:

不使用弱引用缓存
        Time Elapsed:  350ms
        CPU Cycles:    952,061,115
        Gen 0:          1
        Gen 1:          0
        Gen 2:          0
        GDI increment:  0

使用弱引用缓存
        Time Elapsed:  42ms
        CPU Cycles:    142,020,499
        Gen 0:          0
        Gen 1:          0
        Gen 2:          0
        GDI increment:  0

这里下载测试代码:
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值