Effective C# 原則16:垃圾最小化(譯)

來源:互聯網
上載者:User

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.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.