記憶體--通俗理解

衝鴨,屎殼郎發表於2020-10-12

什麼是記憶體(一):儲存器層次結構

轉自:https://www.cnblogs.com/yaoxiaowen/p/7805661.html

首先給大家講個段子:

2015年開網咖,買了 DDR4 8g 記憶體條400多根,一根180塊,今年2017年,網咖賠了20多萬,昨天我把網咖電腦全賣了。記憶體條600一根,居然賺回了我網咖的錢,感謝三星,感謝人民,感謝黨。。。

今年以來,記憶體條價格暴漲,已經躍升為新的新一代理財產品,所以今天就和大家討論一下記憶體的話題,主要內容就是在程式執行過程中,記憶體的作用以及如何與CPU,OS互動。

我們先來討論:計算機的執行究竟是在做什麼?來看一下經典的馮諾依曼結構。電腦科學雖然飛速發展了幾十年,但是依舊遵循馮諾依曼結構。

馮諾依曼結構

馮諾依曼結構.jpg-23.2kB
圖1:馮諾依曼結構

數學家馮諾依曼提出的 體系結構包含以下幾個要點:

  • 把程式本身當作資料來對待,程式和該程式處理的資料用同樣的方式儲存。
  • 計算機的數制採用二進位制。
  • 計算機應該按照程式順序執行。

我們根據這張圖進行思考就可以得到一個結論,所謂計算機處理任務,就是根據輸入內容,資料/程式從儲存器送往CPU進行處理,然後再將結果輸出。

關於程式與資料,資料就是一首MP3歌曲, 程式就是用來控制解析播放這首歌的程式碼,從底層來講就是供CPU執行的指令.總之在計算機當中它們都是0和1,不過為行文方便,我們直接簡稱為資料或程式或指令, 將它們理解為同一個意思,畢竟它們都屬於0和1組成的流,這個可以根據上下文來理解。

本文討論的主要內容,就是 儲存器部分,為什麼計算機需要儲存器部分?這是顯而易見的,我寫好了程式,或者下載了一部電影,肯定得有個地方放啊。這樣今後需要的時候,才能執行程式或者看電影啊。

我們思考一下,這個儲存器應該具備什麼樣的特點。

  • 1.穩定,掉電不丟失資料:這個道理上面已經提過,辛辛苦苦下載個小電影,一關電腦資料都丟失了。這肯定不行的。
  • 2.儲存容量大:就像誰也不嫌棄自己錢多,嫌棄自家房子太大。我們既然儲存東西,那麼容量肯定越大越好。
  • 3.讀寫速度快:拷貝個電視劇,速度那麼慢,真心累啊。
  • 4.價格便宜:新發布的iphone x我為啥不買,因為它有一個缺點我無法接受,那就是太貴了。一臺電腦賣一百萬,我們誰又能買得起呢?
  • 5.體積小:這個也是理所當然的。

關於這個儲存器,我們大概想出了一個理想的儲存器應該具備的的5個特點。
但是有句話說的好。理想很豐滿,顯示很骨感。一個屌絲在紙上列出了幾十條他理想女友的標準,但是他能如願嗎?

先說結論,完全滿足我們理想條件的儲存器目前還沒發明出來呢。目前的半導體工業只能造出部分符合條件的儲存器,但是完全滿足以上幾條標準的,對不起,未來也許能做到,但是起碼目前做不到。

所以這也是目前計算機系統儲存器系統比較複雜的原因,區分為記憶體,硬碟,光碟等不同的儲存器,如果有個完美的符合我們理想條件的儲存器,直接使用這種儲存器就好了。

先看看看我們最常見的儲存裝置:磁碟。足夠穩定;有電沒電都正常儲存;容量也較大;價格也可以接受,所以磁碟是我們最常見的儲存裝置。

磁碟就是我們儲存器的代表了。

為了行文方便,文中直接將儲存器用磁碟來代替了,一來大家對磁碟比較熟悉,二來磁碟也是最常見的儲存裝置。類似flash,SD卡,ROM等從廣義上來講,也可以稱為磁碟。因為它們的作用都是儲存資料,掉電後不丟失。(這在下面文章中也會討論到)

