国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 開發(fā) > 綜合 > 正文

發(fā)掘 C# 特性賦予科學(xué)計算項目以威力

2024-07-21 02:17:53
字體:
供稿:網(wǎng)友

原著:fahad gilani

翻譯:yy2better



原文出處:msdn magazine march 2004(c# in-depth)

下載此文章的代碼 scientificc.exe (127kb)

本文假定你熟悉 c#

摘要
  c#語言在多種項目中應(yīng)用的相當(dāng)成功,它們包括 web、數(shù)據(jù)庫、gui及其他更多類型項目。有充分理由認(rèn)為,c# 代碼最前沿的應(yīng)用領(lǐng)域之一很可能是科學(xué)計算。但 c# 能達(dá)到 fortran 和 c++ 應(yīng)用于科學(xué)計算項目的水平嗎?
  在本文中,通過研究由 .net 通用語言運(yùn)行時決定的 jit 編譯器、微軟中間語言和垃圾收集器如何影響性能,作者回答了這個問題。他還論述了 c# 數(shù)據(jù)類型,包括數(shù)組和矩陣,及其它在科學(xué)計算 應(yīng)用中起重要作用的語言特性。
 


  c#語言已獲得工作在不同領(lǐng)域開發(fā)者的尊敬并在他們中間得到相當(dāng)?shù)钠占啊W罱鼉赡辏琧# 在交付健壯的產(chǎn)品中起著重要的作用,從桌面應(yīng)用程序到 web 服務(wù),從高階商務(wù)自動化到系統(tǒng)級應(yīng)用程序,從單用戶產(chǎn)品到網(wǎng)絡(luò)分布環(huán)境中的企業(yè)解決方案,都有 c# 的存在。假設(shè)此語言的強(qiáng)大特性,你可能會問 c# 和 microsoft®.net 框架是否能被 更加廣泛地用于除gui和基于web組件之外的程序中。它是否已準(zhǔn)備好被科學(xué)團(tuán)體用于開發(fā)高性能數(shù)字性編碼?
  答案并不一目了然,因?yàn)槭紫刃枰卮鹨恍┢渌鼏栴}。例如,什么是科學(xué)性計算(scientific computing)?為什么它不同于傳統(tǒng)的計算?語言是否真的具備適于科學(xué)計算 編程的特性?
  本文中,我會揭示 c# 的一些內(nèi)在特性,它們允許開發(fā)者以輕松、實(shí)用的方式使用注重性能(performance-critical)的代碼。你將看到 c# 如何在科學(xué) 社區(qū)中扮演著重要角色,如何為下一代數(shù)字計算敞開大門。你也將看到,盡管謠傳因內(nèi)存管理開銷過大而使受管代碼運(yùn)行緩慢,但是復(fù)雜性適度的代碼運(yùn)行得很快;它根本不會被垃圾收集器中斷,因?yàn)榇蠖鄶?shù)數(shù)字上的處理不會需要足夠多的內(nèi)存釋放而導(dǎo)致調(diào)用垃圾收集。我將探究 c# 在數(shù)字計算世界里是不是一個好的選擇。我還將考察一些基準(zhǔn)(benchmark),并將結(jié)果與非受管 c++ 代碼進(jìn)行比較以便了解在性能和效率方面 c# 處在什么位置。

計算科學(xué)
  計算機(jī)的可用性已經(jīng)使科學(xué)家們更容易地證明理論、解答復(fù)雜方程式、模擬3d環(huán)境、預(yù)報天氣和執(zhí)行許多其它高強(qiáng)度運(yùn)算任務(wù)。多年來,數(shù)百種高級語言被研發(fā)出來以促進(jìn)計算機(jī)在這些領(lǐng)域的應(yīng)用(其中 有些是高度專業(yè)的并發(fā)設(shè)計概念框架,例如 ada和 occam;有些是曇花一現(xiàn)的計算機(jī)科學(xué)工具,例如 eiffel 或 algol)。然而,有少數(shù)成為卓越的科學(xué)性編程語言,例如 c、c++ 和 fortran——它們在當(dāng)今科學(xué)計算領(lǐng)域扮演 著主要角色已有相當(dāng)長的一段時間。
  但是,如何確定哪種高級語言用于科學(xué)計算呢?一般來說,某種語言要想有資格成為產(chǎn)生科學(xué)計算代碼的平臺,它必須包括的標(biāo)準(zhǔn)之一是提供一套豐富的能被用于衡量性能的工具 ,并且必須允許開發(fā)者輕松有效地表達(dá)問題域。本質(zhì)上,科學(xué)計算語言應(yīng)該能生成可以被細(xì)微調(diào)整的有效的高性能代碼。

性能和語言
  性能已經(jīng)成為區(qū)分用于科學(xué)計算編程語言的關(guān)鍵因素之一。編譯器和代碼生成技術(shù)常常被認(rèn)為是性能限制因素,但這種假定不完全正確。例如,使用最廣泛的 c++ 編譯器在代碼生成和優(yōu)化方面做得很好。 有一些微妙之處,它們與代碼的效率比起來,通常就不重要了。例如,c++中應(yīng)避免創(chuàng)建過多臨時對象,尤其它是一種非常容易創(chuàng)建未命名臨時變量但又不使用它們的語言。可以使用表達(dá)式模板達(dá)到此目的,表達(dá)式模板允許延遲數(shù)學(xué)表達(dá)式的實(shí)際運(yùn)算,直到它被賦值。結(jié)果可以避免在運(yùn)行時招致 巨大的抽象懲罰(abstraction penalty)。
  這不是說語言特性是影響性能的唯一因素。當(dāng)進(jìn)行語言之間明確的評估以衡量性能和成本時,真正評估的是編譯器編寫者的技巧,而不是語言本身。如果能從語言、運(yùn)行時或平臺中獲取可接 受的性能,那么選擇可能緊緊關(guān)乎個人所好。
  如果你已經(jīng)體驗(yàn)過 c# 并且正在考慮進(jìn)行真正的科學(xué)計算,那么沒必要采用其它語言;c# 綽綽有余。

