1. 程式人生 > >《深入理解計算機系統》(三)

《深入理解計算機系統》(三)

C只支援大小在編譯時就能知道的多維陣列(對於第一維可能有些例外)。在許多應用程式中,我們需要程式碼能夠動態分配的任意大小的陣列進行操作。,為此,我們必須顯示地寫出從多維陣列到一維陣列的對映。

異類的資料結構:C提供了兩種不同型別的物件結合到一起來建立資料型別的機制;結構,用關鍵字struct來宣告,將多個物件集合到一個單位中;聯合,用關鍵字union來宣告,允許用幾種不同的型別來引用一個物件。 

結構:C的struct宣告建立一個數據型別,將可能不同型別的物件聚合到一個物件中。結構的各個組成部分是用名字來引用的。結構的實現類似於陣列的實現,因為結構的所有組成部分都存放在儲存器中連續的區域,而指向結構的指標就是結構第一個位元組的地址。編譯器儲存關於每個結構型別的資訊,指示每個域的位元組偏移,它以這些偏移作為儲存器引用指令中的位移,從而產生對元素構成的應用

struct資料型別的建構函式是C提供的與C++和JAVA物件最接近的東西,它允許程式設計師儲存關於一個數據結構中某些實體的資訊,並用名字來引用這些資訊。

聯合提供了一種方式,能夠規避C的型別系統,允許以多種型別來引用一個物件。聯合宣告的語法與結構一樣

,不過他們不是用不同的域來引用不同的儲存器塊,而是引用的同一儲存器塊。

在一些情況中,聯合十分有用,但是,它也引起一些討厭的錯誤。因為它繞過了C型別系統提供的安全措施。一種應用情況是,我們事先知道對一個數據結構中的兩個不同域的使用時互斥的,那麼將這兩個域作為聯合的一部分,而不是結構的一部分,會減少分配空間的總量。

對齊:許多計算機系統對基本資料型別的可允許地址做出了一些限制,要求某種型別的物件的地址必須是某個值K(通常是2、4、8)的倍數。這種對齊限制簡化了處理器和儲存器系統之間介面的硬體設計。例如:假設一個處理器總是從儲存器中取8個位元組出來,則地址必須為8的倍數。如果我們能保證所有的double都將他們的地址對齊成8的倍數,那麼可以用一個儲存器操作來讀或者寫值了。否則我們可能需要執行兩次儲存器訪問,因為物件可能分放在兩個8位元組儲存器塊中。

無論資料是否對齊,LA32硬體都能正常工作,不過,Intel還是建議要對齊資料以提高儲存器系統的效能。Linux沿用的對齊策略是2位元組資料型別(例如short)的地址必須是2的倍數,而較大的資料型別(例如int、int*、float和double)的地址必須是4的倍數。注意,這個要求就意味著一個short型別物件的地址的最低位必須等於0.類似地,任何int型別的物件或指標的地址的最低兩位必須都是0.

     Microsoft    Windouws  對對齊的要求更嚴格——任何k位元組(基本)物件的地址都必須是K的倍數。特別地,它要求一個double的地址應該是8的倍數,這種要求提高了儲存器效能,代價是浪費了一些空間。

    確保每種資料型別性都是按照指定方式來組織和分配,即每種型別的物件滿足它的對齊限制,就可保證實施對齊。編譯器在彙編程式碼中放入命令,指明全域性資料所需的對齊。

指標式C語言的一個重要特色,他們 提供一種同一方式,能夠遠端訪問資料結構。

在C中指標可以指向任何資料型別。

每個指標都有一個型別,這個型別表明指標指向的物件 是哪一類的。

每個物件都有一個值。這個值是某個指定型別的物件的地址。

指標是用&運算子建立的。這個運 算符可以應用到任何lvalue類的C表示式上,也就是可以出現在賦值語句左邊的表示式,這樣的例子包括變數以及結構、聯合和陣列的元素。

操作符用於指標的間接引用。其結果是一個值,它的型別與該指標的型別相關。

陣列與指標是緊密相連的。可以引用一個數組的名字(但是不能修改),就好像它是一個指標變數一樣。陣列引用與指標運算和間接應用有一樣的效果。

