一次效能提升300%的優化實踐

iDotNetSpace發表於2009-08-11
效能優化一般都是從效能瓶頸開始。專案中有這樣一個控制元件,它包含很多個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做特別處理,保證所有關鍵終止程式碼都有機會執行。

一次效能提升300%的優化實踐
#region The SafeFontHandle class

    
internal sealed class SafeFontHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        
private SafeFontHandle()
            : 
base(true)
        {
        }

        
public SafeFontHandle(IntPtr preexistingHandle, bool ownsHandle)
            : 
base(ownsHandle)
        {
            
base.SetHandle(preexistingHandle);
        }

        
protected override bool ReleaseHandle()
        {
            
return SafeNativeMethods.DeleteNativeFontHandle(base.handle);
        }
    }

    
#endregion


2,使用HandleCollector類防止Font的Handle超過作業系統最大限制。HandleCollector會跟蹤Font的Handle,並在其達到指定閥值時強制執行垃圾回收。垃圾回收後,SafeFontHandle會釋放Font的handle。

一次效能提升300%的優化實踐
    [SuppressUnmanagedCodeSecurity]
    
internal static class SafeNativeMethods
    {
        
private static HandleCollector FontHandleCollector = new HandleCollector("GdiFontHandle"5001000);

        
internal static IntPtr CreateNativeFontHandle(Font font)
        {
            IntPtr handle 
= font.ToHfont();
            
if (handle != IntPtr.Zero)
            {
                FontHandleCollector.Add();
            }
            
return handle;
        }

        
internal static bool DeleteNativeFontHandle(IntPtr handle)
        {
            
bool success = DeleteObject(handle);
            
if (success)
            {
                FontHandleCollector.Remove();
            }
            
return success;
        }

        [System.Runtime.InteropServices.DllImportAttribute(
"gdi32.dll")]
        
internal static extern bool DeleteObject(System.IntPtr gdiObject);
    }


3,使用弱引用快取類WeakReferenceCachePool來快取SafeFontHandle,這樣可以不影響SafeFontHandle被GC正常垃圾回收,從而釋放Font的Handle。關於弱引用快取類WeakReferenceCachePool,可以參考《一個弱引用快取類》這篇文章。

一次效能提升300%的優化實踐
    internal static class SafeFontHandleFactory
    {
        
#region Instance Data

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

        
#endregion

        
#region Methods

        
public static SafeFontHandle CreateSafeFontHandle(Font font)
        {
            
if (font == null)
            {
                
throw new ArgumentNullException();
            }

            SafeFontHandle safeFontHandle 
= _cachePool[font];
            
if (safeFontHandle == null)
            {
                IntPtr nativeHandle 
= SafeNativeMethods.CreateNativeFontHandle(font);
                safeFontHandle 
= new SafeFontHandle(nativeHandle, true);
                _cachePool[font] 
= safeFontHandle;
            }
            
return safeFontHandle;
        }

        
#endregion
    }



這樣就成功的快取了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

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/12639172/viewspace-611891/,如需轉載,請註明出處,否則將追究法律責任。

相關文章