msil和可移植性
  與所有其它面向.net的語言一樣,c# 編譯成微軟中間語言(msil),它運(yùn)行于通用語言運(yùn)行時(clr)。clr 可松散地被描述為just-in-time(jit)優(yōu)化編譯器和垃圾收集器 的混合物。c# 公開和利用了clr中的很多功能,所以更細(xì)致地研究該運(yùn)行時的工作機(jī)制是很重要的。
  科學(xué)家的關(guān)鍵需求之一是代碼可移植性。科學(xué)研究機(jī)構(gòu)和實(shí)驗(yàn)室擁有許多平臺和機(jī)器,包括基于 unix 工作站和pc。它們常常希望在不同機(jī)器上運(yùn)行代碼,以追求更好的結(jié)果或因?yàn)?某一特定的機(jī)器為他們提供一套數(shù)據(jù)處理和分析工具。然而,達(dá)到完全的硬件透明度已不是一個輕松的任務(wù)而且不總是完全可能。例如,多數(shù)大規(guī)模項目開發(fā)時使用了多種語言混合 的方法;因此,很難保證在一種架構(gòu)或平臺上可運(yùn)行的應(yīng)用程序也能在另一種上運(yùn)行。
  clr 使應(yīng)用程序和庫可被多種語言編寫,這些語言都可編譯成 msil。然后msil可運(yùn)行在任何支持它的架構(gòu)上。現(xiàn)在,科學(xué)家就可用 fortran 編寫它們數(shù)學(xué)庫,在c++中調(diào)用它們,使用 c# 和 asp.net 在 internet 發(fā)布結(jié)果。
  不像 java 虛擬機(jī)(jvm),clr是一個常規(guī)用途環(huán)境,它被設(shè)計用來面向多種不同的編程語言。此外,clr 提供了數(shù)據(jù)層,不僅僅是應(yīng)用層的互用性并允許在語言間共享資源。
  目前,可以獲得大量能輸出 msil 的語言編譯器。這些語言包括(但不限于)ada、c、c++caml、cobol、eiffel、fortran、java、list、logo、mixal、pascal、perl、php、python、scheme 和 smalltalk。另外,system.reflection.emit 名字空間大大降低了開發(fā) 面向 clr 的編譯器的進(jìn)入門檻。
  將 clr 移植到不同架構(gòu)是一項正在進(jìn)行的工作。然而,一份開源實(shí)現(xiàn)已由 mono/ximian 開發(fā)出來,并且可獲得 s390、sparc 和 powerpc 架構(gòu) 以及 strongarm 系統(tǒng)的實(shí)現(xiàn)。微軟也發(fā)布了一個運(yùn)行在 freebsd 系統(tǒng)上的開源版本,包括 mac os x。(更多信息請看 msdn 雜志 2002 july 上 jason wittington 的文章 "rotor: shared source cli provides source code for a freebsd implementation of .net")
  所有這些進(jìn)展發(fā)生在過去的僅僅數(shù)年中。假以更多時間,很可能一個全功能的 clr 將可以適用于所有通用架構(gòu)。

jit 編譯器是否變得更好?
  jit 編譯技術(shù)是一種非凡的技術(shù),它為廣泛的優(yōu)化敞開了大門。 盡管當(dāng)前實(shí)現(xiàn)的實(shí)際情況是:由于時間限制,能被完成的優(yōu)化在數(shù)量上是限制性的,從理論上講,它應(yīng)該比現(xiàn)有的任何靜態(tài)編譯器做得要好。當(dāng)然 ,這是因?yàn)樽⒅匦阅艿拇a的動態(tài)屬性或其上下文直到運(yùn)行時才被充分了解或被驗(yàn)證。jit 編譯器能通過生成更有效代碼來使用這些收集的信息,從理論上講,這些代碼每次運(yùn)行都會被再次優(yōu)化。通常,編譯器為每個方法只發(fā)出一次機(jī)器碼。一旦機(jī)器碼生成,它就以原始機(jī)器速度執(zhí)行。
  在科學(xué)計算編程中,這可能是一個便利的工具。科學(xué)性代碼主要由數(shù)字和用數(shù)字表示的算法組成。要在合理的時間內(nèi)完成這些計算,某些硬件資源需要被細(xì)心利用。雖然一些靜態(tài)編譯器在優(yōu)化代碼方面做得很好,但是 jit 編譯器的動態(tài) 本性允許使用眾多技術(shù)優(yōu)化資源利用,譬如基于優(yōu)先級的注冊配置,懶代碼選擇,緩存調(diào)整和特定 cpu 優(yōu)化。這些技術(shù)也為更嚴(yán)格的優(yōu)化提供了廣袤的空間,譬如 強(qiáng)度縮量(strength reduction)和常量繁殖(constant propagation)、冗余 的存儲后加載(load-after-store)、公共子表達(dá)式排除、數(shù)組邊界檢查排除、方法內(nèi)聯(lián)等等。雖然 jit 編譯器有可能實(shí)現(xiàn)這些類型的優(yōu)化,但當(dāng)前.net jit編譯器沒有 先進(jìn)到這一步。
  過去,程序員要確保運(yùn)行在某種機(jī)器上的代碼是為底層體系架構(gòu)進(jìn)行過手工優(yōu)化(例如軟件流水線,或手工利用緩存),此乃慣例。在另一臺不同硬件機(jī)器上運(yùn)行同樣的代碼需要修改原始代碼以對應(yīng)新硬件。隨著時間的流逝,處理器執(zhí)行代碼的方式 已經(jīng)有所改變,它們使用專門的內(nèi)置指令或技術(shù)。這些優(yōu)化現(xiàn)在能通過 jit 編譯器發(fā)掘出來,不需要修改現(xiàn)有代碼。結(jié)果,運(yùn)行在工作站上的代碼也可以在具有完全不同 體系架構(gòu)的家庭 pc 上運(yùn)行得一樣好。
  .net框架1.1版本發(fā)布的 jit 編譯器相對于它的前輩 1.0 版本有相當(dāng)?shù)母倪M(jìn)。figure 1顯示 clr1.0 和 1.1 版本的執(zhí)行比較,它是通過在兩個平臺上運(yùn)行 scimark2.0 基準(zhǔn)套件得出的。測試機(jī)器配置是 奔騰四 2.4ghz,256兆內(nèi)存。