指標也可以指向函式,這提供了一個很強大的儲存和傳遞程式碼引用的功能,這些程式碼可以被程式的某個其他部分呼叫。

         向函式傳遞資料:其他語言(例如Pascal)提供兩種方式來向過程傳遞資料——傳值(by   value)和引用(by    reference),傳值是指呼叫者提供實際的引數值,而引用是指呼叫者 提供一個指向該值的指標。在C中所有的函式都是傳值的,但是我們可以通過顯示地產生一個值的指標,並把該指標傳遞給過程,從而實現啦引用函式的效果。

使用GDB偵錯程式:GNU的偵錯程式GDB提供了許多有用的特性來支援對機器級程式的執行時評估和分析。有了GDB,通過觀察正在執行的程式,同時又對程式的執行有相當的控制,這就使得研究程式的行為變為可能。

   GDB的命令語法有點含混晦澀,但是線上幫助資訊(用GDB的help命令呼叫)能克服這些毛病。

  儲存器的越界引用和緩衝區溢位:我們已經看到,C對於陣列引用不進行任何邊界檢查,而且區域性變數和狀態資訊(例如暫存器值和返回指標)都存放在棧中,這兩種情況結合到一起就能導致嚴重的程式錯誤,一個對越界的陣列元素的寫操作破壞了儲存在棧中的狀態資訊。然後,當程式使用這個被破壞的狀態,試圖重新載入暫存器或執行ret指令時,就會出現嚴重的錯誤。

一種特別常見的狀態破壞稱為緩衝區溢位(buffer   overflow)。通常,在棧中分配某個位元組陣列來儲存一個字串,但是字串的瘡毒超出了為陣列分配的空間。

    gets從標準輸入讀入一行,在遇到一個“\n”字元或某個錯誤情況時會停止。它將這個字串拷貝到引數s指明的位置,並在字串結尾加上null字元。

    gets的問題是它沒有辦法確定是否為儲存整個字串分配了足夠的空間。

緩衝區溢位的一個更加致命的使用就是讓程式執行它本來不願意執行的函式,這是一種最常見的通過計算機網路攻擊系統安全的方法。通常,輸入給程式一個字串,這個字串包含一些可執行程式碼的位元組編碼,稱為exploit   code  ,另外,還有一些位元組會用一個指向緩衝區中那些可執行程式碼的指標覆蓋掉返回指標。所以,執行ret指令的效果就是跳轉到exploit   code.

    一種攻擊形式中,exploit   code會使用系統呼叫一個shell程式,提供給攻擊者一組作業系統的函式。在另一種形式中,exploit   code  執行一些未授權的任務,修復對棧的破壞,然後第二次執行ret指令,(看上去好像)正常返回給呼叫者。

    蠕蟲和病毒都是試圖在計算機中傳播他們自己的程式碼。蠕蟲(worm)是這樣一種程式,它可以自己執行,並且能夠將一個完全有效的自己傳播到其他機器,與此相應地,病毒(virus)是這樣一段程式碼,他能將自己新增到包括作業系統在內的其他程式中,但它不能獨立執行。

浮點程式碼:處理浮點值的指令時IA32體系結構最不優美的特性之一。在最早的intel機器中,浮點是由一個獨立的協處理器來完成的,這個部件有他自己的暫存器和處理能力,能夠執行一部分指令。

浮點暫存器:浮點單元包括8個浮點暫存器,但是和普通暫存器不一樣,這些暫存器是被當成一個淺棧來對待的。當壓入棧中的值超過8個時,棧底的那些值就會消失。大多數算術指令不會直接引用儲存器,而是從棧中彈出他們的源運算元,計算結果,再將結果壓入棧中。

   將浮點暫存器組織成一個有界的棧,使得編譯器很難用這些暫存器來存放一個呼叫其他過程的區域性變數。對於區域性變數的存放,我們已經看到有些通用暫存器可以被指定為由被呼叫者儲存,因此可以用來儲存跨過程呼叫的區域性變數,這種指定對IA32來說是不可能的。因為他的標識隨著值壓入棧中和從棧中彈出是變化的。

   另一方面,他會將浮點暫存器作為真正的棧來對待,每次過程呼叫時,都將本地址壓入其中。不幸的是很快就會導致棧溢位,因為只有8個值的位置。作為代替,編譯器產生的程式碼會在呼叫另一個過程之前,將每個本地浮點值都壓入到主程式棧中,然後在返回時把他們取出來。這樣引起的儲存器訪問操作會降低程式的效能。

     我們用記符%st(i)來引用浮點暫存器,這裡i代表相當於棧頂的位置。值i的範圍為0~7.暫存器%st(0)是棧頂元素,%st(1)是第二個。以此類推,當一個新值壓入棧中時,暫存器%st(7)中的值就丟失了。當從棧中彈出時,%st(7)的新值是不可預測的。編譯器產生的程式碼必須能在暫存器棧有限的容量中工作。

   在過程中使用浮點:同整數引數一樣,浮點引數是通過棧傳遞給呼叫過程。float4個位元組  double8個位元組,結果是以擴充套件精度格式在浮點暫存器棧頂部返回的。

