Effective C# 原则16:垃圾最小化(译)
Effective C# 原則16:垃圾最小化
Item 16: Minimize Garbage
垃圾回收器對內存管理表現的非常出色,并且它以非常高效的方法移除不再使用的對象。但不管你怎樣看它,申請和釋放一個基于堆內存的對象總比申請和釋放一個不基于堆內存的對象要花上更多的處理器時間。你可以給出一些嚴重的性能問題,例如應用程序在某個方法內分配過量的引用對象。
你不應該讓垃圾回收器超負荷的工作,為了程序的效率,你可以使用一些簡單的技巧來減少垃圾回收器的工作。所有的引用類型,即使是局部變量,都是在堆上分配的。所有引用類型的局部變量在函數退出后馬上成為垃圾,一個最常見的“垃圾”做法就是申請一個Windows的畫圖句柄:
protected override void OnPaint( PaintEventArgs e )
{
? // Bad. Created the same font every paint event.
? using ( Font MyFont = new Font( "Arial", 10.0f ))
? {
??? e.Graphics.DrawString( DateTime.Now.ToString(),
????? MyFont, Brushes.Black, new PointF( 0,0 ));
? }
? base.OnPaint( e );
}
OnPaint()函數的調用很頻繁的,每次調用它的時候,都會生成另一個Font對象,而實際上它是完全一樣的內容。垃圾回收器每次都須要清理這些對象。這將是難以置信的低效。
取而代之的是,把Font對象從局部變量提供為對象成員,在每次繪制窗口時重用同樣的對象:
private readonly Font _myFont =
? new Font( "Arial", 10.0f );
protected override void OnPaint( PaintEventArgs e )
{
? e.Graphics.DrawString( DateTime.Now.ToString( ),
??? _myFont, Brushes.Black, new PointF( 0,0 ));
? base.OnPaint( e );
}
這樣你的程序在每次paint事件發生時不會產生垃圾,垃圾回收器的工作減少了,你的程序運行會稍微快一點點。當你把一個實現了IDisposable接口的局部變量提升為類型成員時,例如字體,你的類同樣也應該實現IDisposable接口。原則18會給你解釋如何正確的完成它。
當一個引用類型(值類型的就無所謂了)的局部變量在常規的函數調用中使用的非常頻繁時,你應該把它提升為對象的成員。那個字體就是一個很好的例子。只有常用的局部變量頻繁訪問時才是很好的候選對象,不是頻繁調用的就不必了。你應該盡可能的避免重復的創建同樣的對象,使用成員變量而不是局部變量。
前面例子中使用的靜態屬性Brushes.Black,演示了另一個避免重復創建相似對象的技術。使用靜態成員變量來創建一些常用的引用類型的實例??紤]前面那個例子里使用的黑色畫刷,每次當你要用黑色畫刷來畫一些東西時,你要在程序中創建和釋放大量的黑色畫刷。前面的一個解決方案就是在每個期望黑色畫刷的類中添加一個畫刷成員,但這還不夠。程序可能會創建大量的窗口和控件,這同樣會創建大量的黑色畫刷。.Net框架的設計者預知了這個問題,他們為你創建一個簡單的黑色畫刷以便你在任何地方都可以重復使用。Brushes對象包含一定數量的靜態Brush對象,每一個具有不同的常用的顏色。在內部,Brushes使用了惰性算法來,即只有當你使用時才創建這些對象。一個簡單的實現方法:
private static Brush _blackBrush;
public static Brush Black
{
? get
? {
??? if ( _blackBrush == null )
????? _blackBrush = new SolidBrush( Color.Black );
????? return _blackBrush;
? }
}
當你第一次申請黑色畫刷時,Brushes類就會創建它。然而Brushes類就保留一個單一的黑色畫刷的引用句柄,當你再次申請時它就直接返回這個句柄。結果就是你只創建了一個黑色畫刷并且一直在重用它。另外,如果你的應用程序不須要一個特殊的資源,一個檸檬綠(lime green)的畫刷就可能永遠不會創建??蚣芴峁┝艘粋€方法來限制對象,使得在滿足目標的情況下使用最小的對象集合。學會在你的應用程序里使用這樣的技巧。
你已經學會了兩種技術來最小化應用程序的(對象)分配數量,正如它承擔它自己的任務一樣。你可以把一個經常使用的局部變量提升為類的成員變量,你可以提供一個類以單件模式來存儲一些常用的給定對象的實例。最后一項技術還包括創建恒定類型的最終使用值。System.String類就是一個恒定類型,在你創建一個字符串后,它的內容就不能更改了。當你編寫代碼來修改這些串的內容時,你實際上是創建了新的對象,并且讓舊的串成為了垃圾。這看上去是清白的例子:
string msg = "Hello, ";
msg += thisUser.Name;
msg += ". Today is ";
msg += System.DateTime.Now.ToString();
這實際上低效的如果你是這樣寫:
string msg = "Hello, ";
// Not legal, for illustration only:
string tmp1 = new String( msg + thisUser.Name );
string msg = tmp1; // "Hello " is garbage.
string tmp2 = new String( msg + ". Today is " );
msg = tmp2; // "Hello <user>" is garbage.
string tmp3 = new String( msg + DateTime.Now.ToString( ) );
msg = tmp3;// "Hello <user>. Today is " is garbage.
字符串tmp1,tmp2,tmp3以及最原始的msg構造的(“Hello”),都成了垃圾。+=方法在字符串類上會生成一個新的對象并返回它。它不會通過把字符鏈接到原來的存儲空間上來修改結果。對于先前這個例子,給一個簡單的構造例子,你應該使用string.Format()方法:
string msg = string.Format ( "Hello, {0}. Today is {1}",
? thisUser.Name, DateTime.Now.ToString( ));
對于更多的復雜的字符串操作,你應該使用StringBuilter類:
StringBuilder msg = new StringBuilder( "Hello, " );
msg.Append( thisUser.Name );
msg.Append( ". Today is " );
msg.Append( DateTime.Now.ToString());
string finalMsg = msg.ToString();
StringBuilder也一個(內容)可變的字符串類,用于生成恒定的字符串對象。在你還沒有創建一個恒定的字符串對象前,它提供了一個有效的方法來存儲可變的字符串。更重要的是,學習這樣的設計習慣。當你的設計提倡使用恒定類型時(參見原則7),對于一些要經過多次構造后才能最終得到的對象,可以考慮使用一些對象生成器來簡化對象的創建。它提供了一個方法讓你的用戶來逐步的創建(你設計的)恒定類型,也用于維護這個類型。
(譯注:請理解作者的意圖,只有當你使用恒定類型時才這樣,如果是引用類型,就不一定非要使用對象生成器了。而且注意恒定類型的特點,就是一但創建就永遠不能改變,所有的修改都會產生新的實例,string就是一個典型的例子,它是一個恒定的引用類型;還有DateTime也是一個,它是一個恒定的值類型。)
垃圾回收器在管理應用程序的內存上確實很高效。但請記住,創建和釋放堆對象還是很占時間的。避免創建大量的對象,也不要創建你不使用的對象。也要避免在局部函數上多次創建引用對象。相反,把局部變量提供為類型成員變量,或者把你最常用的對象實例創建為靜態對象。最后,考慮使用可變對象創建器來構造恒定對象。
======================
???
Item 16: Minimize Garbage
The Garbage Collector does an excellent job of managing memory for you, and it removes unused objects in a very efficient manner.But no matter how you look at it, allocating and destroying a heap-based object takes more processor time than not allocating and not destroying a heap-based object. You can introduce serious performance drains on your program by creating an excessive number of reference objects that are local to your methods.
So don't overwork the Garbage Collector. You can follow some simple techniques to minimize the amount of work that the Garbage Collector needs to do on your program's behalf. All reference types, even local variables, are allocated on the heap. Every local variable of a reference type becomes garbage as soon as that function exits. One very common bad practice is to allocate GDI objects in a Windows paint handler:
protected override void OnPaint( PaintEventArgs e )
{
? // Bad. Created the same font every paint event.
? using ( Font MyFont = new Font( "Arial", 10.0f ))
? {
??? e.Graphics.DrawString( DateTime.Now.ToString(),
????? MyFont, Brushes.Black, new PointF( 0,0 ));
? }
? base.OnPaint( e );
}
?
OnPaint() gets called frequently. Every time it gets called, you create another Font object that contains the exact same settings. The Garbage Collector needs to clean those up for you every time. That's incredibly inefficient.
Instead, promote the Font object from a local variable to a member variable. Reuse the same font each time you paint the window:
private readonly Font _myFont =
? new Font( "Arial", 10.0f );
protected override void OnPaint( PaintEventArgs e )
{
? e.Graphics.DrawString( DateTime.Now.ToString( ),
??? _myFont, Brushes.Black, new PointF( 0,0 ));
? base.OnPaint( e );
}
?
Your program no longer creates garbage with every paint event. The Garbage Collector does less work. Your program runs just a little faster. When you elevate a local variable, such as a font, that implements IDisposable to a member variable, you need to implement IDisposable in your class. Item 18 explains how to properly do just that.
You should promote local variables to member variables when they are reference types (value types don't matter) and they will be used in routines that are called very frequently. The font in the paint routine makes an excellent example. Only local variables in routines that are frequently accessed are good candidates. Infrequently called routines are not. You're trying to avoid creating the same objects repeatedly, not turn every local variable into a member variable.
The static property Brushes.Black, used earlier illustrates another technique that you should use to avoid repeatedly allocating similar objects. Create static member variables for commonly used instances of the reference types you need. Consider the black brush used earlier as an example. Every time you need to draw something in your window using the color black, you need a black brush. If you allocate a new one every time you draw anything, you create and destroy a huge number of black brushes during the course of a program. The first approach of creating a black brush as a member of each of your types helps, but it doesn't go far enough. Programs might create dozens of windows and controls, and would create dozens of black brushes. The .NET Framework designers anticipated this and created a single black brush for you to reuse whenever you need it. The Brushes class contains a number of static Brush objects, each with a different common color. Internally, the Brushes class uses a lazy evaluation algorithm to create only those brushes you request. A simplified implementation looks like this:
private static Brush _blackBrush;
public static Brush Black
{
? get
? {
??? if ( _blackBrush == null )
????? _blackBrush = new SolidBrush( Color.Black );
????? return _blackBrush;
? }
}
?
The first time you request a black brush, the Brushes class creates it. The Brushes class keeps a reference to the single black brush and returns that same handle whenever you request it again. The end result is that you create one black brush and reuse it forevermore. Furthermore, if your application does not need a particular resourcesay, the lime green brushit never gets created. The framework provides a way to limit the objects created to the minimum set you need to accomplish your goals. Copy that technique in your programs..
You've learned two techniques to minimize the number of allocations your program performs as it goes about its business. You can promote often-used local variables to member variables. You can provide a class that stores singleton objects that represent common instances of a given type. The last technique involves building the final value for immutable types. The System.String class is immutable: After you construct a string, the contents of that string cannot be modified. Whenever you write code that appears to modify the contents of a string, you are actually creating a new string object and leaving the old string object as garbage. This seemingly innocent practice:
string msg = "Hello, ";
msg += thisUser.Name;
msg += ". Today is ";
msg += System.DateTime.Now.ToString();
?
is just as inefficient as if you had written this:
string msg = "Hello, ";
// Not legal, for illustration only:
string tmp1 = new String( msg + thisUser.Name );
string msg = tmp1; // "Hello " is garbage.
string tmp2 = new String( msg + ". Today is " );
msg = tmp2; // "Hello <user>" is garbage.
string tmp3 = new String( msg + DateTime.Now.ToString( ) );
msg = tmp3;// "Hello <user>. Today is " is garbage.
?
The strings tmp1, tmp2, and tmp3, and the originally constructed msg ("Hello"), are all garbage. The += method on the string class creates a new string object and returns that string. It does not modify the existing string by concatenating the characters to the original storage. For simple constructs such as the previous one, you should use the string. Format() method:
string msg = string.Format ( "Hello, {0}. Today is {1}",
? thisUser.Name, DateTime.Now.ToString( ));
?
For more complicated string operations, you can use the StringBuilder class:
StringBuilder msg = new StringBuilder( "Hello, " );
msg.Append( thisUser.Name );
msg.Append( ". Today is " );
msg.Append( DateTime.Now.ToString());
string finalMsg = msg.ToString();
?
StringBuilder is the mutable string class used to build an immutable string object. It provides facilities for mutable strings that let you create and modify text data before you construct an immutable string object. Use StringBuilder to create the final version of a string object. More important, learn from that design idiom. When your designs call for immutable types (see Item 7), consider creating builder objects to facilitate the multiphase construction of the final object. That provides a way for users of your class to construct an object in steps, yet maintain the immutability of your type.
The Garbage Collector does an efficient job of managing the memory that your application uses. But remember that creating and destroying heap objects still takes time. Avoid creating excessive objects; don't create what you don't need. Also avoid creating multiple objects of reference types in local functions. Instead, consider promoting local variables to member variables, or create static objects of the most common instances of your types. Finally, consider creating mutable builder classes for immutable types.
轉載于:https://www.cnblogs.com/WuCountry/archive/2007/03/05/664674.html
總結
以上是生活随笔為你收集整理的Effective C# 原则16:垃圾最小化(译)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 元宵之思
- 下一篇: 程序员成功之路 ——The road a