figure 1 .net 1.1中 jit 的改進(jìn)

  scimark 基準(zhǔn)由許多在科學(xué)計算應(yīng)用中建立的通用計算要素組成,在內(nèi)存訪問浮點(diǎn)運(yùn)算方面各自處理不同的行為模式。這些要素是:快速傅立葉轉(zhuǎn)換(fft)、連續(xù) 松弛迭代(sor:over-relaxation iterations)、用于復(fù)雜線性系統(tǒng)的解決方案的蒙特-卡羅積分、稀疏矩陣乘法和稠密矩陣分解(lu) 。
  scimark 最初用 java 開發(fā)(http://math.nist.gov/scimark),后來被 chris re 和 wener vogels 移植到 c#(http://math.nist.gov/scimark)。注意這個實(shí)現(xiàn)沒有使用不安全代碼,這會使它運(yùn)行速度提高 5 至 10 個百分點(diǎn)。
  figure 1 以每秒百萬浮點(diǎn)運(yùn)算數(shù)(mflops)顯示了.net框架兩個版本的綜合得分。這給你一個大致的概念:當(dāng)前版本(1.1) 運(yùn)行得如何 以及未來版本改進(jìn)得將有多好。
  此圖顯示公共語言運(yùn)行時 1.1 版本勝過 1.0 版本一大截(具體在這里是 54.1 mflops )。版本 1.1 在整個實(shí)現(xiàn)中融進(jìn)了許多性能改進(jìn)技術(shù),包括一些已加入 jit 編譯器中的針對特定架構(gòu) 的優(yōu)化,譬如使用 ia-32 sse2 指令進(jìn)行浮點(diǎn)數(shù)到整數(shù)轉(zhuǎn)換。當(dāng)然,編譯器也對其它處理器生成對應(yīng)的優(yōu)化代碼。
  我期待下一次發(fā)布的 jit 編譯器將表現(xiàn)更佳。jit 編譯器將產(chǎn)生比靜態(tài)編譯器更快的運(yùn)行代碼,這只是時間問題。

自動內(nèi)存管理
  從實(shí)現(xiàn)角度看,自動內(nèi)存管理大概是 clr 給開發(fā)者最好的禮物。與 c/c++ malloc 或 new 調(diào)用中緩慢且昂貴的鏈表橫斷式釋放相比,crl 的內(nèi)存分配相對較快(堆指針僅僅被移動到下一個空閑槽)。而且,內(nèi)存在運(yùn)行時是自動管理的,例如自動釋放和整理未使用空間。程序師不再需要追蹤指針、過早釋放內(nèi)存塊或根本不釋放它們(雖然像 c# 和 visual c++® 這樣的語言仍賦予開發(fā)者那樣的選擇)。
  幾乎可預(yù)見的是,許多開發(fā)者對考慮使用垃圾收集器的反應(yīng)。然而,對于使用內(nèi)存頻繁的應(yīng)用程序,垃圾收集器確實(shí)導(dǎo)致小小的運(yùn)行時成本,它們還處理所有跟蹤內(nèi)存泄漏 的凌亂細(xì)節(jié)以及清除搖擺指針。它們始終保持對堆資源的管理、使其緊湊并可以重復(fù)利用。
  最近的研究和試驗(yàn)顯示,在計算密集型應(yīng)用中,對象的分配和釋放更加頻繁,垃圾收集器通過堆壓塑實(shí)際上可以提高性能。另外,內(nèi)存中以不同方式隨機(jī)展開的被頻繁引用的對象被緊湊地收集在一起以 便提供更佳的定位和緩沖利用。這大大加速了整個應(yīng)用程序的性能。同時,垃圾收集器的缺點(diǎn)之一是其不可預(yù)測的時間性,這導(dǎo)致很難使收集工作只在正確的時刻執(zhí)行。這個領(lǐng)域的研究正取得進(jìn)展,垃圾收集器這些年已有改進(jìn)。 屆時,更好的算法會出現(xiàn),從而提供更具確定性的行為。
  早先我提及,數(shù)字處理代碼通常不調(diào)用垃圾收集。對一些適度簡單的應(yīng)用來說確實(shí)如此,在這些應(yīng)用程序里,主要涉及數(shù)字,也沒有太多相關(guān)的內(nèi)存分配。這其實(shí)取決于問題的本質(zhì) 以及你已經(jīng)設(shè)計出來的方案。如果它涉及許多生命期為中短期的對象,垃圾收集器將會被相當(dāng)頻繁地調(diào)用。如果只有少數(shù)長生命期對象,并且一直到應(yīng)用程序結(jié)束時才釋放,那么這些對象將被提升 為年長一代,并導(dǎo)致收集調(diào)用顯著減少(如果有的話)。
  figure 2 展示了一個沒有調(diào)用垃圾收集器的執(zhí)行矩陣乘法的應(yīng)用程序。我選擇矩陣是因?yàn)樗鼈兪乾F(xiàn)實(shí)世界里許多科學(xué) 計算應(yīng)用程序的核心。矩陣提供了一種實(shí)用方式來解決許多應(yīng)用領(lǐng)域的問題,例如在計算機(jī)圖形算法、計算機(jī) x 線斷層攝影、遺傳學(xué)、密碼學(xué)、電力網(wǎng)和經(jīng)濟(jì)學(xué)領(lǐng)域。
  figure 2 代碼定義了一個matrix類,它聲明一個二維數(shù)組用于存儲矩陣數(shù)據(jù)。main方法創(chuàng)建 該類的三個實(shí)例,每個維度都是200×200(每個對象約313kb大小)。這些矩陣每一個引用以傳值方式傳遞到 matmul 方法(引用本身以值方式傳送而不是實(shí)際的對象),然后 matmul 方法執(zhí)行矩陣 a 和 b 的乘法,并存儲結(jié)果到矩陣 c。
  為了更加有趣,matmul 方法在一個循環(huán)中被調(diào)用 1000 次。換句話說,我控制著這些對象的重用以有效地執(zhí)行 1000 次“不同”矩陣的乘法運(yùn)算而沒有一次調(diào)用垃圾收集器。通過.net通用語言運(yùn)行時的內(nèi)存性能計數(shù)器, 你可以監(jiān)視收集的次數(shù)。
  然而,對于較大型的計算,如果你請求比可用內(nèi)存更多的空間,垃圾收集最終不可避免。這種情形下,你可以二者擇一。例如孤立代碼中性能關(guān)系密切的代碼,將之改為非受管代碼,然后從c#受管代碼中調(diào)用它們。這里一個警告是 p/invoke 或.net interop 調(diào)用會招致小的運(yùn)行時開銷,所以你可能將其作為最后一種選擇,或者如果你確信運(yùn)算的粒度足以能抵消調(diào)用所需的開銷,就采用它。
  垃圾收集功能不應(yīng)該阻礙生成高性能科學(xué)計算代碼。其目的是消除你用別的方式不得不面對的內(nèi)存管理問題,只要你明白它的工作原理以及使用它所需的成本,你就不需要擔(dān)心垃圾收集 的開銷。
  現(xiàn)在讓我們離開 clr,轉(zhuǎn)到語言本身。正如我早先提及,c#有許多特性使它十分適合于科學(xué)計算。下面讓我們逐一討論這些特性。

面向?qū)ο?
  c#是一種面向?qū)ο笳Z言。既然現(xiàn)實(shí)世界是由具有動態(tài)屬性的密切相關(guān)的對象組成,那么面向?qū)ο蟪绦蛟O(shè)計方法常常是科學(xué)計算編程問題的最佳解決方案。 此外,通過替代內(nèi)部代碼片段,結(jié)構(gòu)良好,面向?qū)ο蟮拇a可以更容易被修改以適應(yīng)科學(xué)計算模型的變化。
  然而,不是所有科學(xué)計算問題都表現(xiàn)為類似對象的特性或關(guān)系,所以面向?qū)ο蠓椒ㄔ谔幚泶祟悊栴}時會導(dǎo)致不必要的復(fù)雜性。例如,figure 2 中 已有的矩陣乘法代碼沒有使用專門的類——在單個類中聲明三個多維數(shù)組的矩陣。
  問題可能涉及對象間復(fù)雜的關(guān)系,而對象的編程則帶來進(jìn)一步的復(fù)雜性或不必要的開銷。以分子動力學(xué)(md)為例,分子動力學(xué)廣泛應(yīng)用于計算化學(xué)、物理學(xué)、生物學(xué)和材料科學(xué)。最早使用計算機(jī) 進(jìn)行科學(xué)計算者之一要追溯到1957年,alder 和 wainwright 模擬了 150 個氬原子的運(yùn)動。在md中,科學(xué)家感興趣的是通過兩個物體間的勢模擬原子間的相互作用,這種 方式類似于行星、太陽、衛(wèi)星和恒星之間受地心引力的影響而產(chǎn)生相互作用一樣。采用面向?qū)ο蠓椒ń蓚€原子間的相互作用可能不是一個壞想法。假設(shè)一個立方體內(nèi)包含n3個原子,n為一個很大的數(shù)字。 能量守恒等式所產(chǎn)生的算術(shù)計算強(qiáng)度是如此之大,以致于借助傳統(tǒng)的程序過程(非面向?qū)ο螅﹣韺?shí)現(xiàn)的話,必須進(jìn)行過程簡化和解決性能問題,也就是說,過程代碼會 變得更復(fù)雜且性能更差。它真正取決于數(shù)據(jù)存儲和所使用的算法。
  你可以用 c# 來解決問題。使用語言所具備的強(qiáng)大的面向?qū)ο竽芰赡懿皇亲罴堰x擇,但它為傳統(tǒng)的科學(xué)計算程序員提供了良好的開端。單個類可以包含用于執(zhí)行計算和產(chǎn)生結(jié)果的 所有變量和方法。盡管如此,對于相對較大的問題,由于 oop 提供的模塊化和數(shù)據(jù)完整性,它對科學(xué)計算編程來說是個很有價值的工具,這使代碼擴(kuò)充和重用變得 更容易。