對於浮點,條件碼是浮點狀態字的一部分,浮點狀態字是一個16位暫存器,包含關於浮點單元的各種標誌。必須將這個狀態字轉換成整數字,然後測試某些特殊的位。

      現在,優化編譯器基本上使得效能優化不再是用匯編程式碼寫程式的一個原因了。一個高質量的編譯器產生的程式碼通常和手工編寫的一樣好,甚至更好。而C語言基本上使得機器訪問不再使用匯編程式碼了。C語言能夠通過聯合和指標運算訪問低階資料表示,以及能對位級資料表示進行操作,這就為大多數程式設計師提供了足夠多訪問機器的能力。儘管如此,有時候用匯編寫程式碼仍然是唯一的選擇,特別是實現作業系統時就更是這樣。比如:作業系統必須訪問一些特殊的暫存器,他們存放著程序狀態資訊,執行輸入和輸出操作要使用特殊的指定或是訪問特殊的儲存器位置。即使是對應用程式設計師來說,也有一些機器特性,例如條件碼的值,是不能用C直接訪問的。

現在的問題是要將主要由C組成的程式碼與少量彙編程式碼整合到一起。一種方法是用匯編程式碼寫一些關鍵函式,使用的引數傳遞和暫存器使用規則與C編譯器遵守的一樣,這些彙編函式儲存在獨立的檔案中,由聯結器將編譯好的C程式碼和編譯好的彙編程式碼結合起來。

      基本的內嵌彙編:GCC還可以將彙編與C程式碼混合起來。內嵌彙編允許使用者直接往彙編器產生的程式碼序列中插入彙編程式碼。可以提供一些特性,以指定指令運算元和向彙編器說明彙編指令要覆蓋哪些暫存器。當然得到的程式碼是與機器高度相關的,因為不同型別機器的機器指令是不相容的。asm命令也是與GCC相關的,它與很多其他編譯器是不相容的,儘管如此,這還是一種有效的方式,將於機器相關的程式碼數量降低到絕對小。

        內嵌彙編是作為GCC資訊檔案的一部分來說明的,在任何安裝了GCC的機器上執行命令infogcc,會得到一個分層的文件閱讀器。沿著名為“C Extensions”的連結,然後是名為“Extended   Asm”的連結,就能找到內嵌彙編的文件,不幸的是這個文件有點不完全,也不太準確。

        術語code-string 表示一個以帶括號的字串形式給出的彙編程式碼序列。編譯器會將這個字串一字不差的插入到產生的彙編程式碼中,因此,編譯器提供的彙編和使用者提供的彙編就合併到一起了,編譯器不會檢查字串是否出錯,因此,要等到彙編器才會報告錯誤。

        asm的擴充套件格式:GCC提供了asm的一個擴充套件版本,它允許程式設計師指定哪些程式值要作為彙編程式碼序列的運算元,以及那些暫存器要被彙編程式碼覆蓋。有了這些資訊,編譯器產生的程式碼就能正確建立所需要的源值,執行彙編指令,並使用計算出的值。這些資訊中還包括編譯器所需的關於暫存器使用的資訊,這樣一來,重要的程式值就不會被彙編程式碼指令覆蓋了。

         雖然asm語句的語法有點難懂,而且它的使用也使程式碼的可移植性變差了,但是對於編寫用於少量彙編程式碼來訪問機器級特性的程式,這條語句還是非常有用的。

          想要程式碼進行正常工作,是需要進行一些嘗試和犯點錯誤的,最好的方法就是用-s選項編譯選項,然後檢查生產出的彙編程式碼,看他是否達到了期望的效果。程式碼還應該用不同的選項設定來測試,

          組合語言與C程式碼差別很大。在組合語言程式中,各種資料型別之間的差別很小。程式是以指令序列來表示的,每條指令都完成一個單獨的操作。部分程式狀態,如暫存器和執行時棧,對程式設計師來說是直接可見的。僅提供了低階操作來支援資料處理和程式控制。編譯器必須用多條指令來產生和操作各種資料結構,來實現像條件、迴圈和過程這樣的控制結構。

            C中缺乏邊界檢查,使得許多程式容易出現緩衝區溢位,而這已經使許多系統容易受到入侵者的惡意攻擊。

          編譯C++與編譯C就非常相似。實際上,C++的早起實現就只是你簡單地執行了從C++到C的源到源的轉換,並對結果執行C編譯器,產生目的碼。C++的物件用結構來表示,類似於C的struct。C++的方法是用指向實現方法的程式碼的指標來表示的。相比而言,JAVA的實現方式完全不同。java的目的碼是一種二進位制表示,稱為java位元組程式碼。這種程式碼可以看成是虛擬機器的機器級程式,這種機器並不是直接用硬體實現的,相反,軟體直譯器處理位元組程式碼,模擬虛擬機器的行為。這種方法的有優點是相同的java位元組程式碼可以在許多不同的機器上執行。

