何時彙編比C更快?

已知的瞭解彙編器的緣由之一是,有時能夠用它來編寫比用高級語言(尤爲是C)編寫更高性能的代碼。 可是,我也聽到過不少次聲明,儘管這並不是徹底錯誤,但實際上可將彙編程序用於生成更多性能代碼的狀況極爲罕見,而且須要彙編方面的專業知識和經驗。 算法

這個問題甚至都沒有涉及到彙編程序指令將是特定於機器且不可移植的,或者彙編程序的任何其餘方面。 固然,除了彙編語言以外,還有不少了解彙編語言的充分理由,但這是一個特定的問題,須要徵集示例和數據,而不是對彙編語言和高級語言的擴展論述。 編程

誰能提供一些特定的例子說明使用現代編譯器進行彙編比編寫良好的C代碼要快得多,而且您能夠提供帶有分析依據的主張嗎? 我對這些案例的存在頗有信心,可是我真的想確切地知道這些案例有多深奧,由於這彷佛有些爭議。 緩存


#1樓

根據個人經驗,有幾個例子: 架構

  • 訪問沒法從C訪問的指令。例如,許多體系結構(如x86-64,IA-64,DEC Alpha和64位MIPS或PowerPC)支持64位乘64位乘法,產生128位結果。 GCC最近添加了擴展名,以提供對此類說明的訪問,可是在須要該程序集以前。 當實施RSA之類的東西時,訪問此指令可能對64位CPU產生巨大的影響-有時性能會提升4倍。 分佈式

  • 訪問特定於CPU的標誌。 咬住我不少的是進位標誌; 在進行多精度加法運算時,若是您沒法訪問CPU進位,則必須比較結果以查看其是否溢出,這每條肢體須要3-5條指令; 更糟糕的是,就數據訪問而言,這是串行的,這會破壞現代超標量處理器的性能。 當連續處理成千上萬個這樣的整數時,可以使用addc是一個巨大的勝利(進位位上的爭用也存在超標量問題,但現代CPU處理起來很不錯)。 函數

  • SIMD。 即便是自動向量化的編譯器也只能處理相對簡單的狀況,所以,若是要得到良好的SIMD性能,一般經常須要直接編寫代碼。 固然,您可使用內部函數而不是彙編程序,可是一旦您進入內部函數級別,則基本上不管如何都在編寫彙編程序,只是將編譯器用做寄存器分配器和(名義上)指令調度程序。 (我傾向於將內在函數用於SIMD只是由於編譯器能夠爲我生成函數序言,而不是爲我生成函數序言,所以我能夠在Linux,OS X和Windows上使用相同的代碼而沒必要處理函數調用約定之類的ABI問題,但其餘比SSE內在函數確實不是很好-Altivec的內在函數彷佛更好,儘管我對它們沒有太多經驗。 做爲一個事例,(當今)矢量化編譯器沒法弄清楚,請閱讀有關位片化AESSIMD糾錯的信息 -能夠想象一個編譯器能夠分析算法並生成這樣的代碼,但在我看來,這就像一個聰明的編譯器距現有(至少)至少30年。 工具

另外一方面,多核計算機和分佈式系統已將許多最大的性能優點轉移到了另外一個方向上-將內部循環以彙編形式編寫可額外提升20%的速度,或者經過在多個內核上運行它們來實現300%的加速,或在10000%的速度下達到10000%在一組機器上運行它們。 固然,使用ML或Scala這樣的高級語言比使用C或asm進行高級優化(諸如期貨,備忘錄等之類的東西)一般要容易得多,而且一般能夠帶來更大的性能優點。 所以,一如既往,須要進行權衡。 性能


#2樓

簡單的答案... 知道彙編的 (又有他的參考,而且利用每個小的處理器高速緩存和管道功能等)能夠保證比任何編譯器產生更快的代碼。 優化

可是,這些天的差別在典型應用中可有可無。 編碼


#3樓

從彙編編碼器的角度來看,C常常比您想像的要多作沒必要要的事情,由於C標準如此說。

例如,整數提高。 若是要在C中移動char變量,一般會但願代碼實際上只是這樣作,即一次移位。

可是,這些標準強制編譯器在移位以前對int進行符號擴展,而後將結果截斷爲char,這可能會使代碼複雜化,具體取決於目標處理器的體系結構。


#4樓

Longpoke,只有一個限制:時間。 若是您沒有足夠的資源來優化代碼的每一個更改,並花時間分配寄存器,優化少許溢出,而沒有的話,則編譯器將每次都獲勝。 您對代碼進行修改,從新編譯和測量。 若有必要,請重複。

另外,您能夠在高級方面作不少事情。 一樣,檢查生成的程序集可能會使IMPRESSION感受到代碼已被廢棄,但實際上它的運行速度要比您認爲的更快。 例:

int y = data [i]; //在這裏作一些事情.. call_function(y,...);

編譯器將讀取數據,將其壓入堆棧(溢出),而後從堆棧中讀取並做爲參數傳遞。 聽起來很糟糕? 它實際上多是很是有效的延遲補償,而且能夠加快運行時間。

//優化的版本call_function(data [i],...); //畢竟沒有那麼優化。

優化版本的想法是,咱們減小了套準壓力並避免了溢出。 但實際上,「糟糕」版本的速度更快!

查看彙編代碼,僅查看說明並得出結論:更多的說明,較慢的說明將是錯誤的判斷。

這裏要注意的事情是:許多組裝專家認爲他們瞭解不少,但瞭解不多。 規則也從體系結構更改成下一個。 例如,沒有銀彈x86代碼,它老是最快的。 這些天最好遵循經驗法則:

  • 記憶很慢
  • 快取
  • 嘗試更好地使用緩存
  • 你多久想念一次? 您有延遲補償策略嗎?
  • 您能夠爲一個緩存未命中執行10-100條ALU / FPU / SSE指令
  • 應用程序體系結構很重要。
  • ..可是當問題不在體系結構中時它沒有幫助

一樣,過度相信編譯器會神奇地將思想欠佳的C / C ++代碼轉換爲「理論上最佳」的代碼,這是一廂情願的想法。 若是您在此低級關注「性能」,則必須瞭解所使用的編譯器和工具鏈。

對於初學者來講,C / C ++中的編譯器一般不太擅長從新排序子表達式,由於這些函數具備反作用。 函數式語言不會受到這種警告的困擾,但並不能很好地適應當前的生態系統。 有一些編譯器選項容許寬鬆的精度規則,這些規則容許由編譯器/連接器/代碼生成器更改操做順序。

這個話題有點死衚衕。 對於大多數狀況而言,這是可有可無的,其他的,他們不管如何都知道本身在作什麼。

歸結爲:「瞭解本身在作什麼」,這與知道本身在作什麼有些不一樣。


#5樓

我已經閱讀了全部答案(超過30個),卻找不到簡單的緣由:若是您已經閱讀並練習了《 英特爾®64和IA-32架構優化參考手冊》 , 那麼彙編程序的運行速度會比C快。 更慢的是寫這樣慢的彙編的人沒有看過「優化手冊」 。

在Intel 80286的美好時光中,每條指令均以固定的CPU週期數執行,可是自1995年發佈的Pentium Pro起,Intel處理器就利用了複雜流水線:亂序執行和寄存器重命名,成爲了超標量。 在此以前,在1993年生產的Pentium上有U和V管線:雙管線能夠在不依賴時在一個時鐘週期執行兩條簡單指令的狀況; 但這與奔騰Pro中出現的亂序執行和寄存器重命名沒有什麼可比的,現在幾乎保持不變。

用幾句話來解釋,最快的代碼是指指令不依賴於先前的結果,例如,您應始終清除整個寄存器(經過movzx)或使用add rax, 1inc rax來消除對標誌先前狀態的依賴等。 。

若是時間容許,您能夠閱讀更多關於亂序執行和註冊重命名的信息,Internet上有不少可用信息。

還有其餘重要問題,例如分支預測,加載和存儲單元的數量,執行微操做的門的數量等,可是要考慮的最重要的事情是無序執行。

大多數人根本不瞭解亂序執行,所以他們像80286同樣編寫彙編程序,指望他們的指令將花費固定的時間來執行,而與上下文無關; 而C編譯器知道亂序執行並正確生成代碼。 這就是爲何這種不瞭解的人的代碼速度較慢的緣由,可是若是您意識到這一點,您的代碼就會更快。

相關文章
相關標籤/搜索