高精度浮點(diǎn)運(yùn)算
  沒有哪個科學(xué)計算代碼能忽視精確度和準(zhǔn)確度。即使現(xiàn)今最強(qiáng)大的計算機(jī)也只能運(yùn)算有限位數(shù)精度,具備更高的精度將有助于獲取更準(zhǔn)確結(jié)果。這方面的重要性不能低估,由于計算機(jī)算術(shù)運(yùn)算錯誤而導(dǎo)致的災(zāi)難會讓你清楚地認(rèn)識這一點(diǎn),例如,1996 年著名的 ariane 5號非載人火箭爆炸(其慣性參照系統(tǒng)里一個64位浮點(diǎn)數(shù)被錯誤轉(zhuǎn)換為16位有符號整數(shù)……嘣)。當(dāng)然,你或許不會開發(fā)火箭軟件,但重要的是必須知道在許多科學(xué)計算應(yīng)用程序中高精度的重要性以及為什么如此重要,同時,用于二進(jìn)制浮點(diǎn)算法的 ieee 754 標(biāo)準(zhǔn) 與其說有所幫助,不如說實(shí)際的約束更多些。
  c#允許浮點(diǎn)算術(shù)使用更高的硬件平臺支持的精度,譬如 intel 的 80 位雙精度格式。這種擴(kuò)展類型具有比雙精度類型更高精度,它被底層硬件執(zhí)行所有浮點(diǎn)算術(shù)時隱式使用,這樣提供了準(zhǔn)確 的或近似準(zhǔn)確的結(jié)果。下面這段話直接摘錄于 c# 規(guī)格說明書第 4.1章節(jié),以此作為 c# 支持高精度浮點(diǎn)運(yùn)算的例子:

  ……在形如 x*y/z 的表達(dá)式中,乘法產(chǎn)生超出雙精度取值范圍的結(jié)果,但是接著除法又使臨時結(jié)果回到雙精度取值范圍里,表達(dá)式的事實(shí)上的結(jié)果是在更高精度范圍內(nèi)被求值,所產(chǎn)生的是有窮結(jié)果而不是無窮大。

  c#也支持十進(jìn)制類型——128位數(shù)據(jù)類型,適合于金融和貨幣計算。

