CString 的效率

CString 的一個問題是它確實掩藏了一些低效率的東西。從另外一個方面講,它也確實可以被實現得更加高效,你可能會說下面的代碼:

CString s = SomeCString1;
s += SomeCString2;
s += SomeCString3;
s += ",";
s += SomeCString4;

比起下面的代碼來,效率要低多了:

char s[1024];
lstrcpy(s, SomeString1);
lstrcat(s, SomeString2);
lstrcat(s, SomeString 3);
lstrcat(s, ",");
lstrcat(s, SomeString4);

  總之,你可能會想,首先,它爲 SomeCString1 分配一塊內存,然後把 SomeCString1 複製到裏面,然後發現它要做一個連接,則重新分配一塊新的足夠大的內存,大到能夠放下當前的字符串加上SomeCString2,把內容複製到這塊內存 ,然後把 SomeCString2 連接到後面,然後釋放第一塊內存,並把指針重新指向新內存。然後爲每個字符串重複這個過程。把這 4 個字符串連接起來效率多低啊。事實上,在很多情況下根本就不需要複製源字符串(在 += 操作符左邊的字符串)。
  在 VC++6.0 中,Release 模式下,所有的 CString 中的緩存都是按預定義量子分配的。所謂量子,即確定爲 64、128、256 或者 512 字節。這意味着除非字符串非常長,連接字符串的操作實際上就是 strcat 經過優化後的版本(因爲它知道本地的字符串應該在什麼地方結束,所以不需要尋找字符串的結尾;只需要把內存中的數據拷貝到指定的地方即可)加上重新計算字符串的長度。所以它的執行效率和純 C 的代碼是一樣的,但是它更容易寫、更容易維護和更容易理解。
  如果你還是不能確定究竟發生了怎樣的過程,請看看 CString 的源代碼,strcore.cpp,在你 vc98的安裝目錄的 mfc/src 子目錄中。看看 ConcatInPlace 方法,它被在所有的 += 操作符中調用。

啊哈!難道 CString 真的這麼"高效"嗎?比如,如果我創建

CString cat("Mew!");

  然後我並不是得到了一個高效的、精簡的5個字節大小的緩衝區(4個字符加一個結束字符),系統將給我分配64個字節,而其中59個字節都被浪費了。
  如果你也是這麼想的話,那麼就請準備好接受再教育吧。可能在某個地方某個人給你講過儘量使用少的空間是件好事情。不錯,這種說法的確正確,但是他忽略了事實中一個很重要的方面。
  如果你編寫的是運行在16K EPROMs下的嵌入式程序的話,你有理由儘量少使用空間,在這種環境下,它能使你的程序更健壯。但是在 500MHz, 256MB的機器上寫 Windows 程序,如果你還是這麼做,它只會比你認爲的“低效”的代碼運行得更糟。
  舉例來說。字符串的大小被認爲是影響效率的首要因素,使字符串儘可能小可以提高效率,反之則降低效率,這是大家一貫的想法。但是這種想法是不對的,精確的內存分配的後果要在程序運行了好幾個小時後才能體現得出來,那時,程序的堆中將充滿小片的內存,它們太小以至於不能用來做任何事,但是他們增加了你程序的內存用量,增加了內存頁面交換的次數,當頁面交換的次數增加到系統能夠忍受的上限,系統則會爲你的程序分配更多的頁面,直到你的程序佔用了所有的可用內存。由此可見,雖然內存碎片是決定效率的次要因素,但正是這些因素實際控制了系統的行爲,最終,它損害了系統的可靠性,這是令人無法接受的。
  記住,在 debug 模式下,內存往往是精確分配的,這是爲了更好的排錯。
  假設你的應用程序通常需要連續工作好幾個月。比如,我常打開 VC++,Word,PowerPoint,Frontpage,Outlook Express,Forté Agent,Internet Explorer和其它的一些程序,而且通常不關閉它們。我曾經夜以繼日地連續用 PowerPoint 工作了好幾天(反之,如果你不幸不得不使用像 Adobe FrameMaker 這樣的程序的話,你將會體會到可靠性的重要;這個程序機會每天都要崩潰4~6次,每次都是因爲用完了所有的空間並填滿我所有的交換頁面)。所以精確內存分配是不可取的,它會危及到系統的可靠性,並引起應用程序崩潰。
  按量子的倍數爲字符串分配內存,內存分配器就可以回收用過的內存塊,通常這些回收的內存塊馬上就可以被其它的 CString 對象重新用到,這樣就可以保證碎片最少。分配器的功能加強了,應用程序用到的內存就能儘可能保持最小,這樣的程序就可以運行幾個星期或幾個月而不出現問題。
  題外話:很多年以前,我們在 CMU 寫一個交互式系統的時候,一些對內存分配器的研究顯示出它往往產生很多內存碎片。Jim Mitchell,現在他在 Sun Microsystems 工作,那時侯他創造了一種內存分配器,它保留了一個內存分配狀況的運行時統計表,這種技術和當時的主流分配器所用的技術都不同,且較爲領先。當一個內存塊需要被分割得比某一個值小的話,他並不分割它,因此可以避免產生太多小到什麼事都幹不了的內存碎片。事實上他在內存分配器中使用了一個浮動指針,他認爲:與其讓指令做長時間的存取內存操作,還不如簡單的忽略那些太小的內存塊而只做一些浮動指針的操作。(His observation was that the long-term saving in instructions by not having to ignore unusable small storage chunks far and away exceeded the additional cost of doing a few floating point operations on an allocation operation.)他是對的。
  永遠不要認爲所謂的“最優化”是建立在每一行代碼都高速且節省內存的基礎上的,事實上,高速且節省內存應該是在一個應用程序的整體水平上考慮的。在軟件的整體水平上,只使用最小內存的字符串分配策略可能是最糟糕的一種方法。
  如果你認爲優化是你在每一行代碼上做的那些努力的話,你應該想一想:在每一行代碼中做的優化很少能真正起作用。你可以看我的另一篇關於優化問題的文章《Your Worst Enemy for some thought-provoking ideas》。
  記住,+= 運算符只是一種特例,如果你寫成下面這樣:

CString s = SomeCString1 + SomeCString2 + SomeCString3 + "," + SomeCString4;

則每一個 + 的應用會造成一個新的字符串被創建和一次複製操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章