磁碟和硬碟什麼關係呢?其實是同一個意思。硬碟是最常見的磁碟型別。在很早之前,計算機使用軟盤儲存資料,所以那種軟盤也被稱為 磁碟,不過軟盤都早就被歷史淘汰了,(電腦硬碟分割槽從C盤開始,就是因為AB盤是之前軟盤的編號)。所以現在我們說磁碟,直接理解成硬碟就好了。

在我們軟體當中,有個概念叫做資料持久化,意思就是說將資料儲存起來,掉電之後不丟失,這其實就是儲存在磁碟上面。

所以現在我們理解的計算機執行就是這樣一個過程:將資料從磁碟送往CPU,供CPU進行計算,並將結果輸出。

因為我們這片文章就是 討論 記憶體,儲存等問題,所以關於 輸入裝置,輸出裝置之類的,就不再涉及和討論。

然後我們再簡短來討論CPU的發展歷史。

世界上第一臺計算機是1946年在美國誕生的ENIAC,當時CPU還是使用笨重的電子管,後面的故事依次是貝爾實驗室發明了電晶體,TI的工程師又發明了整合電晶體,IBM研發成功首款使用積體電路的計算機,IBM360, 後面 就是仙童八叛徒與intel,AMD的故事了。這段很著名的IT故事,我們不再累述了。伴隨著世界上第一款商用處理器:Intel4004的出現,波瀾壯闊的摩爾定律開始了。

當時負責IBM 360 作業系統開發的那個專案經理,根據該專案經驗, 寫了一本經典著作《人月神話》,也有其他參與者根據該專案經驗,立傳出書了,所以當時那批人都是大牛。

摩爾定律:當價格不變時,積體電路上可容納的元器件的數目,約每隔18-24個月便會增加一倍,效能也將提升一倍。

半導體行業開始騰飛了。CPU上整合的電晶體數量越來越多。 intel i9的製程工藝已經到了14nm。所以CPU的執行速度也越來越快。

當然,摩爾定律也快到盡頭了,根據量子力學,2nm是理論極限值。線寬不能再細了,低於2nm,隧穿效應就會產生干擾。

閒扯了一段CPU的發展歷史,想說明的是,現在的CPU整合度越來越高,速度也越來越快。每秒鐘能執行的指令也越來越多。(如果不知道指令,彙編之類的啥意思,看一下我的的另一篇文章關於跨平臺的一些認識,否則下面的內容看著也有難度)。

CPU的作用就是去執行指令(當然,也包括輸出結果等,本文只討論和儲存器相關,所以不扯其他的),並且儘可能的以它的極限最高速度去執行指令,至於具體的執行過程,做過微控制器或者學過微機原理的應該比較清楚。就是伴隨著時鐘週期滴滴答答的節奏,CPU踏著拍子來執行指令。