第四章、處理器體系結構

 理解處理器是如何工作的能幫助理解整個計算機系統時如何讓工作的。

兩個儲存器傳送指令中的儲存器引用方式是簡單的基址加位移形式,在地址計算中,我們不支援第二變址暫存器和任何暫存器值的伸縮。

同IA32一樣,我們不允許從一個儲存器地址直接傳送到另一個儲存器地址,現在我們也不允許將立即數傳送到儲存器。

有四個整數操作指令,他們是addl、subl、andl、xorl.

七個跳轉指令:jmp 、jle、jl、je、jne、jge、jg

call指令將返回地址入棧,然後跳到目的地址。

pushl和popl指令實現入棧和出棧。

halt指令停止指令的執行。IA32中有一個與之相當的指令,叫hlt。IA32的應用程式不允許使用這條指令,因為它會導致整個系統停止。我們在Y86程式中用halt指令來停止模擬器

有的指令只有一個位元組長,而有的需要運算元的指令編碼就更長一些。首先,可能有附加的暫存器指示符位元組,指定一個或兩個暫存器。從指令的彙編程式碼表示中可以看到,根據指令型別,指令可以指定用於資料來源和目的的暫存器,或是用於地址計算的基址暫存器。沒有暫存器運算元的指令,例如分支指令和呼叫指令,就沒有暫存器指示符位元組。

指令集的一個重要性質就是位元組編碼必須有唯一的解釋。任何一個位元組序列要麼是一個唯一的指令序列的編碼,要麼就不是一個合法的位元組序列。Y86就具有這個性質,因為每條指令的第一個位元組有唯一的程式碼和組合功能,給定這個位元組我們就可以決定所有其他附加位元組的長度和含義。這個性質保證了處理器可以無二義性地執行目的碼程式。只要從序列的第一個位元組開始處理,即使程式碼嵌入在程式中其他位元組中,我們仍然可以很容易地確定指令序列。反過來說如果不知道一段程式碼序列的其實位置,我們就不能準確地確定怎樣將序列劃分成單獨的指令。

. pos    0:從地址0處開始產生程式碼

. align   4:在四位元組邊界處對齊

邏輯設計和硬體控制語言HCL:在硬體設計中,電子電路被用來計算位的函式,以及在各儲存器元素中儲存位。大多數現代電路技術都是用訊號線上的高電壓或低電壓來表示不同的位值。通常的技術中,邏輯1是用1.0伏特左右的高電壓表示的,而邏輯0是用0.0伏特左右的低電壓表示。

要實現一個數字系統需要三個主要的組成部分:計算機的函式的組合邏輯、儲存位的儲存器元素,以及控制儲存器元素更新的時鐘訊號。

遞迴過程:每個呼叫在棧中的都有它自己的私有空間,多個未完成呼叫的區域性變數不會相互影響,當過程被呼叫時分配區域性儲存,當返回時釋放儲存。

