Net性能優(yōu)化總結_第1頁
Net性能優(yōu)化總結_第2頁
Net性能優(yōu)化總結_第3頁
Net性能優(yōu)化總結_第4頁
Net性能優(yōu)化總結_第5頁
已閱讀5頁,還剩8頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

NET性能優(yōu)化方面的總結1.C#語言方面1.1垃圾回收垃圾回收解放了手工管理對象的工作,提高了程序的健壯性,但副作用就是程序代碼可能對于對象創(chuàng)建變得隨意。1.1.1避免不必要的對象創(chuàng)建由于垃圾回收的代價較高,所以C#程序開發(fā)要遵循的一個基本原則就是避免不必要的對象創(chuàng)建。以下列舉一些常見的情形。避免循環(huán)創(chuàng)建對象★如果對象并不會隨每次循環(huán)而改變狀態(tài),那么在循環(huán)中反復創(chuàng)建對象將帶來性能損耗。高效的做法是將對象提到循環(huán)外面創(chuàng)建。在需要邏輯分支中創(chuàng)建對象如果對象只在某些邏輯分支中才被用到,那么應只在該邏輯分支中創(chuàng)建對象。使用常量避免創(chuàng)建對象程序中不應出現(xiàn)如newDecimal(0)之類的代碼,這會導致小對象頻繁創(chuàng)建及回收,正確的做法是使用Decimal.Zero常量。我們有設計自己的類時,也可以學習這個設計手法,應用到類似的場景中。使用StringBuilder做字符串連接1.1.2不要使用空析構函數(shù)★如果類包含析構函數(shù),由創(chuàng)建對象時會在Finalize隊列中添加對象的引用,以保證當對象無法可達時,仍然可以調用到Finalize方法。垃圾回收器在運行期間,會啟動一個低優(yōu)先級的線程處理該隊列。相比之下,沒有析構函數(shù)的對象就沒有這些消耗。如果析構函數(shù)為空,這個消耗就毫無意義,只會導致性能降低!因此,不要使用空的析構函數(shù)。在實際情況中,許多曾在析構函數(shù)中包含處理代碼,但后來因為種種原因被注釋掉或者刪除掉了,只留下一個空殼,此時應注意把析構函數(shù)本身注釋掉或刪除掉。1.1.3實現(xiàn)IDisposable接口垃圾回收事實上只支持托管內在的回收,對于其他的非托管資源,例如WindowGDI句柄或數(shù)據庫連接,在析構函數(shù)中釋放這些資源有很大問題。原因是垃圾回收依賴于內在緊張的情況,雖然數(shù)據庫連接可能已瀕臨耗盡,但如果內存還很充足的話,垃圾回收是不會運行的。C#的IDisposable接口是一種顯式釋放資源的機制。通過提供using語句,還簡化了使用方式(編譯器自動生成try...finally塊,并在finally塊中調用Dispose方法)。對于申請非托管資源對象,應為其實現(xiàn)IDisposable接口,以保證資源一旦超出using語句范圍,即得到及時釋放。這對于構造健壯且性能優(yōu)良的程序非常有意義!為防止對象的Dispose方法不被調用的情況發(fā)生,一般還要提供析構函數(shù),兩者調用一個處理資源釋放的公共方法。同時,Dispose方法應調用System.GC.SuppressFinalize(this),告訴垃圾回收器無需再處理Finalize方法了。String操作1.2.1使用StringBuilder做字符串連接String是不變類,使用+操作連接字符串將會導致創(chuàng)建一個新的字符串。如果字符串連接次數(shù)不是固定的,例如在一個循環(huán)中,則應該使用StringBuilder類來做字符串連接工作。因為StringBuilder內部有一個StringBuffer,連接操作不會每次分配新的字符串空間。只有當連接后的字符串超出Buffer大小時,才會申請新的Buffer空間。典型代碼如下:StringBuildersb=newStringBuilder(256);Ifor(inti=0;i<Results.Count;i++)日{Isb.Append(Results[i]);}如果連接次數(shù)是固定的并且只有幾次,此時應該直接用+號連接,保持程序簡潔易讀。實際上,編譯器已經做了優(yōu)化,會依據加號次數(shù)調用不同參數(shù)個數(shù)的String.Concat方法。例如:Stringstr=str1+str2+str3+str4;會被編譯為String.Concat(str1,str2,str3,str4)。該方法內部會計算總的String長度,僅分配一次,并不會如通常想象的那樣分配三次。作為一個經驗值,當字符串連接操作達到10次以上時,則應該使用StringBuilder。這里有一個細節(jié)應注意:StringBuilder內部Buffer的缺省值為16,這個值實在太小。按StringBuilder的使用場景,Buffer肯定得重新分配。經驗值一般用256作為Buffer的初值。當然,如果能計算出最終生成字符串長度的話,則應該按這個值來設定Buffer的初值。使用newStringBuilder(256)就將Buffer的初始長度設為了256。1.2.2避免不必要的調用ToUpper或ToLower方法String是不變類,調用ToUpper或ToLower方法都會導致創(chuàng)建一個新的字符串。如果被頻繁調用,將導致頻繁創(chuàng)建字符串對象。這違背了前面講到的''避免頻繁創(chuàng)建對象”這一基本原則。例如,bool.Parse方法本身已經是忽略大小寫的,調用時不要調用ToLower方法。另一個非常普遍的場景是字符串比較。高效的做法是使用Compare方法,這個方法可以做大小寫忽略的比較,并且不會創(chuàng)建新字符串。還有一種情況是使用HashTable的時候,有時候無法保證傳遞key的大小寫是否符合預期,往往會把key強制轉換到大寫或小寫方法。實際上HashTable有不同的構造形式,完全支持采用忽略大小寫的key:newHashTable(StringComparer.OrdinalIgnoreCase)。1.2.3最快的空串比較方法將String對象的Length屬性與0比較是最快的方法:if(str.Length==0)其次是與String.Empty常量或空串比較:if(str==String.Empty)或if(str=="")注:C#在編譯時會將程序集中聲明的所有字符串常量放到保留池中(internpool),相同常量不會重復分配。1.3多線程1.3.1線程同步線程同步是編寫多線程程序需要首先考慮問題。C#為同步提供了Monitor.Mutex、AutoResetEvent和ManualResetEvent對象來分別包裝Win32的臨界區(qū)、互斥對象和事件對象這幾種基礎的同步機制。C#還提供了一個lock語句,方便使用,編譯器會自動生成適當?shù)腗onitor.Enter和Monitor.Exit調用。同步粒度同步粒度可以是整個方法,也可以是方法中某一段代碼。為方法指定MethodImplOptions.Synchronized屬性將標記對整個方法同步。例如:[Methodlmpl(MethodlmplOptions.Synchronized)]publicstaticSerialManagerGetInstance()|日{Iif(instance==null)申{Iinstance=newSerialManager();}Ireturninstance;}通常情況下,應減小同步的范圍,使系統(tǒng)獲得更好的性能。簡單將整個方法標記為同步不是一個好主意,除非能確定方法中的每個代碼都需要受同步保護。同步策略使用lock進行同步,同步對象可以選擇Type、this或為同步目的專門構造的成員變量。避免鎖定Type*鎖定Type對象會影響同一進程中所有AppDomain該類型的所有實例,這不僅可能導致嚴重的性能問題,還可能導致一些無法預期的行為。這是一個很不好的習慣。即便對于一個只包含static方法的類型,也應額外構造一個static的成員變量,讓此成員變量作為鎖定對象。避免鎖定this鎖定this會影響該實例的所有方法。假設對象obj有A和B兩個方法,其中A方法使用lock(this)對方法中的某段代碼設置同步保護?,F(xiàn)在,因為某種原因,B方法也開始使用lock(this)來設置同步保護了,并且可能為了完全不同的目的。這樣,A方法就被干擾了,其行為可能無法預知。所以,作為一種良好的習慣,建議避免使用lock(this)這種方式。使用為同步目的專門構造的成員變量這是推薦的做法。方式就是new一個object對象,該對象僅僅用于同步目的。如果有多個方法都需要同步,并且有不同的目的,那么就可以為些分別建立幾個同步成員變量。C#為各種集合類型提供了兩種方便的同步機制:Synchronized包裝器和SyncRoot屬性。//CreatesandinitializesanewArrayListArrayListmyAL=newArrayList();myAL.Add("The");myAL.Add("quick");myAL.Add("brown");myAL.Add("fox");//CreatesasynchronizedwrapperaroundtheArrayListArrayListmySyncdAL=ArrayList.Synchronized(myAL);調用Synchronized方法會返回一個可保證所有操作都是線程安全的相同集合對象??紤]mySyncdAL[0]=mySyncdAL[0]+"test"這一語句,讀和寫一共要用到兩個鎖。一般講,效率不高。推薦使用SyncRoot屬性,可以做比較精細的控制。使用ThreadStatic替代NameDataSlot★存取NameDataSlot的Thread.GetData和Thread.SetData方法需要線程同步,涉及兩個鎖:一個是LocalDataStore.SetData方法需要在AppDomain一級加鎖,另一個是ThreadNative.GetDomainLocalStore方法需要在Process一級加鎖。如果一些底層的基礎服務使用了NameDataSlot,將導致系統(tǒng)出現(xiàn)嚴重的伸縮性問題。規(guī)避這個問題的方法是使用ThreadStatic變量。示例如下:publicsealedclassInvokeContextI日{I[ThreadStatic]IprivatestaticInvokeContextcurrent;IprivateHashtablemaps=newHashtable();}1.3.3多線程編程技巧使用DoubleCheck技術創(chuàng)建對象internalIDictionaryKeyTable日{Iget勺{I if(this._keyTable==null)勺 {lock(base._lock)勺{I if(this._keyTable==null)勺 {this._keyTable=newHashtable();}returnthis._keyTable;}創(chuàng)建單例對象是很常見的一種編程情況。一般在lock語句后就會直接創(chuàng)建對象了,但這不夠安全。因為在lock鎖定對象之前,可能已經有多個線程進入到了第一個if語句中。如果不加第二個if語句,則單例對象會被重復創(chuàng)建,新的實例替代掉舊的實例。如果單例對象中已有數(shù)據不允許被破壞或者別的什么原因,則應考慮使用DoubleCheck技術。1.4類型系統(tǒng)1.4.1避免無意義的變量初始化動作CLR保證所有對象在訪問前已初始化,其做法是將分配的內存清零。因此,不需要將變量重新初始化為0、false或null。需要注意的是:方法中的局部變量不是從堆而是從棧上分配,所以C#不會做清零工作。如果使用了未賦值的局部變量,編譯期間即會報警。不要因為有這個印象而對所有類的成員變量也做賦值動作,兩者的機理完全不同!ValueType和ReferenceType以引用方式傳遞值類型參數(shù)值類型從調用棧分配,引用類型從托管堆分配。當值類型用作方法參數(shù)時,默認會進行參數(shù)值復制,這抵消了值類型分配效率上的優(yōu)勢。作為一項基本技巧,以引用方式傳遞值類型參數(shù)可以提高性能。為ValueType提供Equals方法.net默認實現(xiàn)的ValueType.Equals方法使用了反射技術,依靠反射來獲得所有成員變量值做比較,這個效率極低。如果我們編寫的值對象其Equals方法要被用到(例如將值對象放到HashTable中),那么就應該重載Equals方法。publicstructRectangle日{I public doubleLength;I public doubleBreadth;I public overrideboolEquals (objectob)勺{I if(obisRectangle)I returnEquels((Rectangle)ob))I elseI returnfalse;}IprivateboolEquals(Rectanglerect)勺{Ireturnthis.Length==rect.Length&&this.Breadth==rect.Breach;}C#可以在值類型和引用類型之間自動轉換,方法是裝箱和拆箱。裝箱需要從堆上分配對象并拷貝值,有一定性能消耗。如果這一過程發(fā)生在循環(huán)中或是作為底層方法被頻繁調用,則應該警惕累計的效應。一種經常的情形出現(xiàn)在使用集合類型時。例如:ArrayListal=newArrayList();for(inti=0;i<1000;i++)日{Ial.Add(i);//ImplicitlyboxedbecauseAdd()takesanobject}intf=(int)al[0]; //Theelementisunboxed1.5異常處理異常也是現(xiàn)代語言的典型特征。與傳統(tǒng)檢查錯誤碼的方式相比,異常是強制性的(不依賴于是否忘記了編寫檢查錯誤碼的代碼)、強類型的、并帶有豐富的異常信息(例如調用棧)。1.5.1不要吃掉異?!镪P于異常處理的最重要原則就是:不要吃掉異常。這個問題與性能無關,但對于編寫健壯和易于排錯的程序非常重要。這個原則換一種說法,就是不要捕獲那些你不能處理的異常。吃掉異常是極不好的習慣,因為你消除了解決問題的線索。一旦出現(xiàn)錯誤,定位問題將非常困難。除了這種完全吃掉異常的方式外,只將異常信息寫入日志文件但并不做更多處理的做法也同樣不妥。1.5.2不要吃掉異常信息★有些代碼雖然拋出了異常,但卻把異常信息吃掉了。為異常披露詳盡的信息是程序員的職責所在。如果不能在保留原始異常信息含義的前提下附加更豐富和更人性化的內容,那么讓原始的異常信息直接展示也要強得多。千萬不要吃掉異常。1.5.3避免不必要的拋出異常拋出異常和捕獲異常屬于消耗比較大的操作,在可能的情況下,應通過完善程序邏輯避免拋出不必要不必要的異常。與此相關的一個傾向是利用異常來控制處理邏輯。盡管對于極少數(shù)的情況,這可能獲得更為優(yōu)雅的解決方案,但通常而言應該避免。1.5.4避免不必要的重新拋出異常如果是為了包裝異常的目的(即加入更多信息后包裝成新異常),那么是合理的。但是有不少代碼,捕獲異常沒有做任何處理就再次拋出,這將無謂地增加一次捕獲異常和拋出異常的消耗,對性能有傷害。1.6反射反射是一項很基礎的技術,它將編譯期間的靜態(tài)綁定轉換為延遲到運行期間的動態(tài)綁定。在很多場景下(特別是類框架的設計),可以獲得靈活易于擴展的架構。但帶來的問題是與靜態(tài)綁定相比,動態(tài)綁定會對性能造成較大的傷害。1.6.1反射分類typecomparison:類型判斷,主要包括is和typeof兩個操作符及對象實例上的GetType調用。這是最輕型的消耗,可以無需考慮優(yōu)化問題。注意typeof運算符比對象實例上的GetType方法要快,只要可能則優(yōu)先使用typeof運算符。memberenumeration:成員枚舉,用于訪問反射相關的元數(shù)據信息,例如Assembly.GetModule、Module.GetType、Type對象上的IsInterface、IsPublic、GetMethod、GetMethods、GetProperty、GetProperties、GetConstructor調用等。盡管元數(shù)據都會被CLR緩存,但部分方法的調用消耗仍非常大,不過這類方法調用頻度不會很高,所以總體看性能損失程度中等。memberinvocation:成員調用,包括動態(tài)創(chuàng)建對象及動態(tài)調用對象方法,主要有Activator.CreateInstance、Type.InvokeMember等。1.6.2動態(tài)創(chuàng)建對象C#主要支持5種動態(tài)創(chuàng)建對象的方式:Type.InvokeMemberContructorInfo.InvokeActivator.CreateInstance(Type)Activator.CreateInstance(assemblyName,typeName)Assembly.CreateInstance(typeName)最快的是方式3,與DirectCreate的差異在一個數(shù)量級之內,約慢7倍的水平。其他方式,至少在40倍以上,最慢的是方式4,要慢三個數(shù)量級。1.6.3動態(tài)方法調用方法調用分為編譯期的早期綁定和運行期的動態(tài)綁定兩種,稱為Early-BoundInvocation和Late-BoundInvocation。Early-BoundInvocation可細分為Direct-call、Interface-call和Delegate-calloLate-BoundInvocation主要有Type.InvokeMember和MethodBase.Invoke,還可以通過使用LCG(LightweightCodeGeneration)技術生成IL代碼來實現(xiàn)動態(tài)調用。從測試結果看,相比DirectCall,Type.InvokeMember要接近慢三個數(shù)量級;MethodBase.Invoke雖然比Type.InvokeMember要快三倍,但比DirectCall仍慢270倍左右??梢妱討B(tài)方法調用的性能是非常低下的。我們的建議是:除非要滿足特定的需求,否則不要使用!1.6.4推薦的使用原則模式如果可能,則避免使用反射和動態(tài)綁定使用接口調用方式將動態(tài)綁定改造為早期綁定使用Activator.CreateInstance(Type)方式動態(tài)創(chuàng)建對象使用typeof操作符代替GetType調用反模式1.在已獲得Type的情況下,卻使用Assembly.CreateInstance(type.FullName)1.7基本代碼技巧這里描述一些應用場景下,可以提高性能的基本代碼技巧。對處于關鍵路徑的代碼,進行這類的優(yōu)化還是很有意義的。普通代碼可以不做要求,但養(yǎng)成一種好的習慣也是有意義的。1.7.1循環(huán)寫法可以把循環(huán)的判斷條件用局部變量記錄下來。局部變量往往被編譯器優(yōu)化為直接使用寄存器,相對于普通從堆或棧中分配的變量速度快。如果訪問的是復雜計算屬性的話,提升效果將更明顯。for(inti=0,j=collection.GetIndexOf(item);i<j;i++)需要說明的是:這種寫法對于CLR集合類的Count屬性沒有意義,原因是編譯器已經按這種方式做了特別的優(yōu)化。1.7.2拼裝字符串拼裝好之后再刪除是很低效的寫法。有些方法其循環(huán)長度在大部分情況下為1,這種寫法的低效就更為明顯了:publicstaticstringToString(MetadataKeyentityKey)日{Istringstr="”;I object[]vals=entityKey.values;I for(inti=0;i<vals.Length;i++)自{Istr+=","+vals[i].ToString();}I returnstr==""?"":str.Remove(0,1);}推薦下面的寫法:if(str.Length==0)str=vals[i].ToString();elsestr+=","+vals[i].ToString();其實這種寫法非常自然,而且效率很高,完全不需要用個Remove方法繞來繞去。1.7.3避免兩次檢索集合元素獲取集合元素時,有時需要檢查元素是否存在。通常的做法是先調用ContainsKey(或Contains)方法,然后再獲取集合元素。這種寫法非常符合邏輯。但如果考慮效率,可以先直接獲取對象,然后判斷對象是否為null來確定元素是否存在。對于Hashtable,這可以節(jié)省一次GetHashCode調用和n次Equals比較。如下面的示例:publicIDataGetItemByID(Guidid)日{IIDatadata1=null;Iif(this.idTable.ContainsKey(id.ToString())自{Idata1=this.idTable[id.ToString()]asIData;}Ireturndata1;}其實完全可用一行代碼完成:returnthis.idTable[id]asIData;1.7.4避免兩次類型轉換考慮如下示例,其中包含了兩處類型轉換:if(objisSomeType)|日{ISomeTypest=(SomeType)obj;Ist.SomeTypeMethod();}效率更高的做法如下:SomeTy

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論