1. 程式人生 > 其它 >資料結構是如何裝入 CPU 暫存器的?

資料結構是如何裝入 CPU 暫存器的?

大家好,我是小風哥。我們在之前很多文章的講解中涉及了CPU與暫存器,然後有同學問了這樣一個問題:既然CPU內部的暫存器數量有限,容量有限,那麼我們使用的龐大的資料結構是怎樣裝入暫存器供CPU計算的呢?這篇文章就為你講解一下這個問題。

記憶體與資料

真正有用的程式是離不開資料的,比如一個int、一個float等,這些都是非常簡單的資料。當然也有非常複雜的資料,這樣的資料通常在記憶體中以資料結構的形式組織起來,比如你建立了一個數組、一個連結串列、建立了一棵樹、一張圖,就像這樣:那麼很顯然這些資料存放在記憶體中,而且這些資料在不同的場景下有不同的大小,從數B、數KB到數百GB都有可能,與此同時,CPU內部的暫存器數量是固定的,容量也是極其有限的,那麼CPU是如何利用有限的資源操作龐大的資料結構呢?要回答這一問題,我們需要要認識一位農夫,因為他不生產資料,他只是資料的搬運工

,這位農夫就是。。

搬運資料的機器指令

你沒有看錯,這位農夫就是我們之前多次提到的機器指令。機器指令中除了負責邏輯運算、執行流控制、函式呼叫等指令外,還有一類指令,這類執行只負責和記憶體打交道,典型的就是精簡指令集架構中的Load/Store機器指令,即記憶體讀寫指令(複雜指令集沒有單獨的記憶體讀寫指令)。原來,從巨集觀上看的話,存放在記憶體中的資料,比如一個數組,可能會非常龐大,但是具體到程式碼,每一個步驟操作的資料又會非常簡單,就像這樣:

int*huge_arr=newint[1*1024*1024*1024];

我們建立了一個長度為1G的陣列,每個int 4位元組,則這個陣列的大小就是4GB,這顯然是一個很龐大的陣列。對於這樣的資料,我們通常都會怎麼使用呢?最常見的情況可能是遍歷一邊,然後對每個字元進行一個簡單操作,這裡以計算陣列之和為例:

long int sum = 0;for (int i = 0; i < 1 * 1024* 1024 *1024; i++) {    sum += huge_arr[i];}

雖然整個陣列多達4GB,但具體到每一步我們一次只能操作一個元素,就像這裡的:

sum += huge_arr[i];

這行程式碼翻譯成機器指令可能是這樣的,我們假設此時i為100:

load $r0 100($r2)add $r1 $r1 $r0

(注意,實際當中編譯器不會傻傻的生成100這樣的常數,這裡程式碼僅用來方便講解問題)。
第一行指令中陣列首地址存放在暫存器r2中,100($r2)表示陣列首地址+100,這樣我們就能得到huge_arr[100]的地址了,然後將該地址中的值利用load指令載入到暫存器r0中。

買二手手機號平臺地圖第二行就簡單多了,r1暫存器中儲存的是sum的值,該行指令執行過後r1中的值就已經加上了huge_arr[100]。現在你應該能看出來了吧,雖然我們不能把整個陣列載入到暫存器供CPU計算,但這其實是沒有必要的,因為我們一次只能運算元組中的一個元素,我們只需要把這一個元素載入到暫存器就足矣了。對於其它複雜的資料結構也是同樣的道理,無論多麼複雜的資料,程式碼對其一次的操作都是很簡單很微小的,這一微小的操作使用的基本元素都可以通過記憶體讀寫指令載入到暫存器,修改完後再寫回記憶體。

編譯器

現在你應該知道了為什麼CPU內部那麼少的暫存器能操作記憶體中龐大的資料結構,實際上由於記憶體中的資料要遠大於CPU暫存器的容量,因此編譯器必須精心挑選,好讓那些經常使用的資料放到暫存器中的時間更長一點,這樣可以減少記憶體讀寫次數。在上面的示例中,r2暫存器儲存的是huge_arr這個陣列在記憶體中的起始地址,那麼這個資料應該放到暫存器中,因為後續遍歷到的每一個元素都要用到該地址,這項工作就是編譯器來完成的。編譯器把那些經常使用的資料放到暫存器,剩下的放到記憶體中,然後利用記憶體讀寫指令在暫存器和記憶體之間來回搬運資料。

總結

通過本文不難發現,實際上我們沒有必要一次性把整個資料全部裝到CPU暫存器中,而是用到哪些才裝載哪些。在最細粒度的操作中,依賴的運算元都可以直接載入到記憶體,這通常是由記憶體讀寫機器指令來完成的。我是小風哥,希望這篇文章對大家理解CPU與暫存器有所幫助。