值類型,或輕量級對象
  c#中對象類型主要有兩種——引用類型(重量級對象)和值類型(輕量級對象)。
  引用類型總是在堆中分配(除非使用 stackalloc 關(guān)鍵字),并給予一個額外的間接層;也即,它們需要通過對其存儲位置的引用來訪問。既然這些類型不能直接訪問, 某個引用類型的變量總是保存實(shí)際對象的引用(或 null ) 而不是對象本身。假設(shè)引用類型在堆中分配,運(yùn)行時必須確保每個分配請求被正確執(zhí)行。考慮下面代碼,它執(zhí)行一次成功的分配:

matrix m = new matrix(100, 100); 
  其幕后執(zhí)行是:clr內(nèi)存管理器收到分配請求,它會計算存儲該對象包括頭部和類變量所需的內(nèi)存數(shù)量。然后內(nèi)存管理器檢查堆中可用空閑空間,以確認(rèn)是否有足夠空間供這次分配。如果有,對象 所需空間會被成功分配并且對其存儲地址的引用也會被返回。如果沒有足夠空間存儲對象,垃圾收集器將被啟動去釋放一些空間并進(jìn)行堆緊縮操作。
  如果執(zhí)行成功,為了保持后續(xù)的垃圾收集操作,內(nèi)存管理器將對象寫入內(nèi)存前還必須采取另一個重要步驟。這一步驟涉及產(chǎn)生一塊稱作寫屏障(write barrier)的代碼(垃圾收集器的實(shí)現(xiàn)細(xì)節(jié)超出本文范圍)。相反地,每當(dāng)有對象被寫入內(nèi)存或 者當(dāng)對象在內(nèi)存中產(chǎn)生對另一個對象的引用(例如原先存在對象指向新創(chuàng)建對象),運(yùn)行時便生成寫屏障。垃圾收集器功能實(shí)現(xiàn)的許多復(fù)雜性之一是要記住這些對象的存在 可寫性,因而在收集過程中它們不會被誤收集,雖然它們是被毫不相關(guān)的另一個對象所指向的對象。正如你可能會猜測,這些寫屏障招致小的運(yùn)行時開銷,所以對于科學(xué) 計算應(yīng)用來說,在運(yùn)行過程中創(chuàng)建數(shù)百萬對象不是理想場景。
  值類型被直接存儲在棧中(雖然此規(guī)則有例外,我馬上會講到)。值類型不需要間接層,所以值類型變量總保存自身實(shí)際值而不能將引用保存為其它類型(因而,它們也就不能為 null)。使用值類型主要優(yōu)點(diǎn)是它們的分配只產(chǎn)生很小的運(yùn)行時開銷。分配它們時,只是簡單增加棧指針并且不需要被內(nèi)存管理器管理。這些對象決不調(diào)用垃圾收集功能。此外,值類型不生成寫屏障。
  c#中,簡單數(shù)據(jù)類型(int,float,byte)、枚舉類型和結(jié)構(gòu)(struct)類型都是值類型。雖然前面我講過值類型直接存儲在棧中,但我沒有使用“總是”,正如我論述引用類型時一樣。包含在引用類型內(nèi)的值類型不會 被存儲在棧中,而是堆中,它被包含于引用類型對象中。例如,看看下面代碼片段:
class point{    private double x, y;    public point (double x, double y)    {        this.x = x;        this.y = y;    }}      
  這個類的一個實(shí)例占用24字節(jié),其中8字節(jié)用于對象頭,剩余16字節(jié)用于兩個雙精度變量x和y。與此同時,引用類型是包含在值類型對象中的(例如,結(jié)構(gòu)中包含數(shù)組) ,它不會導(dǎo)致整個對象在堆中分配。只有數(shù)組在堆中分配,對該數(shù)組的引用被置于在棧中存放的結(jié)構(gòu)中。
  值類型派生于 system.valuetype,它本身又派生于 system.object。因?yàn)檫@個,值類型具備像類一樣的特征。值類型有構(gòu)造函數(shù)(除了無參數(shù)構(gòu)造函數(shù))、索引指示器(indexer)、方法和重載運(yùn)算符,它們也能實(shí)現(xiàn)接口。然而,它們不能被繼承,也不能從其 它類型繼承。這些對象容易成為 jit 優(yōu)化因素,因?yàn)樗鼈兩捎行А⒏咝阅艽a。
  這里有一個警告:極其容易意外地將值類型設(shè)陷為一個對象,從而導(dǎo)致在堆中分配它——眾所周知,這就是裝箱(boxing)技術(shù)。確信你的代碼 不會進(jìn)行在不必要的值裝箱操作,否則將失去最初得到的性能。另一個警告是:值類型數(shù)組(例如雙精度或整型數(shù)組)是在堆中存放,而不是棧中。只有保存數(shù)組引用的值是存放在棧中。這是因?yàn)樗袛?shù)組類型都隱含派生于system.array,它們都是引用類型。
  對于科學(xué)計算應(yīng)用來說,值類型不管怎樣都比引用類型快而有效、是最優(yōu)選擇。下一部分,我們將考察科學(xué)計算應(yīng)用中用戶自定義的輕量級數(shù)據(jù)類型眾多應(yīng)用之一。