至於CPU的指令集,那就是Intel的架構師們的工作,總之,CPU認識這些指令,並且能執行運算。(別忘記了馮諾依曼體系結構那張圖)。對於這些指令,但是CPU採取了各種措施來加快執行過程(也可以理解為加快它的計算速度)。比如有以下幾種常見的措施:

  • 流水線(pipeline)技術:有電子廠打工經歷的讀者肯定很熟悉這個流水線模式。CPU的流水線工作方式和工業生產上的流水線概念一樣。就是將一個指令的執行過程也分解為多個步驟,CPU中的每個電路只執行其中一個步驟,這樣前赴後繼加快執行速度。CPU中多個不同功能的電路單元組成一條指令處理流水線,然後將一條指令分成幾個步驟後再由這些電路單元分別執行。在執行過程中,指令源源不斷的送往CPU。讓每個電路單元都不閒著,這樣就大大的加快了執行速度。
  • 超執行緒(Hyper-Threading)技術:對於超執行緒,百度百科的解釋我都沒看懂,但是大概原理就是這樣的。CPU在進行執行緒切換的時候,要執行 切換各種暫存器狀態等一些操作。把第一個執行緒的各種暫存器狀態寫回快取中儲存,然後把第二個執行緒的相關內容送到各種暫存器上。該過程必不可少,否則待會再將第一個執行緒切換回來時,不知道該執行緒的各個狀態, 那還怎麼接著繼續執行呢?也正因為如此,所以這個過程比較慢,大概需要幾萬個時鐘週期。所以後來做了這樣的設計,把每個暫存器等都多做一個,就是多做一組暫存器(也包括一些其他相關電路等),,CPU在執行A執行緒時,使用的第一組暫存器,切換到B執行緒,直接使用第二組暫存器,然後再切換A執行緒時,再使用第一組暫存器。,CPU就不用再傻傻的等著暫存器值的切換,執行緒切換隻需要幾個時鐘週期就夠了。對於普通的執行多工的計算機,CPU執行緒切換是個非常頻繁的操作,所以使用該技術就會節省大量的時鐘週期。也就是相當於加快了CPU的執行速度。這就是CPU宣傳引數中所謂的四核八執行緒的由來,其實就是超執行緒技術。(每個核多做一組暫存器等電路固然會佔用寶貴的空間,但是它帶來的優點遠遠大於缺點)。
  • 超標量技術:CPU可以在每個時鐘週期內執行多個操作,可以實行指令的並行運算。
  • 亂序執行: 我們認為程式都是順序執行的。但是在CPU層面上,指令的執行順序並不一定與它們在機器級程式(彙編)中的順序一樣。比如 a = b+c; d++;這兩個語句 不按照順序執行也不會影響最終結果。當然這只是在CPU執行指令的層面,在程式設計師們看來,依舊認為程式是順序執行的。

前面扯了那麼多,就是為了說明CPU的執行速度很快。雖然每條指令的執行時間需要幾個時鐘週期到幾十個時鐘週期不等。但是CPU採用了種種技術來加快執行過程。所以平均執行一條指令只需要一個週期。而現在CPU主頻都那麼高。比如i7 7700K主頻達到了 4.2G。這也就意味著,每個core每秒鐘大約可以執行4.2億條指令。那四個core呢?

CPU每秒鐘可以執行幾億(甚至十幾億)條指令,所以它的執行速度真丫的的快啊

我們討論完CPU如此快的執行速度,我們再來說我們常見的儲存裝置-機械硬碟。

機械硬碟結構.jpg-90.4kB

圖2:機械硬碟結構

機械硬碟的結構就不再具體的討論了。它讓我想起了民國電影中那種播放音樂的唱片機。

帶機械硬碟的電腦,在使用過程中,如果機箱被摔了,可能後果很嚴重,就是因為可能會把機械硬碟的那個讀寫頭/傳動臂等機械結構摔壞。

機械硬碟容量很大(目前普遍1T,2T),我們的資料和程式是儲存在磁碟上的,所以CPU要想執行指令/資料,就要從儲存器,也就是磁碟上讀取, CPU一秒鐘可以執行幾億條指令,但是相對之下,磁碟的讀寫速度就是慢如蝸牛。假設磁碟一秒鐘可以讀取100條指令。那麼這中間就存在 巨大的速度差異。半導體行業發展了幾十年,CPU的執行速度一再飛速提升,奈何磁碟技術發展的太不給力了,CPU再快,可是磁碟嚴重拖後腿,那CPU就相當於工作嚴重不飽和,如果直接從磁碟上 來讀取資料,那麼CPU相當於 99.9999%的時間都在閒置著。

"假設磁碟一秒鐘可以讀取100條指令。":帶有假設字樣的,具體數字都是隨便寫的。比如 磁碟讀寫速度自然有它的引數指標,不過我們只是為了說明問題, 所以能理解其中的道理就好。

磁碟廠商們也在努力研究,比如SSD(固態硬碟),它的速度就比 機械硬碟快了一二十倍吧。但是對於CPU的速度,這也是然並卵啊。(更何況SSD相比機械硬碟太貴了)

所以這就是個大問題。

我們的目標就是執行任務時讓CPU全負荷的執行,爭取對於每一個時鐘週期,CPU都不會閒置浪費。

這就像是老闆對我們這些員工的希望一樣。老闆給我們發工資, 那麼他就是希望我們每一天的每一分每一秒都在努力幫公司幹活。不要有什麼任何時間閒著。所以我們要感謝勞動法,讓我們每天工作八小時就夠了。畢竟我們也是血肉之軀,也需要吃喝拉撒睡覺。