陣列分配和訪問:C中陣列是一種將標量型資料聚整合更大資料型別的方式。C用來實現陣列的方式很簡單,因此很容易翻譯成機器程式碼。C的一個不同尋常的特點是可以對陣列中的元素產生指標,並對這些指標進行運算,這些運算會在彙編程式碼中翻譯成地址計算。

優化編譯器非常善於簡化陣列索引所使用的地址計算,不過這使得C程式碼和它到機器程式碼的翻譯之間的對應關係很難理解

指標運算:C允許對指標進行運算,而計算出來的值會根據該指標引用的資料型別的大小進行調節。

單運算元的操作符&和*可以產生指標和間接引用指標。

陣列與迴圈:在迴圈程式碼內,對陣列的引用通常有非常規則的模式,優化編譯器會使用這些模式。

為什麼要避免使用整數乘法?

因為在較老的IA32處理器模型中,整數乘法指令要花費30個時鐘週期,所以編譯器要儘可能地避免使用它,而在大多數新近的處理器模型中,乘法指令只需要3個時鐘週期,所以不一定進行這樣的優化了。

巢狀陣列:即使是建立陣列的陣列時,陣列分配和引用的通用原則也是有效的。

固定大小的陣列:對固定大小的多維陣列進行操作的程式碼,C編譯器能夠進行多種優化。

邏輯閘是數位電路的基本計算元素,他們生產的輸出,等於他們輸入位置的某個布林函式

組合電路和HCL布林表示式:將很多的邏輯閘組合成一個網,我們就能得到計算快,即組合電路。如何組成這個網有兩條限定:

1、兩個或多個邏輯閘的輸出不能接在一起,否則他們會使線上的訊號矛盾,導致一個不合法的電壓或電路故障。

2、這個網必須是無環的,也就是在網中不能有路徑經過一系列的門而形成一個迴路,這樣的迴路會導致該網路的計算函式有歧義。

我們的HCL表示式很清楚地表明瞭組合邏輯電路和C中表達式的相似之處,他們都是用布林操作來對輸入進行計算的函式。值得注意的是這兩種表示式的區別:

1、因為組合電路是由一些邏輯閘組成的,它有個屬性就是輸出會持續地響應出入的變化。如果電路的輸入變化了,在一定的延遲之後,輸出也會相應地變化,而C表示式只會在程式執行過程中被遇到時才進行求職。

2、C的邏輯表示式允許引數是任意數,0表示FALSE,其他的任何值都表示TRUE。而我們的邏輯閘只對位值0和1進行操作。

3、C的邏輯表示式有個屬性就是它們可能只被部分求值。如果一個AND或OR操作的結果只用對第一個引數求值就能確定,那麼就不用對第二個結果進行求職了。

字級的組合電路和HCL表示式:通過將邏輯閘組成一個更大的網,我們可以構造出能計算更加複雜函式的組合邏輯。通常我們設計了能對資料字進行操作的電路,他們是一些位級的訊號代表一個整數或一些控制模式。

執行字級計算的組合電路是根據輸出字的各個位,用邏輯閘來計算輸出子的各個位。

儲存器和時鐘控制:組合電路從本質上講,不儲存任何資訊。相反,他們只是簡單地相應輸入訊號,產生等於輸出的某個函式輸出。為了產生時序電路,也就是有狀態並且在這個狀態上進行計算的系統,我們必須引入按位儲存資訊的裝置,我們考慮兩類儲存器裝置:

1、時鐘暫存器(簡稱暫存器)儲存單個位或字。時鐘訊號控制暫存器載入輸入值;

2、隨機訪問儲存器(簡稱儲存器)儲存多個字,用地址來選擇該讀或該寫哪個字。

暫存器檔案有兩個讀埠(A和B),還有一個寫埠(W),這樣一個多埠隨機訪問儲存器允許進行多個讀和寫操作。

雖然暫存器檔案不是組合電路(因為它有內部的儲存),但是從中讀取字的操作與以地址輸入、資料為輸出的一塊組合邏輯是一樣的。

時鐘訊號按照類似於將值載入進時鐘暫存器一樣的方式控制向暫存器檔案寫入字。

將處理組織成階段:通常,處理一條指令包括很多操作。我們將他們組織成某個特殊的階段序列,使得即使指令的動作差異很大,但所有的指令都都遵循統一的序列。

各個階段以及各個階段內執行的操作:

1、取指:取指階段從儲存器讀入指令,地址為程式計數器(pc)的值。

2、解碼:解碼階段從暫存器檔案讀入最多兩個運算元。

3、執行:在執行階段算術/邏輯單元要麼執行指令指明的操作,計算儲存器引用的有效地址,要麼增加或減少棧指標。

4、訪存:訪存階段可以將資料寫入儲存器,或者從儲存器讀出資料。

5、寫回:寫回階段最多可以寫兩個結果到暫存器檔案。

6、更新PC:將PC設定成下一條指令地址

SEQ唯一的問題就是它太慢了,時鐘必須非常慢,以使訊號能在一個週期內傳播過所有的階段。

SEQ每次只執行一個指令。

流水線化的設計目的就是每個週期都有一條指令進入執行階段並最終完成,要是達到這個指令就意味著吞吐量是每個時鐘週期一條指令。為了達到這個目的我麼必須在取出當前指令之後,馬上確定下一條指令位置,不幸的是,如果取出的指令是條件分支指令,要到幾個週期後,也就是指令通過執行階段後,我們才能知道是否要選擇分支。類似地,如果取出的指令時ret,要到指令通過訪存階段,才能確定返回地址。
流水線過深,收益反而下降。

將流水線技術引入一個帶反饋的系統會導致相鄰指令間在發生相關時出現問題,在完成我們的設計之前,必須解決這個問題,這些相關有兩種形式:

1、資料相關,下一條指令會用到這一條指令計算出的結果。

2、控制相關:一條指令要確定下一條指令的位置,例如在執行跳轉,呼叫或返回指令時,這些相關可能會導致流水線產生計算錯誤,稱為冒險。同相關一樣,冒險也可以分為兩類,資料冒險、控制冒險。

用暫停來避免資料冒險:暫停是一種常用的用來避免冒險的技術,暫停時,處理器會停止流水線中一條或多條指令,直到冒險條件不再滿足。

用轉發來避免資料冒險:我們PIPE的設計是在解碼階段從暫存器檔案中讀入源資料,但是有可能對這些源暫存器的寫要在寫回階段才能進行,與其暫停直到寫完成,不如簡單地將要寫的值傳到流水線暫存器E作為源運算元。

有一類資料冒險不能單純用轉發來解決,因為儲存器讀是在流水線較後面才發生的,例如載入/使用冒險

所有需要流水線控制邏輯進行特殊處理的條件,都會導致我們流水線不能夠實現每個時鐘週期發射一條新指令的目標,我們可以通過確定往流水線中插入旗袍的頻率來衡量這種效率的損失,因為插入旗袍會導致無用的流水線週期,一條返回指令會產生三個氣泡,一個載入/使用冒險會產生一個,而一個預測錯誤的分支會產生兩個。我們可以通過計算PIPE執行一條指令所需要的平均時鐘週期數的估計值,來量化在這些出處罰對整體效能的影響,這種衡量方法稱為CPI(每指令週期數)。這種衡量值是流水線平均吞吐量的倒數,不過時間單位是時鐘週期,而不是微微秒。

另一種看待CPI的方法是,假設我們在處理器上執行某個基準程式,並觀察執行階段的執行。每個週期,執行階段要麼會處理一條指令,然後這條指令繼續通過剩下的階段,直到完成,要麼會處理一個由三種特殊情況之一的而插入的氣泡。

流水線化通過讓不同的階段進行操作,改進了系統的吞吐量效能。

管理複雜性是首要問題。我們想要優化使用硬體資源,在最小的成本下獲得最大的效能。

我麼不需要直接實現ISA.ISA的實現就意味著一個順序的設計。為了獲得更高的效能,我們想運用硬體能力以同時執行許多操作。這就導致要使用流水線化的設計、

硬體設計人員必須非常謹慎小心,一旦晶片被製造出來,就幾乎不可能改正任何錯誤。

第五章  優化程式效能

編寫高效程式需要兩類活動,第一、我們必須選擇一組最好的演算法和資料結構;第二、我們必須編寫出編譯器能夠有效優化以轉換成高效可執行程式碼的原始碼。

對於第二部分,理解優化編譯器的能力和侷限性是很重要的。C有些特性,例如執行指標運算和強制型別轉換的能力,使得對它優化很困難。程式設計師經常能夠以一種使編譯器更容易產生高效程式碼的方式來編寫他們的程式。