復(fù)數(shù)算術(shù)
  雖然c#象c一樣不支持內(nèi)置的復(fù)數(shù)數(shù)據(jù)類型,但是這不能阻止你創(chuàng)建自己的復(fù)數(shù)類型。你可使用一個結(jié)構(gòu)來實(shí)現(xiàn),它具備值語義。
  復(fù)數(shù)數(shù)據(jù)為一實(shí)數(shù)有序?qū)ΓǚQx和y)和用來相乘的虛數(shù)i,并遵循一些算術(shù)規(guī)則。下面這個例子中,z是一個復(fù)數(shù):
z = x + yi, where i2 = -1            
  復(fù)數(shù)在科學(xué)計算應(yīng)用中廣泛使用,用于研究物理現(xiàn)象,譬如涉及障礙的電流、波長和液體流動,分析汽車減震器的桁條壓力、移動,發(fā)電機(jī)和電動機(jī)的設(shè)計,建模中大矩陣的的處理。它們形成這個宇宙中幾乎任何事的基本等式,所以將復(fù)數(shù)視作編程語言中 單獨(dú)一種數(shù)據(jù)類型對于許多科學(xué)計算應(yīng)用來說是至關(guān)重要的。
  figure 3 展示了一個基本的用戶定義復(fù)數(shù)類型的實(shí)現(xiàn)和它的使用。它本質(zhì)上體現(xiàn)了c#有助于 科學(xué)計算應(yīng)用的兩個不同方面。它們是:結(jié)構(gòu)的使用,用于創(chuàng)建自定義數(shù)據(jù)類型的頗有價值的資源,這種類型能被當(dāng)作另一個簡單類型(c#中簡單數(shù)據(jù)類型都是結(jié)構(gòu)),和用戶定義類型的一元或二元運(yùn)算符重載,這顯著提高了 科學(xué)計算應(yīng)用程序代碼的可讀性和易管理性。這一點(diǎn)值得進(jìn)一步關(guān)注。最引人注目的是,運(yùn)算符重載提高代碼可讀性,這在科學(xué)計算程序中極為重要。盡管使用方法調(diào)用可達(dá)到同樣的效果,但這不總是一個好主意,如果代碼包含許多復(fù)雜的數(shù)字表達(dá)式。以下面簡單數(shù)字表達(dá)式為例,它包含三個復(fù)數(shù):
c3 = c1 * c2 + 5 * (c1-13)      
如果此表達(dá)式以方法調(diào)用形式編寫,你會得到類似下行代碼:
complex c3 = c1.multiply(c2).add((c1.minus(13)).multiply      
  忘記其中任何一處的括號,你將陷入在屏幕上查找編譯錯誤。想象一下編寫一個比上面表達(dá)式更復(fù)雜的數(shù)字表達(dá)式將會怎樣。或者想象一段時間后重新回到你的代碼,并打算在一個嵌套 了無數(shù)個add和multiply方法名的表達(dá)式中將“add”方法名改為“multiply”。首先你必須理解代碼是做什么的,然后,如果幸運(yùn)的話,經(jīng)過多次不成功的修改嘗試后你終于得到正確結(jié)果。運(yùn)算符重載允許你以自然、合乎邏輯 的方式來表示數(shù)字表達(dá)式。
  當(dāng)處理存儲在棧中的實(shí)際值時,你可能認(rèn)為運(yùn)算符重載對執(zhí)行的整理速度沒有影響。理論上,它根本不應(yīng)該影響性能,并且好的 c++ 編譯器證明此理論是正確的。然而,由于某些限制,當(dāng)前 jit 編譯器不能執(zhí)行相關(guān)的優(yōu)化,所以導(dǎo)致代碼 運(yùn)行比期望的要稍慢。這個問題將在 jit 編譯器未來版本中得到解決。
  雖然對于注重性能程序,當(dāng)前運(yùn)算符重載可能不是最優(yōu)的解決方法,但是它確實(shí)提高代碼可讀性和重用性。實(shí)際上,科學(xué)計算社區(qū)里的開發(fā)者需要編寫可理解的并且在符號與語法方面盡可能近似真實(shí)數(shù)字表達(dá)式的代碼。如果你計劃對自定義類型僅執(zhí)行少數(shù)算術(shù)運(yùn)算,而且你對使用模糊的符號不反感,那么你可以完全地忽視這些特征并獲取額外的一點(diǎn)性能。c#讓你 有選擇的余地,這使它在科學(xué)計算中的應(yīng)用更具吸引力。

多維數(shù)組
  c#支持三類數(shù)組:一維數(shù)組,不規(guī)則數(shù)組(jagged array),矩形數(shù)組(rectangular array)。
  如同 c 中一樣,一維數(shù)組是以0為索引起點(diǎn)(數(shù)組的第一個元素索引為0),它的元素在內(nèi)存中依次順序存放。因此,這類數(shù)組被隱式處理,使用的是一套專門為此而設(shè)置的 msil 指令(newarr,ldelem,stelem等)進(jìn)行的。由于編譯器能以許多方式優(yōu)化它們,所以一維數(shù)組相當(dāng)有吸引力力。
  當(dāng)一維數(shù)形成幾乎任何數(shù)字應(yīng)用程序的一部分時,很少聽說不用更有效的多維數(shù)組來進(jìn)行數(shù)字計算的。c#中多維數(shù)組有兩種:不規(guī)則數(shù)組和矩形數(shù)組。
  不規(guī)則數(shù)組是元素為一維數(shù)組的一維數(shù)組。最好把不規(guī)則數(shù)組想象成一豎列,列中每個槽指向內(nèi)存中另一個存放一維數(shù)組的地址。假定一維數(shù)組在c#中msil層被優(yōu)化, 那么感覺一維數(shù)組是最有效率的。然而,這幾乎完全取決于如何訪問數(shù)組。如果訪問的代碼地址不好,在內(nèi)存里為跟蹤指針而到處跳轉(zhuǎn)的開銷將占據(jù)執(zhí)行時間。不規(guī)則數(shù)組的另一個好處(或者是問題,取決你的標(biāo)準(zhǔn))是它的行可以是變長,因而取名為不規(guī)則。這導(dǎo)致每次訪問新行時,要執(zhí)行多個邊界檢查。然而,當(dāng)前運(yùn)行時中,不規(guī)則數(shù)組 的檢索具有比矩形數(shù)組更好的優(yōu)化和內(nèi)聯(lián)性能。
  意識到不規(guī)則數(shù)組的一些缺點(diǎn),c#設(shè)計者決定在語言規(guī)格中包含類似 c 的多維數(shù)組,針對注重性能的應(yīng)用程序和僅僅是更喜歡矩形數(shù)組而非不規(guī)則數(shù)組的用戶。與不規(guī)則數(shù)組 不同,矩形數(shù)組被存儲在連續(xù)的內(nèi)存位置,而不是分散在堆中。不幸的是,矩形數(shù)組沒有自己的一套 msil 指令和替代的用于訪問數(shù)組元素的輔助性 set/get 方法。這些 輔助性方法不產(chǎn)生運(yùn)行時開銷(至少對于二維或三位數(shù)組是這樣),因?yàn)閖it編譯器視它們?yōu)閮?nèi)置的。結(jié)果,代碼通過內(nèi)在的許多優(yōu)化技術(shù)被生成,譬如,消除邊界檢查和潛在使用單偏移。
  當(dāng)前jit編譯器中有一個不太重要的局限,關(guān)于矩形數(shù)組邊界檢查的體面消除,因此,順次(逐行)訪問一個相對小的二維不規(guī)則數(shù)組可能在某些機(jī)器上比訪問二維矩形數(shù)組快。對 于大數(shù)組 則不然,因?yàn)椴灰?guī)則數(shù)組會在獲取內(nèi)存中不同位置的隨意數(shù)組元素時消耗很多時間。當(dāng)不是線性訪問每行時,你可估計到不規(guī)則數(shù)組的性能與要訪問元素的數(shù)目成反比。
  由于存儲地址的不合適,在對角或隨機(jī)訪問元素情況下,不規(guī)則數(shù)組表現(xiàn)相當(dāng)槽糕。另一方面,矩形數(shù)組似乎是同樣場景下不規(guī)則數(shù)組性能的四到五倍。figure 4 中 的代碼使用性能計數(shù)器類記錄了四個不同循環(huán),這些循環(huán)測試了順序和對角訪問大的不規(guī)則數(shù)組和大的矩形數(shù)組的情形。
  figure 5顯示了運(yùn)行 figure 4 中代碼的結(jié)果。如同你所看到,順序訪問兩種大數(shù)組消耗差不多的時間,反之對角訪問不規(guī)則數(shù)組幾乎比矩形數(shù)組慢8倍。