看到勞動法說每天工作八小時就夠了,程式猿們哭暈在廁所。

程式猿問科比:“你為什麼這麼成功? ”
科比:“你知道洛杉磯凌晨四點是什麼樣子嗎? ”
程式猿:“不知道,一般那個時候我還沒下班呢,怎麼了?”
科比:“額…….”

通過上面的介紹,我們就明白了計算機體系的主要矛盾,CPU太快了,而磁碟太慢了。所以它倆是不能夠直接通訊的,我們可以加一層過度。這就是記憶體的作用。這就是幾百塊錢一根的記憶體條的作用和功能。

實際上,一般情況下,記憶體的讀寫速度比磁碟快幾十萬倍左右。所以它終於夠資格和CPU直接通訊了。

這裡有張圖,我們來看一下磁碟/記憶體,與CPU速度之間逐漸增大的差距(主要是CPU技術發展太迅猛了)。

磁碟DRAM和cpu速度之間逐漸增大的差距.png-122.1kB

圖三:磁碟DRAM和cpu速度之間逐漸增大的差距

所以現在程式執行過程是這樣的。CPU執行任務時,只與記憶體通訊,它從記憶體獲取指令/資料或寫回資料。記憶體再與磁碟通訊,記憶體從磁碟讀取資料/指令,或者記憶體將資料寫回磁碟。

提到新增過渡層。這其實和JVM的原理都是類似的。具體可參考我的另一篇文章關於跨平臺的一些認識。也許這就是大道至簡吧。

儲存器層次結構

我們這裡說的記憶體,主要是指主存。就是主機板上插的記憶體條。它的讀寫速度比磁碟快了幾十萬倍。但是相對於CPU的速度依舊還是慢。那麼主存和CPU之間,可以繼續新增速度更快的過度層。所以intel i7的儲存器層次結構是這樣的。

一個儲存器層次結構的示例.jpg-111.3kB

圖4:一個儲存器層次結構的示例

前面扯了那麼多篇幅,就是告訴你,我們為什麼需要記憶體(主存),那麼理解了主存,自然也就理解了L3,L2,L1等各級快取存在的意義。對於現代的計算機系統,在CPU與磁碟/主存之間,加了多層過度層。

嚴格來講,應該叫CPU的算術邏輯單元(ALU),但是簡單的直接說CPU,大家肯定也能聽得懂。

實際上這是一種快取思想。比如,本地磁碟也相當於 遠方伺服器的快取。因為我們從網上下載資料/檔案時,速度明顯比從本地磁碟讀取要慢。

一般情況下,L5磁碟與L4主存速度相差幾十萬倍, 而L3-L0之間,它們每級快取的速度差異大概是10倍。

我們是拿i7處理器來做例子,它有三級快取,像低端一些的處理器,比如i3,只有兩級快取,但是道理是相同的。本文當中,都是拿i7的儲存器層次來做例子。

明白一點。CPU執行速度實在太快了,一秒鐘執行幾億/十幾億條指令,CPU幹活乾脆利落,那麼儲存器就要想方設法的用最快的速度把指令/資料 送給CPU去執行。否則CPU幹活再快,又有什麼意義呢。

基本思想已經理解了。那麼我們就開始具體討論細節問題。

RAM,ROM,匯流排等

看看上面那幅圖,什麼SRAM,DRAM,還有我們前面講的SSD,Flash,機械硬碟等,還有下面要討論的匯流排(BUS),所以我們先來討論一些基礎硬體知識.

首先,他們都屬於儲存器,儲存器分為兩類:

  1. 易失性(volatile)儲存器:包括記憶體,SRAM,DRAM等,特點是讀寫速度很快,掉電了資料會丟失,價格貴,並且儲存容量較小。
  2. 非易失性(nonvolatile)儲存器:包括磁碟,Flash,光碟,機械硬碟,SSD等,與易失性儲存器相比,它們讀寫速度很慢,但是掉電不丟失資料,儲存容量比較大,價格也便宜。
  • RAM(Random-Access Memory):隨機訪問儲存器。易失性儲存器。也可以訪問兩類:SRAM(靜態的)和DRAM(動態的),並且SRAM的讀寫速度比DRAM更快,價格也更貴。在上圖中也可以看到, SRAM做L1-L3級快取,而DRAM做L4級的主存。
  • ROM(read-only memory):只讀儲存器,非易失性儲存器。這個名字容易讓人產生誤解,它既可以讀,也可以寫,稱之為read-only只是歷史原因。

