C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题
?
理清幾個基本點
在開始談論性能問題之前,有必要首先理清幾個基本點。我們談C#,就是在談.NET Framework(或者更準確一點是CLR,因為.NET Framework除了CLR還包括BCL);談.NET Framework(CLR),也就是在談C#。因為支撐C#語法之后的就是整個CLR的機制。因此,我說C#性能不好,和說CLR性能不好,說的是一個事情(就像說Java性能不好,就是說JVM性能不好一樣)。我不希望在我下面說C#某個地方性能不好的時候,再有論者立即指出來“那不是C#的問題,那是CLR的問題,或者.NET Framework的問題”——如果對C#和.NET還停留在這個認識上,請先去讀讀Jeffrey Richter的《CLR via C#》一書,再來看我下面的文章。
?
另外,我說C#性能有問題,僅針對C#而言,與我對其他語言的態(tài)度無關。我既不是Java的支持者(因為Java的性能比C#還慢),也不是C++的支持者(C++太過臃腫復雜),也不是C的支持者(沒有基本的面向?qū)ο蟪橄蠛屠厥?#xff09;。我既不喜歡任何語言,也不討厭任何語言。編程語言在我只是一個工具——我只是希望這個工具是把鋒利的牛刀,而不是把功能齊全的瑞士小刀。
?
最后我不是毫無選擇地反對“新功能”,我反對的是“添加的功能、沒有重大抽象意義,卻帶來性能損失”,如果有“提高性能的新功能”——比如并發(fā)編程,或者“對管理軟件復雜度”有重大意義,同時性能損失很小很小——比如面向?qū)ο?/span>,那我舉雙手贊成?!?/p> ? 在理清了前面幾個基本點之后,下面開始來針對我前文說過的一些問題一一“講原理”。這篇文章中,我首先來剖析反射的性能問題。 ? 反射的兩大類性能問題 ? 【一】反射綁定與調(diào)用——使用反射帶來的性能問題 ? 反射的綁定與調(diào)用性能差,我想大概做過.NET開發(fā)的人都不會懷疑這一點。但是我還是希望那些嚴肅的程序員認真看看微軟CLR程序經(jīng)理Joel Pobar在MSDN上的這篇文章:Dodge Common Performance Pitfalls to Craft Speedy Applications http://msdn.microsoft.com/en-us/magazine/cc163759.aspx,清楚理解反射綁定與調(diào)用的效率到底為什么那么差?有多差?差在哪里? ? 限于篇幅關系,我簡單在這里總結(jié)一下,反射綁定與調(diào)用的性能問題(具體原理,大家參照MSDN這篇文章): 我想這些性能問題,大家都會認可。但有些朋友會說“我.NET程序中用反射的很少啊?”,首先且不論你用的少不少,但是微軟開發(fā)的很多Application Framework對反射的使用現(xiàn)在越來越多,比如大量使用反射“綁定與調(diào)用”的例子(注意是大量,不是一點點!): 所以說,你用反射用的少,并不代表你最后做出的軟件用反射的少(你的軟件的代碼不可能全都是自己寫的,很多都是依附于微軟的Application Framework,只要這些Application Framework很重地使用了反射,那么你的軟件也就很重的使用了反射) ? 但有朋友會立即指出“我不用WPF/SL,不用WCF、不用WF、不用ASP.NET MVC,類庫都是自己寫,代碼全都是自己寫,保證反射用的很少,甚至確保壓根沒有使用反射,這些性能負擔不久沒有了嗎?”這個問題很好! 也是前面談到.NET各種功能帶來的性能問題的時候,很多朋友最喜歡的辯詞——不用它不就是了嘛! ? 首先如果有這樣的C#程序員,我定佩服你如滔滔江水…….但是,我這里要告訴大家的事實是,“即便你程序中確實所有的代碼都不使用反射,由于C#/.NET內(nèi)置地支持反射,那么你也要為此付出性能代價,而且是很高的性能代價”。這是本文的重點,甚至是我后續(xù)很多論戰(zhàn)文章的重點——很多C#/.NET機制,不管你用不用它,只要內(nèi)置支持這種機制,就不可避免要付出性能代價(當然如果你要用它,還有更多性能代價)。 ? 好,下面讓我們來談談為什么,即便不用反射也要付出很高的性能代價?(這也是MSDN那篇文章所刻意回避的話題)。 ? ? 【二】反射背后需要的支撐機制:元數(shù)據(jù)的性能問題——不使用反射的性能問題 ? ???????? 要談這個問題,首先大家應該清楚C#/.NET中反射的功能是由metadata來支持的,即便你所有的代碼中、你用的所有Application Framework的代碼中都沒有使用一點反射的API,C#編譯器還是會在最后生成的EXE或者DLL中生成所有的metadata。(如果這個不清楚,請先讀Jeffrey Richter的《CLR via C#》一書)。而?Metadata就是C#/.NET性能的罪魁禍首!要理解這一點,大家先來做兩個簡單的針對metadata的分析。 1.??用ILDASM工具將C:\Windows\Microsoft.NET\Framework\v4.0.30128 下面的MSCorlib.dll(.NET核心類庫程序集,其他版本也可以,不必非要4.0)打開。點擊:View->Statistics,看一下其中的元數(shù)據(jù)大小: ? CLR header size???? : 72???????????????? ( 0.00%) CLR meta-data size? : 2083724??????????? (40.09%) CLR additional info : 931312??????????? (17.92%) CLR method headers? : 136967???????????? ( 2.64%) Managed code???????? : 1212346?????????? (23.32%) Data???????????????? : 753152??????????? (14.49%) 注意:這四個部分,其要么是metadata,要么是metadata的輔助信息,所以我在后面文章中都算作元數(shù)據(jù)部分: 整個MSCorlib.dll大小為4.95M。 Metadata總共占用大約3.01M,占總大小大約60.6%。 真正傳統(tǒng)的Code+Data總共占用大約1.87M,占總大小約37.8%。 ? MSCorlib.dll總共大小4.95M,為了支持反射,需要添加的元數(shù)據(jù)竟然有3.01M,占到60%的大小!!!我想大家已經(jīng)看出問題來了。有些朋友可能會說,這是特例吧?別的DLL呢? ? 2.??我們再來隨便找一個DLL,比如WPF的DLL:C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0\PresentationFramework.dll,同樣適用ILDASM打開,點擊:View->Statistics看一下其中的元數(shù)據(jù)大小: ? ? 整個PresentationFramework.dll大小為5.03M。Metadata總共占用大約55.15%! ? 大家可以隨便拿一個自己項目中.NET的DLL或者EXE來分析,看看Metadata的大小占用多少? 基本都在50%以上,甚至有的高達70%! ? 這意味著什么?即使你不用任何反射的代碼,C#/.NET為了讓它支持反射,還要給你最后生成的DLL/EXE強加50%以上的metadata(這是強制的,即便你不用反射,C#/.NET也沒有提供任何編譯選項將這些metadata去掉)。這就是.NET Framework Redistributable本身要40M左右的原因! ? 我想這個鐵的事實是“老趙們”無論如何都不能否認的。但是“老趙們”的典型言論馬上又來了: (1)不就是程序有點大嗎?現(xiàn)在大硬盤很便宜,運行起來還是很快的 (2)就是.NET Framwork有點大,客戶安裝起來不方便 (3)大只是空間效率,不影響程序的時間效率 ? 這些調(diào)調(diào)顯然都是沒有真正搞過“性能優(yōu)化”的“老趙們”的淺見。空間效率并非對時間效率沒有影響,而是有致命影響。一個100M的應用程序,運行起來肯定要比一個40M的程序慢許多。理由如下: ? (1)程序(EXE/DLL)最后都是要加載到內(nèi)存中運行的,不是光放在硬盤上的——這也是為什么.NET程序占用內(nèi)存都超多 (2)占用內(nèi)存多的程序,運行起來必然慢。因為內(nèi)存大的程序必然會出現(xiàn)較多的page fault(即換頁錯誤),cache missing(即緩存失效)(簡單來說,要盡可能在CPU緩存中操作working set,CPU緩存裝不下,就要跑到主存里面找;主存裝不下就要跑到虛擬內(nèi)存-也就是硬盤里面找,那樣軟件運行的性能代價非常高). Page fault和cache missing已經(jīng)成為現(xiàn)代軟件性能的一大公害。很多程序慢下來,如果不是蹩腳的算法,Page fault和cache missing往往都是罪魁禍首!關于這方面的理論,很多牛人都專門講過,國外也有比較牛叉的咨詢公司專門做這方面的優(yōu)化,大家如果想深度理解這方面,可以參照: a. CACHE MEMORY:IMPLEMENTATION ANDDESIGN TECHNIQUES http://www.faculty.iu-bremen.de/birk/lectures/PC101-2003/07cache/cache%20memory.htm ? b. Improving Managed Code Performance-Working SetConsiderations http://msdn.microsoft.com/en-us/library/ff647790.aspx#scalenetchapt05_topic33 ? c.以及微軟的.NET性能經(jīng)理Rico Mariani在這里的文章: My mom doesn't care about space,http://blogs.msdn.com/b/ricom/archive/2004/03/15/89934.aspx ? 所以,總結(jié)下來就是: (1)Metadata非常占用空間,一般占到整個EXE/DLL總大小的50%~70% (2)高昂的空間成本會由于Page fault和cache missing等因素轉(zhuǎn)嫁為高昂的時間成本 (3)即便在代碼中不寫一行反射調(diào)用代碼,所有的metadata仍然會生成,我們?nèi)匀灰獮榇烁冻龈甙旱目臻g代價和時間代價。 ? 比如,我們公司開發(fā)的一個大型醫(yī)療軟件,之前的版本使用C++開發(fā),整個生成代碼體積為40M左右,但是轉(zhuǎn)移到.NET平臺上(被微軟的.NET平臺戰(zhàn)略忽悠過來)后發(fā)現(xiàn)代碼體積為130M左右(功能差不多的前提下,第一版主要是移植,新增功能的代碼量占不到5%),我們反反復復怎么優(yōu)化都優(yōu)化不到原來的40M左右,最后發(fā)現(xiàn)都是反射惹的禍!——我相信我在前文舉出的很多世界著名、或者中國著名的軟件最終沒有選擇.NET,都有過這樣一個評測過程。 ? 其他的例子大家可以自己找,比如就拿mspaint.exe 與paint.net(到這里下載:http://www.softpedia.com/progDownload/Paint-NET-Download-19322.html)比較比較,功能差不多相同。運行一下看看,它們各占多少內(nèi)存:前者5.7M,后者占用17.7M!3倍多! ? 軟件size大,沒關系,你要大在地方,比如因為功能原因,code多一些導致size大我接受。但是你50%-70%的size都去裝metadata了,而我又不怎么用metadata(反射),你還要這么大放在那里,極大地損害軟件性能。 ? 這還是一個小小paint玩具軟件!你讓QQ、photoshop,office等軟件用C#/.NET開發(fā)試試?除非是“老趙們”自己開公司玩。 ? ? 反射性能問題總結(jié) ? 好了,我相信問題已經(jīng)分析清楚了,總結(jié)一下到目前為止,這篇文章的重點: 1.??反射的綁定和調(diào)用成本很高 ——?C#反射綁定與調(diào)用過程中元數(shù)據(jù)字符串比對,參數(shù)校驗,安全校驗,大量臨時對象,會讓使用C#反射時的軟件性能很差,盡量避免使用 2.??你不使用某些性能低的功能,不代表你依附的Application Framework不使用這些功能 —— 目前.NET平臺中WPF/SL, WCF,WF, ASP.NET MVC等幾大核心的框架都很重地使用了反射 3.??有些功能即便程序中不使用,為了支持這種機制,也要付出很高的代價 ——?哪怕所有的代碼都是你寫(不用Application Framework),而且不用一點反射的功能,C#編譯器還是給你的軟件中加了很多支持反射的metadata,占用很高昂的空間成本(大約是整個軟件size的50%) 4.??只要有較大的空間成本,那么時間成本也一定很高 —— 反射背后的metadata占用的高昂的空間成本,由于內(nèi)存加載、working set、cache missing 等各種問題,直接導致的時間成本很大,嚴重影響軟件的運行性能。 ? 上面的分析方法、依據(jù)、包括數(shù)據(jù)都是我和公司美國、德國同事,在開發(fā)C#/.NET產(chǎn)品時(大型醫(yī)療軟件),遇到的非常實際的問題(客戶接受不了C#/.NET寫的軟件速度),用符合工程的系統(tǒng)、全面的分析方法,研究各領域?qū)<业姆治鲆庖?#xff08;包括很多微軟技術專家),對C#/.NET進行的性能研究(不是寫個CodeTimer玩具比較比較兩段代碼就叫性能分析),我們嘗試了很多優(yōu)化策略——最后的結(jié)論就是繞不開C#/.NET底層設計帶來的根深蒂固的性能問題!反射就是一個性能公害! ? 好,相信看到這里,絕大多數(shù)朋友已經(jīng)深入理解了“反射所帶來的嚴重的性能問題”。但是有很多朋友可能還會有疑問,咦?怎么有些人寫C#性能也不錯,而且寫得頭頭是道,似乎很有道理啊。到底誰說的對啊? ? 這樣的疑問很正常,這些論調(diào)就是我前文說的“只見樹木,不見森林”。為了理清網(wǎng)友的疑問,我在下面的小節(jié)中針對這些“一葉障目”的觀點進行一一戳穿,以便于大家今后明辨是非。 ? ? 幾種典型的錯誤的性能論調(diào)或方法 ? 1.??函數(shù)計時論 要比較性能嗎?那好我們寫一段函數(shù),用一個時間計數(shù)器,在函數(shù)執(zhí)行開始處記錄下時間,在函數(shù)執(zhí)行結(jié)束前記錄下時間,最后一減得到的時間差,同樣的功能,哪個語言(或者哪種方式)用的時間少,哪個語言(或者哪個方式)用的時間多,性能差別,一目了然。多客觀啊!!! ? 比如,老趙曾經(jīng)在這篇博文中:一個簡單的性能計數(shù)器:CodeTimer http://www.cnblogs.com/jeffreyzhao/archive/2009/03/10/codetimer.html 抄襲.NET技術大會上Jeffrey Richter老人家show的性能計數(shù)器。 ? 然后下面這兩篇文章都是用這種“函數(shù)計時論”: 《C# vs C++ 全局照明渲染性能比試》: http://www.cnblogs.com/miloyip/archive/2010/06/23/cpp_vs_cs_GI.html 《回firelong之C#慢》 http://www.cnblogs.com/sumtec/archive/2010/06/22/1762564.html ? 問題是這種做法真的全面、客觀的反映了編程語言的性能了嗎???用這種辦法你可以說某一段C#代碼性能還湊合(比如《C# vs C++ 全局照明渲染性能比試》一文中的實驗結(jié)果,比C/C++差也就20~30%嘛,差的不多嘛!),但是問題是,這就是它們性能差別的全部真相嗎? ? 函數(shù)記時論,測量的只是某一個微觀代碼段的性能。不是一個軟件的總體性能。比如“函數(shù)記時論”就常常忽略掉我們前面metadata所帶來的高額的“空間成本”和“時間成本”。正規(guī)公司,只要是care性能的,對于性能評測都有一個系統(tǒng)的、全面的、完整的過程(比如在我們公司稱作Performance Process,和單元測試、重構(gòu)、等都作為一個嚴肅的軟件開發(fā)過程中的一個環(huán)節(jié)而存在),會借助一些系統(tǒng)性的工具:比如Compuware的Application Performance Management Solutions:參見這里:http://www.compuware.com/solutions/application-performance-management.asp來做一些系統(tǒng)性的評測報告。不是拿個CodeTimer這樣的玩具輸出幾個時間值,就拍腦袋下結(jié)論的。 ? 函數(shù)計時論經(jīng)常在各種技術社區(qū)中,吵架時展示的tricky demo中用于比較性能,但是放到一個正規(guī)公司的嚴肅項目里面,絕對不會使用這種方法來評估一個編程語言,平臺,或者軟件的性能。 ? 我希望 “老趙們”以后不要再拿CodeTimer這種玩具說事,要真全面比較性能,用Compuware的Application Performance Management Solutions一整套工具和過程來比較整個軟件的性能,而不是某一段微觀代碼的性能。 ? ? 2.??性能選擇論 某個功能影響性能,你不用不就沒影響了嗎?又沒有人逼你用! ? 前面已經(jīng)證明,C#/.NET的反射功能,你哪怕一點也不用,也有很大的性能成本(即:代碼中完全不用反射,為了支持反射的metadata帶來的空間成本和時間成本也非常高昂)。所以希望以后“老趙們”不要再說這樣的話。 ? 3.??損失忽視論 這個功能帶來的性能損失是很小的,可以忽略不計。 ? 性能是一個軟件最核心的使用指標——如果一個軟件性能不行,就是差軟件!沒有哪些個性能損失是可以忽略不計的。因為在程序代碼中,任何一個性能損失點,都有可能因為各種因素被放大(比如長循環(huán),大規(guī)模并發(fā)用戶等)。 ? “老趙們”喜歡寫“性能不咋地的高級企業(yè)應用”,然后忽悠客戶加硬件。但是請不要忽悠整個.NET社區(qū)的程序員以為天下的軟件都是“很高級的企業(yè)應用”。 ? 4.??性能墊背論 ? “Java的這個feature性能比C#的差,所以C#這個feature性能好”——C#的某些feature(比如反射)性能比Java好,但并不能說明這個feature本身沒有性能問題(這只能說明Java在這個上面性能太差,說明不了C#性能好)。 ? 請“老趙們”以后不要天天在.NET社區(qū)里說“C#這個比Java好,那個比Java cool”,這就像天天告訴自己的孩子,你比你們班最后一名的那個孩子好多了,你說孩子還能學好嗎???你怎么總拿C#跟差的比,不跟好的比呢? ? ? 最后結(jié)語 ? 好,文章寫完了,我希望.NET技術社區(qū)的“老趙們”圍繞“反射的性能話題”來辯駁,不要扯別的話題來放煙霧彈(C#/.NET中別的技術話題,我會在下面的文章中一篇一篇來討論,請大家耐心等待給我一點時間)。謝謝! ? 正要貼本文的時候,看到《關于C#開發(fā)山寨操作系統(tǒng),程序語言,瀏覽器,IDE,Office,Photoshop等大型程序的可行性歪論及意義》http://www.cnblogs.com/DSharp/archive/2010/06/24/1764210.html 這篇文章。我的回答非常明確:沒有任何可行性,且不論商業(yè)可行性、其他技術問題,光反射一項帶來的兩大性能負擔就把路堵死了——這也是我前文說的那么多軟件為什么不采用C#開發(fā)的一個關鍵原因——你搞一個100M的程序,中間有50M都是metadata,你還讓人程序活下去嗎?(記住,50M不僅僅是空間成本,帶來的時間成本照樣很大!) ? P.S. 本文中的“老趙們”指的是那些天天拿著C#語言新特性??岜硌?、而不研究真實技術問題的“所謂的技術精英們”,并不特指老趙一個人,或者老趙的每一個階段(老趙有一段時間還算在研究真問題)。請不要對號入座,謝謝! ? 轉(zhuǎn)載于:https://www.cnblogs.com/firelong/archive/2010/06/24/1764597.html
以上是生活随笔為你收集整理的C#会重蹈覆辙吗?系列之2:反射及元数据的性能问题的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
總結(jié)
- 上一篇: Flash与组件:国外收费组件网站
- 下一篇: Excel35招必学秘技