在程式開發和優化過程中,我們必須考慮程式碼使用的方式,以及影響它的關鍵因素,程式設計師必須在實現和維護程式的簡單性與它的執行速度之間做出權衡折衷。

事實上,編譯器只能執行有限的程式轉換,而且妨礙優化的因素還會阻礙這種優化,妨礙優化的因素就是程式行為中那些嚴重依賴於執行環境的方面。程式設計師必須編寫易於優化的程式碼,以幫助編譯器。就編譯器來說,編譯技術被分為“與機器無關”和“與機器有關”兩類。

與機器無關:使用這些技術時可以不考慮將執行程式碼的計算機的特性。

與機器有關:這些技術是依賴於許多機器的低階細節的。

為了使程式效能最大化,程式設計師和編譯器需要一個目標機器的模型,指明如何處理指令,以及各個操作的時序特性特性。

研究彙編程式碼是理解編譯器以及產生的程式碼會如何執行的最有效的手段之一。

優化編譯器的能力和侷限性:要求他們決不能改變正確的程式行為,他們對程式行為、對使用他們的環境瞭解有限,需要很快地完成編譯工作。



相關推薦

深入理解計算機系統

從Hello World開始認識計算機系統(c語言) 一枚程式設計小白從2018.9.1的學習歷程… 世界上沒有什麼是努力辦不到的,如果有,那麼就更努力一些吧 1.在Unix系統上,原始檔到目標檔案是如何轉化的呢? 從源程式也就是hello.c經過預處理(cpp

深入理解計算機系統筆記:連結

理解連結有很多好處: 有助於構造大型程式有助於避免一些危險程式設計錯誤有助於理解其他重要的系統概念讓你能夠利用共享庫1. 編譯器驅動程式 編譯命令,假設有main.c和swap.c兩個原始檔 $ gcc -O2 -g -o p main.c swap.c 實際上編譯過程

深入理解計算機系統CSAPP課程實驗bomb程式炸彈實驗日誌phase_4

本文接 深入理解計算機系統(CSAPP)課程實驗bomb程式炸彈實驗日誌(phase_3)繼續寫,phase_4部分在昨天已經完成了,日誌在今天才開始寫。個人認為這個部分是整個bomb程式炸彈最難破解的部分,在破解的過程中發現這是一個遞迴函式,體現在組合語言中就顯得特徵不是

深入理解計算機系統CSAPP課程實驗bomb程式炸彈實驗日誌phase_6

找到phase_6的程式碼,比前面幾關都要長很多: 08048c89 <phase_6>: 8048c89: 55 push %ebp 8048c8a: 89 e5 mov %

深入理解計算機系統CSAPP課程實驗bomb程式炸彈實驗日誌phase_3

在Notepad++編輯器中找到函式phase_3,程式碼如下: 08048ea1 <phase_3>: 8048ea1: 55 push %ebp 8048ea2: 89 e5

速讀《深入理解計算機系統》問題及解決

情況 csdn 第六章 填充 以及 函數 順序 時鐘 管理所 第一章 計算機漫遊 P13:用戶棧和運行時堆有什麽區別?數據結構中經常說堆棧,這裏的堆和棧一樣嗎?和操作系統的堆、棧有什麽區別? 參考:堆和棧的區別(內存和數據結構) 操作系統: 棧:由操作系統自動分配釋放

20179215《深入理解計算機系統》第

imu 組成 不但 圖片 想是 運行 href com 語言 《深入理解計算機系統》第三章 程序的機器級表示學習 讀書筆記 一、這章主要任務: ? 二、程序編碼 ?計算機系統使用了多種不同形式的抽象,利用更簡單的抽象模型來隱藏實現的細節。對於機器級編程來說,其中兩種抽

深入理解計算機系統》第二章學習總結

第二章:資訊的表示和處理 1.二進位制與十六進位制: ①十六進位制數字:0 – F,例子:25A4B ② 二進位制數字:0 – 1,例子:0001 0111 0011 1010 0100 1100 ③相互轉換:十進位制→ 十六進位制:Mod(10,16)(倒序排列) 2.字和資料大小: ① 字長決定了

深入理解計算機系統原書第》pdf附網盤下載連結+附一個菜鳥的java學習之路

技術書閱讀方法論 一.速讀一遍(最好在1~2天內完成) 人的大腦記憶力有限,在一天內快速看完一本書會在大腦裡留下深刻印象,對於之後複習以及總結都會有特別好的作用。 對於每一章的知識,先閱讀標題,弄懂大概講的是什麼主題,再去快速看一遍,不懂也沒有關係,但是一定要在不懂的

深入理解計算機系統原書第》pdf

目錄 · · · · · · 出版者的話 中文版序一 中文版序二 譯者序 前言 關於作者 第1章 計算機系統漫遊1 1.1 資訊就是位+上下文1 1.2 程式被其他程式翻譯成不同的格式3 1.3 瞭解編譯系統如何工作是大有益處的4 1.4 處理器讀並解釋儲存

深入理解計算機系統原書第練習題2.6 感性認識整型和浮點型別同一個數在機器中表示

/** * 練習題2.6 感性認識整型和浮點型別(同一個數)在機器中表示 * 由於我的機器是小端表示,將列印結果還原真實數並用二進位制表示 * 41913500 -->(還原) 0x00359141 -->(二進位制) 0000 0000 0011 0101

深入理解計算機系統隨書原始碼下載

csdn上面好多都需要積分才能下載,最後發現官網上面提供了隨書原始碼的下載。 官網的下載地址: 也提供一個百度網盤的下載,網盤裡面只有套接字部分的程式碼,因為我現在只需要這一塊的程式碼,所以只下載了這一塊的程式碼。 連結:https://pan.baidu.

深入理解計算機系統 練習2.15

練習題 2.15 只使用位級和邏輯運算,編寫一個C表示式,他等價於x==y。換句話說,當x和y相等時他將返回1,否則返回0 因為 x ^ y 只會在x == y時為0,所以我們可以利用這一性質得到這個

深入理解計算機系統序章------談程序員為什麽要懂底層計算機結構

人類 是你 驅動 計算機世界 執行過程 鍵盤 二進制 java虛擬機 調優   萬丈高樓平地起,計算機系統就像程序員金字塔的地基。理解了計算機系統的構造原理,在寫程序的道路上才能越走越遠。道理LZ很早就懂了,可是一直沒下定決心好好鉆研,或許是覺得日常工作中根本用不到這些,又

深入理解計算機系統1.2------存儲設備

高速 計算 想法 知識 1-1 運用 文件 字符 設備   上一章我們講解了hello world 程序在計算機系統中是如何運行的。 hello 程序的機器指令最初是存放在磁盤上的,當程序加載時,他們被復制到主存;當處理器運行程序的時候,指令又從主存復制到處理器。相似的,數

深入理解計算機系統2.4------整數的表示無符號編碼和補碼編碼

class 映射 們的 c語言 正數 裏的 小例子 負數 類型   上一篇博客我們主要介紹了布爾代數和C語言當中的幾個運算符。那麽這一篇博客我們主要介紹在計算機中整數是如何表示的,諸如我們在編碼過程中遇到的對數據類型進行強制轉換可能會得到意想不到的結果在這篇博客裏你會得到解

深入理解計算機系統3.1------匯編語言和機器語言

找到 生產 有著 shu 符號 ces pc機 高效率 機器語言   《深入理解計算機系統》第三章——程序的機器級表示。作者首先講解了匯編代碼和機器代碼的關系,闡述了匯編承上啟下的作用;接著從機器語言IA32著手,分別講述了如何存儲數據、如何訪問數據

深入理解計算機系統3.3------操作數指示符和數據傳送指令

邏輯操作 無效 系統 get 訪問 www. 執行 十六 title   在上一篇博客 程序編碼以及數據格式 中我們給出了一個簡單的C程序,然後編譯成了匯編代碼。大家看不懂沒關系,後面的博客我們將逐漸揭開一些匯編指令的神秘面紗。本篇博客我們將對操作數指示符和數據傳送指令進行

深入理解計算機系統3.8------數組分配和訪問

2個 說明 add 如果 c++編譯 類型 操作 http 程序   上一篇博客我們講解了匯編語言中過程(函數)的調用實現。理解數據如何在調用者和被調用者之間傳遞,以及在被調用者當中局部變量內存的分配以及釋放是最重要的。那麽這篇博客我們將講解數組的分配和訪問。 1、