figure 5 順序和對象訪問

  盡管在結(jié)構(gòu)和性能方面矩形數(shù)組通常優(yōu)于不規(guī)則數(shù)組,但可能有一些情況不規(guī)則數(shù)組可提供最優(yōu)的解決方法。如果你的應(yīng)用程序不需要排序、重排、分割、稀疏或大的數(shù)組,那么你會發(fā)現(xiàn)不規(guī)則數(shù)組表現(xiàn)得相當(dāng)好。然而,注意雖然此建議適合于大多數(shù)應(yīng)用程序,但它不適用于庫代碼。你常常沒有意識到其他人使用你的庫代碼這種情況,這將產(chǎn)生性能差的系統(tǒng)。
  例如,你可能編寫一個使用不規(guī)則數(shù)組的通用矩陣庫,你將發(fā)現(xiàn),它在一系列程序中表現(xiàn)糟糕,包括圖像、圖像分析、有限元分析、機(jī)械學(xué)、數(shù)據(jù)挖掘、數(shù)據(jù)擬和(data fitting)和神經(jīng)網(wǎng)絡(luò)。這是因?yàn)轭l繁遭遇稀疏矩陣,此矩陣中大多數(shù)元素是零或不需要的,因而需要隨機(jī)訪問它們。
  對于這種情形,轉(zhuǎn)換到矩形數(shù)組,你的全部性能都得到顯著改進(jìn)。作為一個例子,figure 6 和figure 7 測試了程序的兩種不同實(shí)現(xiàn)的性能,此程序中將幾個大矩陣相乘。每個程序使用 figure 4中顯示的性能計數(shù)器類。figure 6 使用二維不規(guī)則數(shù)組實(shí)現(xiàn)矩陣,figure 7 使用矩形數(shù)組。每個程序輸出它的運(yùn)行時間和得到 mflops 值。這個簡單的測試表明使用矩形數(shù)組的代碼比使用不規(guī)則數(shù)組的等價代碼快大約八至九倍。


figure 8 不規(guī)則數(shù)組vs規(guī)則數(shù)組

  在我機(jī)器上運(yùn)行兩部分代碼,得到的結(jié)果如 figure 8 所示。把多維數(shù)組轉(zhuǎn)化為一維數(shù)組,你甚至?xí)玫礁玫慕Y(jié)果。如果你不介意語法,這有些瑣碎;直接使用一個索引作為偏移。例如,下列代碼聲明一個一維數(shù)組并作為二維數(shù)組使用:
double[] myarray = new double[row_dim * column_dim];            
要索引這個數(shù)組的元素,使用下列偏移:
myarray[row * column_dim + column];      
這勿庸置疑比等價的不規(guī)則或矩形數(shù)組快。
  如果矩形數(shù)組不作為語言的一部分,可能很難將 c# 作為一個適合科學(xué)計算的語言。目前jit編譯器的局限將有希望被克服,但是正如你看我剛顯示的結(jié)果,即使有這些限制,矩形數(shù)組比不規(guī)則數(shù)組表現(xiàn)更好。

