.NET中的高性能应用
本文要點
.NET自4.0以來得到了大幅的性能提升,很值得重新考慮一下基于舊版本.NET框架所做的假定。
在討論性能時垃圾回收是個重復出現(xiàn)的主題,它帶來了許多CLR和語言的提升,比如引用返回和ValueTask
在內(nèi)存分配上更細粒度度量的性能分析API會成為對當前版本API的最大改進(目前正在開發(fā))。
.NET包含有很多豐富的并行編程API和類庫,比如Task Parallel Library、Rx.NET 和Akka.NET。這一領域的挑戰(zhàn)在于教育用戶,以及為使更廣泛的社區(qū)輕松使用而所做的抽象。
.NET作為一個生態(tài)環(huán)境,具備支持物聯(lián)網(wǎng)或小型設備的必要構(gòu)件:.NET Native、.NET Standard 以及便捷CLR 組件。
隨著.NET Standard 和對.NET應用開放的新平臺的出現(xiàn),近期大家對.NET的性能越來越感興趣了。在傳統(tǒng)認知上,.NET不是一個編寫高性能應用的平臺,而通常會把它和企業(yè)級應用聯(lián)系在一起。
近幾年,有幾個正顯著改變這一狀況的趨勢。首先也是最重要的是,.NET Standard 為許多各種不同的平臺打開了大門,比如移動電話和物聯(lián)網(wǎng)設備。這些設備提出了新的挑戰(zhàn),資源的約束會比桌面和服務器多得多。
同時,能夠在Linux上運行服務端應用也向開發(fā)人員展示出極重要的機會和未知的領域。.NET Core還有待積累運行高性能應用的成功案例。在.NET生態(tài)環(huán)境中正在呈現(xiàn)出幾個新的趨勢:微服務、容器和無服務器。每個都帶著他們自己的性能需求。
討論組成員
Ben Watson?- 《編寫高性能.Net代碼》和《 C# 4.0 How-To》的作者
Sasha Goldshtein?- Sela Group的技術總監(jiān)、微軟C# MVP和區(qū)域主管
Matt Warren?- 熱愛尋找和修復性能問題的C#開發(fā)人員
Maoni Stephens?- .NET GC的核心開發(fā)人員
Aaron Stannard?- Petabridge的創(chuàng)始人和技術總監(jiān)
InfoQ:以你們的經(jīng)驗來看,在性能方面.NET處于什么位置?它和其他主流平臺相比如何?
Ben Watson:?.NET 處于強有力的位置,而且在不斷變強。CLR團隊對性能總是非常關注,最近幾年提升了.NET許多方面的性能,比如運行時編譯和垃圾回收。在微軟,我的產(chǎn)品已經(jīng)推動了一些這種改變,很高興看到這些改變能對整個世界有益,而不僅僅是微軟那些大型應用。
當然,也有一些弱點。在線上的世界里,每個請求都很重要,像運行時編譯和垃圾回收會妨礙取得極致的性能。也有解決辦法,但需要很大的工作量,那么就要看這最后一絲性能對你有多么重要了。
性能永無止境,任何平臺都是在做權(quán)衡。.NET給了你一些運行時難以置信的特性,但你必須按它的規(guī)則去充分利用它。最好的性能需要最好的技術努力。其他平臺會有不同的權(quán)衡,但無論如何技術努力都是不可或缺的。
Sasha Goldshtein:過去,大家都認為.NET是針對大型企業(yè)和Web應用的平臺,它們的性能不像游戲、嵌入式系統(tǒng)和實時消息處理那么重要。我認為,最近幾年這種看法發(fā)生了改變,因為更多的人開始認知到C#和F#的便利,以及.NET框架的強大功能和在非軟件操作系統(tǒng)上使用同一代碼庫的潛在能力,使得投身于使用.NET開發(fā)高性能系統(tǒng)值得去做了。
微軟在平臺性能上做了相當大的投入。舉些例子:幾年前已引入.NET Native以改善啟動時間和減少客戶端app的內(nèi)存使用;.NET 4.5 和 4.6對垃圾回收的伸縮性做了重大改進;.NET 4.6 改造了運行時編譯器,使其支持向量化了;C# 7引入了引用局部變量和引用返回,這是一些旨在讓語言層有更好的性能的特性。
總而言之,可能對于我個人而言是可以更容易地用C或C++這樣的低級語言來編寫小型高性能應用了。然而,若想把非托管代碼引入到已有的系統(tǒng)中,或使用這些語言開發(fā)大型數(shù)據(jù)庫,還需要慎重考慮。對于某些特定類型的高性能系統(tǒng),把寶押在.NET上還是可行的,只要你意識到相應的風險并具備在代碼層解決它們的工具,并且可以應對生產(chǎn)環(huán)境中出現(xiàn)的問題就可以了。
Matt Warren: .NET經(jīng)常被拿來和Java比較,因為他們有非常相似的運行時,也就是說,他們都有運行時編譯器、垃圾回收,并支持類似的面向?qū)ο蟮恼Z言(C# v. Java)。我想說的是,這兩個運行時有著同樣的性能特點,但當然也各有所長。
大家經(jīng)常說,Java Hotspot Compiler(JIT)性能比.NET JIT更為優(yōu)化。此外,它還有一些對性能有所幫助的額外特性,例如,它將在一開始解釋一個方法(為改進啟動時間),然后可能在稍后再編譯為機器碼。
然而,在另一方面,現(xiàn)在.NET已經(jīng)支持自定義值類型(結(jié)構(gòu))有很久了,它能讓你更好地控制內(nèi)存層,并有助于減少垃圾回收的影響。Java在不久的將來可能會有自己版本的值類型,但目前還沒有。
我認為,.NET是一個穩(wěn)固的平臺,它在各個方面都做得不錯。它總被打上比某某平臺慢的標簽,但我覺得對于它適用的負載類型,它的表現(xiàn)還不錯。
Maoni Stephens:討論性能時,往往都會提到垃圾回收。因為我就是從事這方面工作的,所以我在此次座談會上的回答將更多地圍繞垃圾回收的上下文來展開。雖然在有些人的印象中.NET具有高生產(chǎn)力而性能一般,但是其實已經(jīng)有很多由.NET寫成的具有高性能需求的產(chǎn)品。為確保我們的垃圾回收能夠處理這些需求,我們在交付重大特性之前總會由內(nèi)部合作伙伴來測試它,讓它來處理現(xiàn)實中最具壓力的負載,比如必應或 Exchange。我們不只是靠基本和大型基準測試,盡管它們非常有用,但嚴重缺乏在現(xiàn)實世界中性能的正確表現(xiàn)。.NET垃圾回收這十幾年來已經(jīng)做過大量的調(diào)整,與其他主流平臺相比已經(jīng)非常有競爭力了。
當然,垃圾回收只是框架的一部分。當有些人討論垃圾回收性能時,其實是在說“垃圾回收是如何來處理我給它的工作負載的”。如果工作負載把許多壓力都放到垃圾回收上,自然會比沒那么大的壓力情況產(chǎn)生更長時間和/或更頻繁的垃圾回收暫停。從歷史角度來看,.NET框架實現(xiàn)的方式和大家用.NET編寫代碼的方式往往會很容易把非常重的壓力丟給垃圾回收。雖然我們一直都在改進我們的垃圾回收,但具有高性能需求的人也需要不停去度量和分析什么因素正在影響著他們應用的性能。這和你在使用什么框架或類庫無關,換句話說,如果你想要達成和保持高性能,就需要去搞清楚你的工作負載是怎樣的表現(xiàn)。如果你對垃圾回收很感興趣,可以閱讀這篇博客,里面僅對垃圾回收的比較做了闡述。
實際上,垃圾回收是向成熟框架提出的一部分挑戰(zhàn),而較新的框架可能不需要面對這些挑戰(zhàn)。我們已經(jīng)有了許多寫完并用了很久的類庫,通常我們很難用新特性去打破現(xiàn)有的局面。即便我們要實現(xiàn),哪怕只有一小部分愿意為高性能投入更多精力的用戶使用,也要盡可能保證其像框架的主要特性一樣可靠。我看到一部分新框架推崇能夠測探性能的特性,但這實際是把不切實際的可靠性負擔甩給了用戶。那不是我們想要采取的方式。
我們的客戶總要求我們拿出更好的性能,因為他們這方面正在超越極限!我聽到許多過去常常使用.NET的人說,當他們由于工作變動而不得不切換到其他框架時,其實真的很想念它。所以現(xiàn)在.NET能運行于更多的操作系統(tǒng)令我非常地興奮。就像之前所說的,鑒于.NET類庫和應用的編寫方式,往往會給垃圾回收產(chǎn)生大量的工作,所以我認為我們需要在可預見的未來時間內(nèi)不斷地改進垃圾回收的性能,以及制作能幫用戶度量性能的工具。與此同時,我們也在考慮研發(fā)更多能夠降低垃圾回收壓力的特性。
Aaron Stannard:大多數(shù)“性能”差異歸根結(jié)底在于框架的頂層實現(xiàn)。出于很多原因,JavaScript不是個“快速”的語言,然而在所有TechEmpower 基準測試中Node.JS都把ASP.NET拋在了身后。為什么?主要原因是ASP.NET采用的是幾十年前特定于Windows的老技術,最初的實現(xiàn)采用的是一種同步的設計思路。而另一方面, Node.JS一開始就設計為異步的了,而且從開始到現(xiàn)在采納了許多其他性能優(yōu)先的選擇。
即便ASP.NET可能是慢的,但C#語言和CLR本身是非常快的。CLR支持值類型,與JVM相比這是性能方面的巨大優(yōu)勢,因為我們可以從本質(zhì)上釋放為這些類型分配的內(nèi)存(在一定程度上……)。CLR近幾年已經(jīng)有了很多的改進,使它在運行期“聰明”多了。例如,垃圾回收器已經(jīng)可以在回收垃圾時對內(nèi)存進行很好地碎片整理了,這樣對后續(xù)內(nèi)存訪問的速度提升有顯著的幫助。CLR JIT也可以使用性能分析信息決策如何在運行時最好地編譯本地函數(shù),比如是否使用轉(zhuǎn)移表或簡化C#接口上只有少量代碼實現(xiàn)的if語句。
C#語言本身還具備一個好處,那就是能夠讓開發(fā)人員對編譯器工作有更多的控制權(quán),你可以提供關于內(nèi)聯(lián)函數(shù)的指令,你可以把內(nèi)存鎖定到空閑的堆上和垃圾回收范圍之外,如果需要,你甚至可以將CLR分配對象視為不安全的,回溯C風格的指針運算。由于指針的存在,相比任何其他托管的語言使用C#和CLR進行性能方面的調(diào)整都會更加靈活。
InfoQ:你們用.NET編寫高性能應用時所面對的挑戰(zhàn)是什么?
Ben Watson:最大的挑戰(zhàn),在某些方面來說仍舊是垃圾回收。當大多數(shù)人首次開始用.NET編程時,會覺得這是理所應當?shù)?#xff0c;不去管它。對于大多數(shù)應用來說,它確實無關緊要,除非你的內(nèi)存分配模式非常糟糕。
然而,垃圾回收問題不應被忽視,特別是在永不停機的高性能系統(tǒng)中。像此類程序的編寫與基礎應用完全不同。例如,當你默認編碼模式導致每秒分配幾GB的內(nèi)存分配時,如何調(diào)整使你的程序不再是垃圾回收機制的主要誘因呢?如果你每毫秒都計數(shù),它什么時候有時間去回收呢?
不是說你要完全避免垃圾回收,而是說要使它的成本盡可能小,使它不會有太大的影響,在性能分析中不再是最突出的問題。為做到這一點,可能需要非常大量的技術工作,但結(jié)果會令人吃驚的。實際上,在托管代碼中也有可能達成像在非托管代碼中的性能,技巧雖然不同,但辦法總是有的。
做這件事需要從根本上轉(zhuǎn)變大家編寫托管代碼的思維,大家必須對得起工程師的名號。在某種程度上,這種技術思維的轉(zhuǎn)變是最難的部分。我們經(jīng)常不把自己的工具當回事,當性能真正重要時卻無能為力了。
Sasha Goldshtein:就像使用任何托管的平臺一樣,垃圾回收器是許多.NET應用變慢或暫停的源頭。當堆變大時,達成幾十甚至幾百GB,鑒于.NET沒有完全并行的回收器(所以某些收集需要把應用的線程完全掛起),這就與管理感應式垃圾回收有許多不同了。結(jié)果,許多人伸手就用非托管的內(nèi)存、把對象放到緩沖池里卻不再釋放,或者為了不出現(xiàn)完全的垃圾回收而非常小心地調(diào)整系統(tǒng)。這很令人遺憾,在我看來,垃圾回收應為應用開發(fā)人員服務,然而有時卻本末倒置了。
性能挑戰(zhàn)的另一個來源是潛伏在各種C#語言結(jié)構(gòu)和.NET類庫API中的各種地雷。例如,通常很難預測哪個類庫方法打算分配內(nèi)存,或者如何以最小內(nèi)存分配的方式來使用語言結(jié)構(gòu)。
最后,在 C/C++ 中仍然很容易去做一些操作,比如指針處理、高級的向量(目前System.Numerics.Vectors中已不再涵蓋)、一般計數(shù)代碼。在C#中得到同樣的結(jié)果經(jīng)常需要些反常的結(jié)構(gòu),比如不安全的代碼,從模版生成的代碼,甚至動態(tài)生成的IL或匯編代碼。
Matt Warren:在我之前工作過的幾個項目里,對垃圾回收影響的處理很成問題。持續(xù)運行數(shù)小時或數(shù)日的系統(tǒng)會分配大量的內(nèi)存,在某一時刻,垃圾回收不得不去徹底清理。因為該應用有著非常嚴格的隱含需求(低于40毫秒),所以這些暫停就成了問題。那么學習如何去檢測、度量和緩解這些垃圾回收的影響就成了我們最大的性能挑戰(zhàn)。
Maoni Stephens:垃圾回收是由內(nèi)存的分配和殘留引發(fā)的。我們想要幫框架程序集的作者和客戶找出與此相關的信息,繼而使垃圾回收的作業(yè)更加簡單。這些年,我們與客戶探討過展現(xiàn)內(nèi)存分配的幾種思路。我記得有一次我們探討了提供“內(nèi)存分配打分”的API,但這沒那么簡單,因為當你采用不同的參數(shù)或執(zhí)行不同的代碼路徑時,會有截然不同的內(nèi)存分配。當然,我們已經(jīng)開發(fā)了能對你的內(nèi)存分配有所幫助的工具(我們有ETW事件和以貼切的方式呈現(xiàn)這些事件的工具),但如果能在我開發(fā)的時候就讓我看到調(diào)用一個API會分配多少字節(jié)的內(nèi)存,或者從A點到B點我已經(jīng)自己分配了多少字節(jié)的內(nèi)存,那就更好了。運行時可以提供這些信息,但我還沒看到有IDE可以做到這一點。
我們所從事的另一件事是,PerfView中一個能幫助查看舊一代對象是如何持有新一代中對象的,也就是說,使它們留存下來。這是短垃圾回收成本的重要部分。我想我們還沒有將其推向大眾(但代碼在PerfView里了,感興趣的話可以查看CROSS_GENERATION_LIVENESS),因為我們還沒有時間使此實現(xiàn)足夠穩(wěn)定。它能讓你找出可以設置為null的對象的域,讓垃圾回收在自然發(fā)生時知道你已經(jīng)不再持有它們的生命周期。
Aaron Stannard:總之,我發(fā)現(xiàn)基礎CLR本身在性能工具方面還落后于其他平臺。這也是我編寫?NBench的其中一個原因,它是一個針對.NET應用的性能測試框架。
舉個例子,Java運行環(huán)境暴露Java管理擴展(JXM)工具鏈接,這使開發(fā)人員可以非常容易地訪問到詳細的性能統(tǒng)計,通過圖形界面或者JMX API都可以。許多針對大規(guī)模JVM應用的管理和監(jiān)控工具都是直接以此為基礎開發(fā)的,立刻進入腦海的一個例子就是針對 Apache Cassandra的DataStax OpsCenter。退一步說,你至少不能不經(jīng)度量就去討論性能是“好”、“壞”或“更好”吧,在我的經(jīng)驗里,在CLR上這么做可是個很痛苦的經(jīng)歷。
目前針對.NET性能的內(nèi)置解決方案是Event Tracing for Windows (ETW)和性能計數(shù)器,與JMX相比,在一定規(guī)模上實現(xiàn)正確性和可靠性還是有點難的。你可能無法在.NET Core中使用它們,因為它們是特定于Windows技術的,據(jù)我所知,目前這種監(jiān)控.NET Core應用的內(nèi)置選項還不多。
我用.NET開發(fā)高性能系統(tǒng)遇到的其他大的挑戰(zhàn),是去弄清楚我所依賴的其他.NET類庫和組件的性能。因為大多數(shù)開發(fā)人員不清楚如何去對他們的代碼進行性能測試乃至性能分析,所以在任何時候把第三方類庫引入到自己的應用中都要冒著很大的風險。我記得在我其中一個大規(guī)模.NET應用中(每天數(shù)百萬個事務)安裝了一個StatsD監(jiān)控組件,這個類庫讓我的CPU利用率飆升了百分之三十左右。結(jié)果是,它用底層UDP端口做了些很怪的事,我不得不給修復它好讓性能回到之前可接受的水平。
InfoQ:.NET在處理并行和并發(fā)上做得怎么樣?有重大改進的空間嗎?
Ben Watson:Task Parallel Library(TPL:任務并行類庫)是該平臺的一大飛躍。它完全轉(zhuǎn)變成了.NET中的并行編程。我們在線上平臺中使用它開發(fā)了一個處理實時查詢的高擴展性應用。若要得到更好的性能,正確編寫的TPL應用僅通過增加更多的處理器就可以很簡單地進行擴展了,實際上我們多次發(fā)現(xiàn),都不需要修改代碼。
即便如此,編寫高擴展性系統(tǒng)仍然是一個非常難的事情。它需要深入理解硬件、操作系統(tǒng)以及CLR的內(nèi)部構(gòu)件。你的期望越高,就越需要慎重考慮每一層。
它正朝著更高、更容易開發(fā)的方向抽象,但仍未達到初級程序員就可以高效準確使用的程度。
從實用的角度來看,實現(xiàn)完美并發(fā)經(jīng)常與避免潛在的阻塞調(diào)用有關。在這個領域,工具還有些改進的空間。我們需要更好的方式來強調(diào)代碼中有問題的區(qū)域,并建議消除這些性能障礙的方式。除非你正在用高度結(jié)構(gòu)化的框架編碼,限制你的一樣做法,否則很容易就會因為不慎重地使用公共API而破壞了性能。如果我們可以把平臺和此類工具結(jié)合,幫你擔起并發(fā)的重擔,我想這能極大地幫助大眾簡單有效地使用并發(fā)。
Sasha Goldshtein:.NET框架有豐富的并發(fā)并發(fā)API:Task Parallel Library、TPL DataFlow,以及 async/await語言特性。這些包括數(shù)據(jù)并行(當你想要并行獲取大規(guī)模條目并進行特定轉(zhuǎn)換時),以及無結(jié)構(gòu)的任務并行(當你有一組想要安排進一個管道并發(fā)執(zhí)行的任務時)。最后,還包括有一些好的異步I/O操作,它們對于高性能服務端代碼來說極為重要。
然而,在GPGPU上還沒有太多的進展,為使.NET應用可以分流通用計算到GPU;在混合方面,有像Xeon Phi這樣的內(nèi)部處理卡;而在向量方面,有比當前的System.Numerics.Vectors更先進的向量指令。使用.NET編寫高并發(fā)的計算密集型應用仍受限于主流處理器(受限于插槽和核相對較少),如果你把混合計算引擎添加到系統(tǒng)中,就變成非常難了。
Matt Warren:我們在.NET中很幸運,里面有許多可能幫到我們的語言和運行時特性。async/await關鍵字、Task/Task乃至經(jīng)過大量調(diào)優(yōu)的線程池。然而,看起來這樣的重大改進現(xiàn)在正在不斷進入高層類庫,例如實現(xiàn)“Actor”并發(fā)模型的Akka.NET 。
Maoni Stephens:在V1.0中我們交付了Server GC模式(一種并行GC)和 Workstation GC模式(一種非并行但在一定程度上是并發(fā)的)。在V4.0我們演進了Workstation GC使其有更強的并發(fā),然后在V4.5中使其可用于Server GC了(也就是Background Server GC),所以它已經(jīng)同時支持并行并發(fā)了,這讓我們可以極大改進大規(guī)模服務器負載,大內(nèi)存堆上的完全垃圾回收暫停從gen2的數(shù)秒阻塞降到了通常Background gen2的10毫秒。我們一直都在不斷地改善已有的垃圾回收,我們還將引入重大改進以幫我們消除一些障礙,比如降低完全阻塞垃圾回收的頻率到非常低的程度(使大多數(shù)人不再擔心這一點),以及在不犧牲太多吞吐率的情況下真正縮短暫停時間。
Aaron Stannard:.NET有一些并行并發(fā)的最佳工具:Task and Parallelism Library (TPL)、 async/await、asynchronous garbage collection、reactive extensions (Rx),以及許多像Akka.NET之類的OSS類庫。
改進空間總是有的:例如,當前為TPL添加的ValueTask類型,它讓一些任務可以返回自由分配的內(nèi)存,這有非常大的好處。
這讓我想起了并行并發(fā)里其中的一個重要部分,即從性能角度來看可以改進的一個地方是,教育我們的最終用戶如何更好地使用這些工具。關于async / await 有一個貨船崇拜的神話,特別是現(xiàn)在,導致有些開發(fā)人員認為async是一個用于“高速模式:啟動!”的關鍵字,于是就產(chǎn)生了如下的代碼塊:
await Task.Run(() => {// 不會從并發(fā)得到任何好處的代碼 }); await Task.Run(() => {// 不會從并發(fā)得到任何好處的更多代碼 });并發(fā)和并行不是魔法,它們是為了提升效率而必須要適當應用的思想。.NET作為一個生態(tài)環(huán)境沒有強調(diào)這些要點的學習,因此我們中那么多人盡管采取了這些神奇的工具,卻構(gòu)建了緩慢和浪費資源的應用。所以換句話說,關于這些高性能和并發(fā)技術的文檔和言傳身教極為重要,然而如今的交流卻忽視了它們,實在令人扼腕。
InfoQ:與桌面.NET框架相比,.NET Core在性能層面帶來了什么優(yōu)勢嗎?
Ben Watson:CLR的基礎部分也是對性能影響最大的部分。我覺得.NET Core最大的優(yōu)勢是“真愛在這里”,注意力都集中在這里了。桌面 .NET不是事后的想法,但是確切來說,是能更清楚地預計到會有更多人轉(zhuǎn)到.NET Core。如果有缺陷需要修復,或者改進需要實現(xiàn),那么首先會在.NET Core了中處理,然后才在相對緩慢的.NET更新發(fā)布時間表中予以考慮。
Sasha Goldshtein:盡管.NET Core和桌面.NET都來自于同一個共享的代碼庫,但是在.NET Core中有許多創(chuàng)新和優(yōu)化的機會,因為它有更快的發(fā)布節(jié)奏。然而,如果你比較.NET Core 和桌面.NET兩個時間點的版本,它們來自于同一個代碼庫,我不覺得你會認為有性能差異:其內(nèi)部是相同的C#編譯器、相同的JIT和相同的垃圾回收引擎。
Matt Warren:幸運的是微軟正好寫了一篇名為“.NET Core中的性能改進”的博客,里面介紹了一些當前的優(yōu)勢。有些改進方法可能曾在.NET框架中出現(xiàn)過(基于向后兼容的考慮),但它表示當前的.NET Core是領先的。
看起來,社區(qū)的幫助(因為它會開源)加上能夠比.NET框架更加快速地迭代,意味著在.NET Core.中會實現(xiàn)更多的改進。
我可以想象,往后差異會越來越大,也就是說,越來越多的性能改進將只出現(xiàn)在.NET Core中,特別是如果他們還新增額外的API的話。
Maoni Stephens:所有.NET SKU共享同一個垃圾回收,垃圾回收POV的最大優(yōu)勢在于,你實際上可以在Core中快速完成一個特性讓大家去試用,因為它是開源的,而不必等到下一個桌面版本才提供給所有的客戶。
Aaron Stannard:巨大成功。已實現(xiàn)了對Spans、System.IO.Pipelines和許多基礎.NET類型(比如字符串)工作方式的改進,這是針對這些類目前在傳統(tǒng).NET 4.*框架上工作方式的所有重大改進。還實現(xiàn)了像ConcurrentQueue這些工作方式的根本性改進,這應該有助于顯著提升并發(fā)應用的速度。
InfoQ:在高性能領域,.NET Native扮演著什么角色呢?在什么情況下它能勝過托管的副本?
Ben Watson:.NET Native 有許多潛力。通常在移動設備場景和Windows商店應用中它非常有用,在那兒你可不想使用用戶有限的電池壽命進行運行時編譯。然而,我很想看到此類技術傳播到傳統(tǒng)桌面和服務端應用,每臺機器一次又一次地在運行時編譯同一個東西會造成大量電力、故障時間、延遲時間等方面的浪費,尤其是數(shù)據(jù)中心更為突出。NGEN有嚴苛的限制,.NET Native最終可以解決的。
Sasha Goldshtein:.NET Native是一個很有意思的項目,因為它把完全的靜態(tài)提前編譯(AOT (ahead-of-time)compilation)帶進了.NET的世界。我說“完全的AOT”,是因為NGen從一開始就已經(jīng)是.NET框架的一部分了,但它仍然需要完整安裝.NET框架,需要用于元數(shù)據(jù)的原始解釋語言模型,以及可以根據(jù)需要開啟運行時編譯。
不過最重要的是,.NET Native構(gòu)建所做的優(yōu)化步驟比基本運行時編譯或NGen要先進得多(實際上,使用了像C++一樣的后端優(yōu)化)。這并不奇怪,因為運行時編譯是在非常嚴格的時間約束下運轉(zhuǎn)的,然而.NET Native編譯會為一個復雜項目花幾分鐘。最終結(jié)果很讓人震驚:對于計算密集型應用,應用更好的優(yōu)化某些部分能大幅提速;而通過減少需要從磁盤加載的依賴和類庫,以及使用運行時編譯,能提升啟動時間。
Matt Warren:.NET Native 的其中一個好處是你的代碼是提前編譯的,而不是在運行時由JIT編譯器編譯的。還有兩個好處,一個是因為在運行時編譯的場景中,所以啟動時間比較快,但首次被調(diào)用的方法必須予以編譯,這會帶來開銷。第二個好處是,靜態(tài)提前編譯器可以做更加昂貴且大范圍的優(yōu)化,而這在JIT中是不可能的。JIT編譯器無法進行這種嘗試,因為它必須在限定的時間里進行工作,而AOT編譯器則不受此約束。
Maoni Stephens:.NET Native減少了啟動時間和磁盤占用。因為它使用了一個做整體分析的編譯器,它能夠執(zhí)行更多的優(yōu)化。我認為在容器的應用場景中它會成為重要角色。
Aaron Stannard:從部署的角度來看,.NET Native會比較方便,因為它讓用戶可以部署.NET應用到從未安裝過.NET運行環(huán)境的機器上,這有點兒像靜態(tài)編譯過的C++應用。但我覺得這么編譯出來的應用在性能上比不上它的托管副本。本機編譯的應用不能充分利用JIT的運行時性能分析,它本來可以讓應用基于實際在運行時的使用情況自己做出性能調(diào)整的。.NET Native應用應該比總是需要.NET JIT的應有更短的啟動時間,但我覺得即時編譯的應用的運行時性能通常會更好一些。
InfoQ:最近.NET語言里添加了幾個性能相關的特性,比如值元組和引用返回。你們覺得接下來會看到什么特性?
Ben Watson:我很想看到在高吞吐率場景下緩沖對象以降低內(nèi)存分配率這一方面的進展。我知道許多緩沖內(nèi)部數(shù)據(jù)結(jié)構(gòu)以降低內(nèi)存加載的.NET API,即便一些官方設計指南很有幫助,但這種緩沖技術應用于非常特殊的用途。一些CLR恰恰可幫你輕松創(chuàng)建簡單可復用的對象,減少垃圾回收上的開銷。
我還希望看到更具伸縮性的JIT。它不慢,但是,在多核上即時編譯代碼會有瓶頸,它使你無法在運行大型應用時把初始化時間降到最低。
Sasha Goldshtein:我希望C#能夠在數(shù)字類型上,或者不共享接口的其他任意家族類型上使用真正通用的代碼。我想到了C++模版,因為你可以創(chuàng)作一個對任意數(shù)字類型(或家族類型)有作用的函數(shù)模版,而不必預先規(guī)定限制條件。不幸的是,看起來引入此類特性需要在CLR方面做出改變,而涉及語言設計是個非常謹慎的事,自CLR 2.0引入泛型以來就在盡量避免變動。
盡管我可以理解這些版本,但我仍然希望看到另一個特性,那就是在C#編程中內(nèi)聯(lián)解釋語言甚至是內(nèi)聯(lián)匯編。這會為進一步優(yōu)化、向量指令和其他硬件相關的優(yōu)化打開新的大門。
Matt Warren:Span,雖然我知道它們即將到來,但其實可能只是個說辭!能夠高效訪問接近或匹配數(shù)組的“任意內(nèi)存的連續(xù)區(qū)域”是最大的好處。一旦他們是在運行時實現(xiàn)的,我們就會看到Span廣泛應用于所有框架和第三方類庫,這將發(fā)揮非常廣泛的作用。
Maoni Stephens:我們在垃圾回收上的哲學已經(jīng)極少有旋鈕了,因為我們認為用戶不應該為了調(diào)優(yōu)內(nèi)存而去花很多時間精通垃圾回收的知識,這應該是我們的工作。我們只為用戶提供了幾個配置,告訴我們他們的應用是什么類型就可以了,比如,是服務端還是客戶端。我們一直都在遵循這個哲學。我們想提供更多的配置,讓客戶告訴我們發(fā)應用性能上下文中如何幫助他們,例如,他們的應用是否想要用更大的堆換得更可預測的暫停;或者在機器上有64GB的內(nèi)存,但是他們希望某一進程只使用32GB。我們正在增加能對糟糕局面做出更好反應的新機制和策略,以使垃圾回收更為健壯,那么我們就可以滿足這些配置的要求了。這是垃圾回收團隊在不久的將來將交付的下一個與性能相關的其中一個特性。
Aaron Stannard:我希望看到一些基本的集合能夠原生支持引用返回,從性能角度來看,這樣能帶來一些真正有趣的可能性,使之能夠傳遞引用到相同的值類型或一系列這種類型的迭代器。我不知道可能會有什么性能收益,但我希望可以進行實驗得出結(jié)論。
不過我希望看到的最大特性是,用于.NET Core的跨平臺的性能工具。我也需要能夠得到在Linux運行環(huán)境中垃圾回收以及其他CLR核心部分的運行時性能指標!
InfoQ:雖然高性能通常與服務端應用有關,但移動和物聯(lián)網(wǎng)設備之類的其他平臺也提出了獨特的性能需求。.NET可以滿足這些非傳統(tǒng)平臺的需求嗎?
Ben Watson:我持樂觀態(tài)度,我們將把.NET看作為一個用于小型和更多獨特設備的優(yōu)秀平臺。我們正看到.NET更多的模塊化方法,在此各種組件可以按需增增減減,或者替換。隨著日趨成熟,我覺得我們將看到一些用于不同設備的非常適當?shù)募軜?gòu)。
在許多方面,我認為今后的高性能應用(.NET及其他)在于適當?shù)膭?chuàng)建,以及具有約束的可復用框架,通過這些約束實現(xiàn)高性能模式。
Sasha Goldshtein:移動和物聯(lián)網(wǎng)方面,它們非常關注于快速啟動時間、更少的內(nèi)存占用,以及面對垃圾回收的決定性性能。這個領域還有些工作要做,比如.NET Native,而在減少垃圾回收暫停或削減.NET應用內(nèi)存大小上普遍沒有太大的進展。我現(xiàn)在擔心,如果它偶爾來次5秒種的垃圾回收,那就很難用C#做物聯(lián)網(wǎng)應用了,而高性能的移動應用也存在同樣的問題。未來在.NET Core和CoreCLR上性能方面的工作必須考慮到這些非傳統(tǒng)性需求。
Matt Warren:要使.NET運行于其他平臺(比如樹莓派和三星的Tizen OS)還有許多工作要做,迄今為止看起來.NET運行時調(diào)整得不錯。針對更多受限的設備做出改變多么有意思呀(比如降低內(nèi)存開銷),所以我們?nèi)鞘芤嬲?#xff01;
Maoni Stephens:機理通常都已經(jīng)有了。我們需要做些性能調(diào)優(yōu)使它們可以更好地應用于其他的平臺。舉個簡單的例子,當電話開始使用Core時,我們需要針對Workstation GC減小段的大小。UWP應用往往會使用更多的垃圾回收句柄,這意味著我們可能需要使我們的垃圾回收句柄代碼路徑更加優(yōu)化。這些事需要我們?nèi)ナ占瘮?shù)據(jù),它對于我們決定優(yōu)先級非常重要。
Aaron Stannard:在我們公司,已經(jīng)有客戶將.NET用于高吞吐率的物聯(lián)網(wǎng)應用了。現(xiàn)在甚至都用不著.NET Core。我期待,隨著.NET Core運行時的成熟和更多的類庫開始支持它,會發(fā)展得越來越好。在移動這一方面,隨著Unity3D遷移至新版本的C#和Mono運行時(順便一提,它也快得多了,從我們的基準測試結(jié)果來看,它已經(jīng)接近Windows桌面CLR的水平),我期待看到Unity3D應用的性能有顯著提升。
總結(jié)
自.NET4.0以下,.NET及其語言在性能方面已經(jīng)有了顯著的發(fā)展,使古老的假設成了過去式。除了垃圾回收、JIT和BCL方面的改進,.NET Native之類的新增內(nèi)容和TPL還拓寬了可用.NET處理的應用場景。許多已列入計劃或正在實現(xiàn)的改進證明,它們一直在密切關注升級和新的基準測試。
關于作者
Ben Watson?從2008年以來已經(jīng)是微軟的一名軟件工程師了。必應是世界領先的、基于.NET的、高性能服務端應用之一,它通過數(shù)以萬計的機器為無數(shù)客戶處理著大量低延遲請求,他作為必應平臺的核心開發(fā)人員在該平臺的建設中起著不可或缺的作用。他是性能的狂熱追求者,花了很多的時間在教育團隊高性能.NET的最佳實踐上。在他閑暇的時間里,喜歡享受地理尋寶、樂高、閱讀、古典音樂,陪伴妻子和孩子在美麗的太平洋西北部度過美好時光。他是《編寫高性能.Net代碼》和《 C# 4.0 How-To》的作者。
Sasha Goldshtein?是Sela Group的技術總監(jiān)、微軟C# MVP和區(qū)域主管、Pluralsight的作者,以及國際咨詢師和培訓師。他是《Introducing Windows 7 for Developers》(微軟出版社,2009年)和《Pro .NET Performance》(Apress出版社,2012年)的作者,他是一名多產(chǎn)的博客寫手和開源貢獻者,開發(fā)了許多著名的培訓課程,其中包括.NET 調(diào)試、.NET性能、安卓應用開發(fā)以及現(xiàn)代C++。他的咨詢工作主要圍繞分布式架構(gòu)、產(chǎn)品調(diào)試和性能診斷,以及移動應用開發(fā)。
Matt Warren是一名C#開發(fā)人員,他最喜歡的就是發(fā)現(xiàn)和修復性能問題。他與Azure、ASP.NET?MVC 和 WinForms合作過一些項目,比如用于存儲政府氣象數(shù)據(jù)的網(wǎng)站、醫(yī)療監(jiān)控設備以及一個確保啤酒桶沒漏的檢測系統(tǒng)!他是一名BenchmarkDotNet和CoreCLR的開源貢獻者。Matt目前主要忙著在CA Technologies做C#產(chǎn)品分析工具和在www.mattwarren.org寫博客。
Maoni Stephens?是.NET GC的主要開發(fā)人員。她在閑暇時喜歡閱讀垃圾回收方面的論文和觀看日本動漫或動物世界。平時,她喜歡使她關注的事情更加高效。
Aaron Stannard?是?Petabridge的創(chuàng)始人和技術總監(jiān),這是一個.NET數(shù)據(jù)平臺公司。Aaron是Akka.NET最初的貢獻者之一,他編寫了Akka.Remote module 和?Helios(底層socket中間件)。在從事Petabridge的事業(yè)之前,Petabridge Aaron 創(chuàng)立了MarkedUp Analytics,一個針對應用開發(fā)人員的實時應用分析和營銷自動化公司,他們的底層借助了Cassandra和Akka.NET。
原文地址:http://www.infoq.com/cn/articles/high-performance-dotnet
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結(jié)
以上是生活随笔為你收集整理的.NET中的高性能应用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么我们要使用Async、Await关
- 下一篇: .Net Core2.0下使用Dappe