ROM相比於RAM,容量更大,價格便宜,讀寫速度則比較慢。

  • 快閃記憶體(Flash memory):非易失性儲存器。SSD,SD卡都屬於Flash技術,如果從概念上來講,他們都屬於ROM,這類儲存器經常用在手機,相機等裝置上。而機械硬碟常用在個人計算機,伺服器上。

其實我覺的把 Flash,ROM等都叫做磁碟,也沒什麼錯。畢竟它們的作用和概念都是相似的,區別只是他們各自使用的半導體技術不同。Flash晶片等基於整合晶片的儲存器讀寫速度比機械硬碟快,不過(相同容量下)價格也比後者貴。而它們相比於SRAM,DRAM則非常慢了,所以後者理解為記憶體即可。

"圖4:一個儲存器層次結構的示例",越往上,讀寫速度越快,價格更貴,儲存容量也越小。(淘寶上搜搜8G的記憶體條,256G的SSD,1T的機械硬碟都是什麼價格就明白了)。像L0 暫存器,每個暫存器只能儲存一個字長的內容,但是CPU讀寫取暫存器耗費的時鐘週期為0個。這是最快的速度。

另外,我們在電腦主機板上可以看到記憶體條(L4主存)。硬碟(L5),但是卻沒看到L3-L0。原因很簡單,他們都是整合在CPU晶片內部的。

我們知道了儲存器的層級結構,下面還有一個問題,就是怎麼把硬碟,記憶體條之類的連線起來進行通訊呢,這就是 匯流排(Bus)了。

一個典型系統的硬體組成.jpg-91.1kB

圖6:一個典型系統的硬體組成

上圖存在三條匯流排,IO匯流排,儲存器匯流排(通常稱為記憶體匯流排),系統匯流排。在主機板上,就是那一排排的32/64根並行的導線。這些導線用來連線CPU,記憶體,硬碟,以其他外圍裝置。CPU與儲存器,輸入輸出裝置等通訊,都是通過匯流排。不同匯流排的速度也有差異。

CPU要通過I/O橋(就是主機板的北橋/南橋晶片組)與外圍裝置連線,因為CPU的主頻太高了,它的時鐘週期一秒鐘震盪幾億次,外圍裝置的時鐘週期都較慢,所以他們不能直接通訊。

本文是討論軟體的,所以硬體部分就一筆帶過,讀者知道有這回事就ok了。匯流排上攜帶地址,資料和控制訊號, 如何區分不同訊號,分辨它與哪個外圍裝置通訊,這就是另外一個問題了。

不管中間怎麼加快取,資料從硬碟到記憶體的速度就是那麼慢,那麼這些快取意義何在?

有些讀者腦子轉的比較快,可能想到了這樣一個問題。

不管你中間怎麼加快取,也不管中間的什麼SRAM,DRAM的讀寫速度有多快,但是磁碟的讀寫速度就是那麼慢,所以磁碟與主存之間的互動速度很慢。CPU歸根到底需要向磁碟讀寫資料。整個環節速度瓶頸就是在磁碟那裡,這個根本快不了,那麼加那麼多級快取,意義有何在呢?

這是一個好問題啊。 下面讓我們繼續討論。

我們來看看,CPU如何讀取磁碟中的一個資料。

讀一個磁碟扇區-1.png-27.6kB

讀一個磁碟扇區-2.png-138.1kB

圖7:讀一個磁碟扇區

網上找的圖片不是很清楚,注意每張圖中的黑線。步驟分三部:

  1. CPU 將相關的命令和地址,通過系統匯流排和IO匯流排傳遞給磁碟,發起一個磁碟讀。
  2. 磁碟控制器將相關的地址解析,並通過IO匯流排與記憶體匯流排將資料傳給記憶體。
  3. 第2步完成之後,磁碟控制器向CPU傳送一箇中斷訊號。(學電子的同學應該很清楚中斷是什麼)。這時CPU就知道了,資料已經傳送到記憶體了。