不安全方法

  c#充滿驚奇。作為具有c/c++背景的開發(fā)者,我不愿意放棄指針。c#支持c風(fēng)格指針很令人驚訝。你可能想知道這是如何做到的,既然垃圾收集器在一次收集中移動所有對象且可能移動了你指向的對象。幸運(yùn)的是,通過釘住內(nèi)存中對象 ,對它進(jìn)行標(biāo)志,以便在收集中不移動它們,運(yùn)行時允許你在垃圾收集器控制之外工作。對操縱數(shù)組而言,這是一個好消息。首先,多維矩形數(shù)組很容易線性訪問,好像它們是一維數(shù)組一樣。其次,當(dāng)訪問數(shù)組元素時,運(yùn)行時執(zhí)行數(shù)組邊界檢查以確保你沒有越界訪問數(shù)組(然而,有些例子,這種情況下jit能檢查訪問邊界并證明它不需要每次做邊界檢查)。在你對你的代碼很自信的方面,卻引入未預(yù)料的開銷。
  但是有一個勾絆。使用指針的代碼必須標(biāo)記為不安全,且只能在完全信任環(huán)境中運(yùn)行,比如,這意味著你不能直接在internet中運(yùn)行這些代碼。然而,這不應(yīng)該影響打算在完全信任環(huán)境中運(yùn)行代碼的科學(xué)程序師,除非想要編寫網(wǎng)絡(luò)程序或被普通公眾使用的程序。


figure 9 不安全代碼性能

  要顯示不安全代碼之于安全代碼執(zhí)行的效率有多好,請看 figure 9,它顯示兩個矩陣乘法方法的基準(zhǔn)測試;一個使用指針訪問單個數(shù)組元素,另一個沒有。結(jié)果表明,不安全版本運(yùn)行速度大約是安全版本的兩倍。如果不安全代碼使用指針運(yùn)算訪問數(shù)組元素,同時做乘法和加法時使用更多增量,這樣的改進(jìn)甚至更引人注目。

語言互用性

  大多數(shù)開發(fā)者花時間與精力生成庫或解決方案,它們都基于.net框架之前的技術(shù)和語言。雖然將你的代碼移植到c有很多優(yōu)點(diǎn),但當(dāng)你已經(jīng) 具備經(jīng)過測試的代碼作為基礎(chǔ)來生成應(yīng)用。這并不總是做此選擇。幸運(yùn)地,由于有了通用語言規(guī)范(cls)和通用類型系統(tǒng)(cts),.net內(nèi)置了對語言互用性 的支持。倘若代碼是cls兼容的,那么能有效地讓其它任何語言編寫的遺留代碼與c#代碼交互。因此,舉例來說,你喜愛的用fortran編寫的數(shù)字庫或代碼,現(xiàn)在能無縫集成到c#程序中。你甚至可用非受管c編寫代碼中 注重性能的部分,然后在受管c#程序中使用它。.net框架通常使受管和非受管代碼之間的轉(zhuǎn)變看起來是無縫的。
  這將給科學(xué)計算開發(fā)帶來革命,因?yàn)殚_發(fā)者現(xiàn)在能同時用許多不同語言編寫代碼,然后輕松集成或共享代碼。

基準(zhǔn)測試(benchmarks)

  前面顯示的基準(zhǔn)測試結(jié)果是通過在 2.4ghz 奔騰四 256兆內(nèi)存機(jī)器上運(yùn)行 scimark 2.0 基準(zhǔn)套件獲取的。為了方便比較起見,取自 scimark 2.0 c語言版本的結(jié)果也顯示在 figure 10 中,以 便讓你了解 c# 相對 c 的比較情況。


figure 10 c#與c性能比較

  應(yīng)該指出,當(dāng)前c#版本的 scimark 在其大多數(shù)內(nèi)核中使用二維不規(guī)則數(shù)組,譬如 sor、稀疏矩陣乘法和 lu。在這三個內(nèi)核中,這是為什么 c# 的性能要比c差很多,而在 fft 上與 c 近似一樣的顯著原因。或許切換到矩形數(shù)組會顯示一個更好的圖形。


figure 11 scimark測試結(jié)果

  這些結(jié)果不是作為兩種語言性能比較的最終暗示。既然 scimark 是直接從 java 轉(zhuǎn)變?yōu)?c#,沒有使用某些非常優(yōu)化的c#特性的實(shí)現(xiàn)通過包含可供選擇的特性,如結(jié)構(gòu)、矩陣數(shù)組、甚至不安全代碼塊, 將會得到更進(jìn)一步的改善,從而與 c 有更好的對比。figure 11 顯示了所有結(jié)果的綜合得分。

結(jié)束語

  當(dāng)開發(fā)者已經(jīng)樂于用 c# 開發(fā)基于 internet 組件和分布式程序時,不需要什么調(diào)查工作來證實(shí) c# 可以作為一門科學(xué)計算語言。在本文中,我展現(xiàn)c#里許多強(qiáng)大 的特性,它們使 c# 成為開發(fā)科學(xué)計算代碼的理想平臺。雖然目前 c# 的性能被充分認(rèn)識完全足以勝任科學(xué)計算語言,我們?nèi)韵M?jit 編譯器的下一個版本的性能會更好。
發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 遂平县| 班戈县| 铁岭市| 广南县| 揭西县| 武威市| 永登县| 竹山县| 五原县| 普兰县| 大田县| 墨脱县| 塘沽区| 临泉县| 大竹县| 搜索| 肇州县| 沐川县| 马公市| 蒙山县| 叙永县| 南漳县| 丰顺县| 开江县| 乐清市| 鹤岗市| 北安市| 汉沽区| 灵武市| 剑川县| 丰城市| 麦盖提县| 青川县| 普兰县| 托里县| 同心县| 大竹县| 句容市| 盘锦市| 宁陕县| 黄冈市|