第二步磁碟操作很慢,但是在第一步CPU發出訊號後。但是第二步和第三部時,CPU根本不參與。第二步很耗時,所以CPU在第一步發出訊號後,就去在幹其他事情啊。(切換到另一個執行緒)。所以此時的CPU依舊沒有閒著。而待第三步時,通過中斷,硬碟主動發訊號給CPU,你需要的資料已經傳送到記憶體了,然後此時它可以將執行緒再切換回來,接著執行這個該執行緒的任務。

除了多執行緒切換,避免CPU閒置浪費,還有一點。
我先問一個問題。

//@author :www.yaoxiaowen.com
int main(){
	//我們執行任務的程式碼
    return 0;
}

對於一個應用/程式而言,它都應該有一個入口。(雖然不一定需要我們直接寫main函式)。入口函式內部就是我們的任務程式碼,任務程式碼執行完了這個應用/程式也就結束了。這個很好理解,比如測試工程師寫的一個測試case。跑完了這個任務就結束了。

但是 有些程式,比如一個 app,你開啟了這個app。不做任何操作。這個介面會一直存在,也不會消失。思考一下這是為什麼。因為這個app程式肯定也要有一個main入口。 main裡面的任務程式碼執行完了,就應該結束了。而一個程式的程式碼/指令數目肯定是有限的。但該app在我們不主動退出情況下,卻不會主動結束。

所以這個app程式的入口main來講,其實是這樣的。

//@author :www.yaoxiaowen.com
int main(){
	boolean flag  = true;
	while (flag){
		//我們執行任務的程式碼
	}
    return 0;
}

並且不僅如此,在一個程式內部,也有大量的for,while等迴圈語句。
那麼當我們把這些相關的程式碼指令送到了主存,或者更高一級的快取時,那麼CPU在執行這些指令時,存取速度自然快了很多。

在執行一個程式時,啟動階段比較慢,因為需要從磁碟讀取資料。(而CPU在這個階段也沒閒置浪費,它會進行執行緒切換執行其他任務)。 但是資料被送往記憶體之後,它執行起來就會快多了,並且伴隨著執行過程,還可能越來越快,因為這些資料,有可能被一級一級的向上送,從L4,送到L3,再送到L2,L1

so,上述那個問題的答案,已經解釋的比較清楚了吧。

區域性性原理(Principle of locality)

locality對於硬體和軟體系統的設計和效能都有著重要的影響。對於我們理解儲存器的層次結構也必不可缺。

程式傾向於引用臨近於與其他最近引用過的資料項的資料項。或者最近引用過的資料項本身。這種傾向性,我們稱之為區域性性原理。它通常有以下兩種形式:

  • 時間區域性性(temporal locality):被引用過一次的儲存器位置的內容在未來會被多次引用。
  • 空間區域性性(spatial locality):如果一個儲存器位置的內容被引用,那麼它附近的位置也很大概率會被引用。

一般而言,有良好區域性性的程式比區域性性差的程式執行的更快。 現代計算機系統的各個層次,從硬體到作業系統、再到應用程式,它們的設計都利用了區域性性。

當然,光說理論的東西比較玄乎。我們來看實際的例子。

//@author www.yaoxiaowen.com
int sum1(int array[N])
{
    int i, sum = 0;
    for(i = 0; i < N; i++)
        sum += array[i];
    return sum;
}

在這個程式中,變數sum,i在每次迴圈迭代時被引用一次,因此對sumi來說,有較好的時間區域性性。
對變數array來說,它是一個int型別陣列,迴圈時按順序訪問array,因為一個C陣列在記憶體中是佔用連續的記憶體空間。因而的較好的空間區域性性,

再來看一個例子:

//@author www.yaoxiaowen.com
int sum2(int array[M][N])
{
    int i, j, sum = 0;
    for(i = 0; i < M; i++){
        for(j = 0; j < N; j++)
            sum += array[j][i];
    }   
    return sum;
}

這是一個空間區域性性很差的程式。
假設這個陣列是array[3][4],因為C陣列在記憶體中是按行順序來存放的。所以sum2對每個陣列元素的訪問順序成了這樣:0, 4, 8, 1, 5, 9…… 7, 11。所以它的空間區域性性很差。

但是幸運的是,一般情況下軟體程式設計天然就是符合區域性性原理的。比如程式的迴圈結構。

假設CPU需要讀取一個值,int var,而var在L4主存上,那麼該值會被依次向上送,L4->L3->L2,但是這個傳遞的過程並不是單純的只傳遞var四個位元組的內容,而是把var所在的記憶體塊(block),依次向上傳遞,為什麼要傳遞block?因為根據區域性性原理,我們認為,與var值相鄰的值,未來也會被引用。

儲存器的層次結構,資料進行傳送時,是以block(塊)為單位傳送的。在整個層次結構上,越往上,block越小而已。

儲存器層次結構中的快取

洋洋灑灑的扯了那麼多,我相對於所謂的 儲存器層次結構讀者應該有一個基本的認識,有些地方介紹的 不夠嚴謹,但是本文的目的也就是讓大家理解基本思想。

歸根到底,它就是一個快取(caching)的思想,並且其實不復雜,

我們做app開發時,對於app中活動頁面等,都是後臺發給我們圖片url,我們下載後才顯示在app上,這時我們總要使用 Glide,Picasso 等圖片快取框架來把下載好的圖片快取在手機本地儲存上。這樣下次開啟app時,如果這個圖片連結沒有改變,我們就直接拿手機本地快取的圖片來進行顯示,而不用再從伺服器上下載了。如果圖片連結改變了,則重新下載。為什麼要這麼做?因為從伺服器上下載比較慢,而手機本地儲存(ROM)中讀取就會快很多。

這個時候可以再回頭看看"圖4:一個儲存器層次結構的示例"

下面這張圖和這段文字來自《深入理解計算機系統》(CSAPP),大家可以有個更嚴謹和細節的認識。

儲存器層次結構中基本的快取原理.png-58.5kB

圖8:儲存器層次結構中基本的快取原理

儲存器層次結構的中心思想:位於k層的更快更小的儲存裝置作為位於k+1層得更大更慢的儲存裝置的快取;資料總是以塊大小為傳送單元(transfer unit)在第k層和第k+1層之間來回拷貝的;任何一對相鄰的層次之間傳送的塊大小是固定的,即每一級快取的塊大小是固定的。但是其它的層次對之間可以有不同的塊大小。

當程式需要第k+1層的某個資料物件d時,它首先在當前儲存在第k層的一個塊中查詢d。如果d剛好在k層,那麼就是快取命中。如果第k層中沒有快取資料物件d,那麼就是快取命不中。當快取不命中發生時,第k層的快取從第k+1層 快取中取出包含d的那個塊,如果第k層的快取已經滿了的話,可能會覆蓋現存的一個塊。(覆蓋策略可以使用常見的LRU演算法)。

volatile 關鍵字

在java和C當中,有一個volatile關鍵字(其他語言估計也有),它的作用就是在多執行緒時保證變數的記憶體可見性,但是具體怎麼理解呢?

我們在"圖4:一個儲存器層次結構的示例"中,說的快取結構其實對於一個單核CPU而言的,比如 對於 一個四核三級快取的CPU,它的快取結構是這樣的。

多核處理器快取結構.jpg-47.8kB

圖9:多核處理器快取結構

我們可以看到L3是四個核共有的,但是L2,L1其實是每個核私有的,如果我有一個變數var,它會被兩個執行緒同時讀取,這兩個執行緒在兩個核上並行執行,因為我們的快取原理,這個var可能分別在兩個核的 L2L1快取,這樣讀取速度最快,但是該var值可能就分別被這兩個核分別修改成不同的值, 最後將值回寫到L3L4主存,此時就會發生bug了。

所以volatile關鍵字就是預防這種情況,對於被volatile修飾的的變數,每次CPU需要讀取時,都至少要從L3讀取,並且CPU計算結束後,也立刻回寫到L3中,這樣讀寫速度雖然減慢了一些,但是避免了該值在每個core的私有快取中單獨操作而其他核不知道。

相關文章