操作系統(tǒng)思考 第七章 緩存

第七章 緩存

作者:Allen B. Downey

原文:Chapter 7 Caching

譯者:飛龍

協(xié)議:CC BY-NC-SA 4.0

7.1 程序如何運(yùn)行

為了理解緩存,你需要理解計(jì)算機(jī)如何運(yùn)行程序。你應(yīng)該學(xué)習(xí)計(jì)算機(jī)體系結(jié)構(gòu)來(lái)深入理解這個(gè)話(huà)題。這一章中我的目標(biāo)是給出一個(gè)程序執(zhí)行的簡(jiǎn)單模型。

當(dāng)程序啟動(dòng)時(shí),代碼(或者程序文本)通常位于硬盤(pán)上。操作系統(tǒng)創(chuàng)建新的進(jìn)程來(lái)運(yùn)行程序,之后“加載器”將代碼從存儲(chǔ)器復(fù)制到主存中,并且通過(guò)調(diào)用main來(lái)啟動(dòng)程序。

在程序運(yùn)行之中,它的大部分?jǐn)?shù)據(jù)都儲(chǔ)存在主存中,但是一些數(shù)據(jù)在寄存器中,它們是CPU上的小型儲(chǔ)存單元。這些寄存器包括:

  • 程序計(jì)數(shù)器(PC),它含有程序下一條指令(在內(nèi)存中)的地址。
  • 指令寄存器(IR),它含有當(dāng)前執(zhí)行的指令的機(jī)器碼。
  • 棧指針(SP),它含有當(dāng)前函數(shù)棧幀的指針,其中包含函數(shù)參數(shù)和局部變量。
  • 程序當(dāng)前使用的存放數(shù)據(jù)的通用寄存器。
  • 狀態(tài)寄存器,或者位寄存器,含有當(dāng)前計(jì)算的信息。例如,位寄存器通常含有一位來(lái)存儲(chǔ)上個(gè)操作是否是零的結(jié)果。

在程序運(yùn)行之中,CPU執(zhí)行下列步驟,叫做“指令周期”:

  • 取指(Fetch):從內(nèi)存中獲取下一條指令,儲(chǔ)存在指令寄存器中。
  • 譯碼(Decode):CPU的一部分叫做“控制單元”,將指令譯碼,并向CPU的其它部分發(fā)送信號(hào)。
  • 執(zhí)行(Execute):收到來(lái)自控制單元的信號(hào)后會(huì)執(zhí)行合適的計(jì)算。

大多數(shù)計(jì)算機(jī)能夠執(zhí)行幾百條不同的指令,叫做“指令集”。但是大多數(shù)指令可歸為幾個(gè)普遍的分類(lèi):

  • 加載:將內(nèi)存中的值送到寄存器。
  • 算術(shù)/邏輯:從寄存器加載操作數(shù),執(zhí)行算術(shù)運(yùn)算,并將結(jié)果儲(chǔ)存到寄存器。
  • 儲(chǔ)存:將寄存器中的值送到內(nèi)存。
  • 跳轉(zhuǎn)/分支:修改程序計(jì)數(shù)器,使控制流跳到程序的另一個(gè)位置。分支通常是有條件的,也就是說(shuō)它會(huì)檢查位寄存器中的旗標(biāo),只在設(shè)置時(shí)跳轉(zhuǎn)。

一些指令集,包括普遍的x86,提供加載和算術(shù)運(yùn)算的混合指令。

在每個(gè)指令周期中,指令從程序文本處讀取。另外,普通程序中幾乎一半的指令都用于儲(chǔ)存或讀取數(shù)據(jù)。計(jì)算機(jī)體系結(jié)構(gòu)的一個(gè)基礎(chǔ)問(wèn)題,“內(nèi)存瓶頸”就在這里。

在當(dāng)前的臺(tái)式機(jī)上,CPU通常為2GHz,也就是說(shuō)每0.5ns就會(huì)初始化一條新的語(yǔ)句。但是它用于從內(nèi)存中傳送數(shù)據(jù)的時(shí)間約為10ns。如果CPU需要等10ns來(lái)抓取下一條指令,再等10ns來(lái)加載數(shù)據(jù),它可能需要40個(gè)時(shí)鐘周期來(lái)完成一條指令。

7.2 緩存性能

這一問(wèn)題的解決方案,或者至少是一部分的解決方案,就是緩存。“緩存”是CPU上小型、快速的儲(chǔ)存空間。在當(dāng)前的計(jì)算機(jī)上,儲(chǔ)存通常為12MiB,訪問(wèn)速度為12ns。

當(dāng)CPU從內(nèi)存中讀取數(shù)據(jù)時(shí),它將一份副本存到緩存中。如果再次讀取相同的數(shù)據(jù),CPU就直接讀取緩存,不用再等待內(nèi)存了。

當(dāng)最后緩存滿(mǎn)了的時(shí)候,為了能讓新的數(shù)據(jù)進(jìn)來(lái),我們需要將一些數(shù)據(jù)扔掉。所以如果CPU加載數(shù)據(jù)之后,過(guò)了一段時(shí)間再來(lái)讀取,數(shù)據(jù)就可能不在緩存中了。

許多程序的性能受限于緩存的效率。如果CPU所需的數(shù)據(jù)通常在緩存中,程序可以以CPU的全速來(lái)運(yùn)行。如果CPU時(shí)常需要不在緩存中的數(shù)據(jù),程序就會(huì)受限于內(nèi)存的速度。

緩存的“命中率”h,是內(nèi)存訪問(wèn)時(shí),在緩存中找到數(shù)據(jù)的比例。“缺失率”m,是內(nèi)存訪問(wèn)時(shí)需要訪問(wèn)內(nèi)存的比例。如果Th是處理緩存命中的時(shí)間,Tm是緩存未命中的時(shí)間,每次內(nèi)存訪問(wèn)的平均時(shí)間是:

h * Th + m * Tm

同樣,我們可以定義“缺失懲罰”,它是處理緩存未命中所需的額外時(shí)間,Tp = Tm - Th,那么平均訪問(wèn)時(shí)間就是:

Th + m * Tp

當(dāng)缺失率很低時(shí)平均訪問(wèn)時(shí)間趨近于Th,也就是說(shuō),程序可以表現(xiàn)為內(nèi)存具有緩存的速度那樣。

7.3 局部性

當(dāng)程序首次讀取某個(gè)字節(jié)時(shí),緩存通常加載一“塊”或一“行”數(shù)據(jù),包含所需的字節(jié)和一些相鄰數(shù)據(jù)。如果程序繼續(xù)讀取這些相鄰數(shù)據(jù),它們就已經(jīng)在緩存中了。

例如,假設(shè)塊大小是64B,你讀取一個(gè)長(zhǎng)度為64的字符串,字符串的首個(gè)字節(jié)恰好在塊的開(kāi)頭。當(dāng)你加載首個(gè)字節(jié)之后,你觸發(fā)了缺失懲罰,但是之后字符串的剩余部分都在緩存中。在讀取整個(gè)字符串之后,命中率是63/64。如果字符串被分在兩個(gè)塊中,你應(yīng)該會(huì)觸發(fā)兩次缺失懲罰。但是這個(gè)命中率是62/64,約為97%。

另一方面,如果程序不可預(yù)測(cè)地跳來(lái)跳去,從內(nèi)存中零散的位置讀取數(shù)據(jù),很少兩次訪問(wèn)到相同的位置,緩存的性能就會(huì)很低。

程序使用相同數(shù)據(jù)多于一次的傾向叫做“時(shí)間局部性”。使用相鄰位置的數(shù)據(jù)的傾向叫做“空間局部性”。幸運(yùn)的是,許多程序天生就帶有這兩種局部性:

  • 許多程序含有非跳轉(zhuǎn)或分支的代碼塊。在這些代碼塊中指令順序執(zhí)行,訪問(wèn)模式具有空間局部性。
  • 在循環(huán)中,程序執(zhí)行多次相同指令,所以訪問(wèn)模式具有時(shí)間局部性。
  • 一條指令的結(jié)果通常用于下一指令的操作數(shù),所以數(shù)據(jù)訪問(wèn)模式具有時(shí)間局部性。
  • 當(dāng)程序執(zhí)行某個(gè)函數(shù)時(shí),它的參數(shù)和局部變量在棧上儲(chǔ)存在一起。這些值的訪問(wèn)具有空間局部性。
  • 最普遍的處理模型之一就是順序讀寫(xiě)數(shù)組元素。這一模式也具有空間局部性。

下一節(jié)中我們會(huì)探索程序的訪問(wèn)模式和緩存性能的關(guān)系。

7.4 緩存性能的度量

當(dāng)我還是UC伯克利的畢業(yè)生時(shí),我是Brian Harvey計(jì)算機(jī)體系結(jié)構(gòu)課上的助教。我最喜歡的練習(xí)之一涉及到一個(gè)迭代數(shù)組,讀寫(xiě)元素并度量平均時(shí)間的程序。通過(guò)改變數(shù)組的大小,就有可能推測(cè)出緩存的大小,塊的大小,和一些其它屬性。

我的這一程序的修改版本在本書(shū)倉(cāng)庫(kù)的cache目錄下。

程序的核心部分是個(gè)循環(huán):

iters = 0;
do {
    sec0 = get_seconds();

    for (index = 0; index < limit; index += stride) 
        array[index] = array[index] + 1;
    
    iters = iters + 1; 
    sec = sec + (get_seconds() - sec0);
    
} while (sec < 0.1);

內(nèi)部的for循環(huán)遍歷了數(shù)組。limit決定數(shù)組遍歷的范圍。stride決定跳過(guò)多少元素。例如,如果limit是16,stride是4,循環(huán)就會(huì)訪問(wèn)0、4、8、和12。

sec跟蹤了CPU用于內(nèi)循環(huán)的的全部時(shí)間。外部循環(huán)直到sec超過(guò)0.1秒才會(huì)停止,這對(duì)于我們計(jì)算出平均時(shí)間所需的精確度已經(jīng)足夠長(zhǎng)了。

get_seconds使用系統(tǒng)調(diào)用clock_gettime,將結(jié)果換算成秒,并且以double返回結(jié)果。

double get_seconds(){
    struct timespec ts;
    clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts);
    return ts.tv_sec + ts.tv_nsec / 1e9;
}

圖 7.1:數(shù)據(jù)大小和步長(zhǎng)的平均缺失懲罰函數(shù)

為了將訪問(wèn)數(shù)據(jù)的時(shí)間分離出來(lái),程序運(yùn)行了第二個(gè)循環(huán),它除了內(nèi)循環(huán)不訪問(wèn)數(shù)據(jù)之外完全相同。它總是增加相同的變量:

iters2 = 0;
do {
    sec0 = get_seconds();
    
    for (index = 0; index < limit; index += stride) 
        temp = temp + index;
    
    iters2 = iters2 + 1;
    sec = sec - (get_seconds() - sec0);

} while (iters2 < iters);

第二個(gè)循環(huán)運(yùn)行和第一個(gè)循環(huán)相同數(shù)量的迭代。在每輪迭代之后,它從sec中減少了消耗的時(shí)間。當(dāng)循環(huán)完成時(shí),sec包含了所有數(shù)組訪問(wèn)的總時(shí)間,減去用于增加temp的時(shí)間。其中的差就是所有訪問(wèn)觸發(fā)的全部缺失懲罰。最后,我們將它除以訪問(wèn)總數(shù)來(lái)獲取每次訪問(wèn)的平均缺失懲罰,以ns為單位:

sec * 1e9 / iters / limit * stride

如果你編譯并運(yùn)行cache.c,你應(yīng)該看到這樣的輸出:

Size:    4096 Stride:       8 read+write: 0.8633 ns
Size:    4096 Stride:      16 read+write: 0.7023 ns
Size:    4096 Stride:      32 read+write: 0.7105 ns
Size:    4096 Stride:      64 read+write: 0.7058 ns

如果你安裝了Python和Matplotlib,你可以使用graph_data.py來(lái)使結(jié)果變成圖形。圖7.1展示了我運(yùn)行在Dell Optiplex 7010上的結(jié)果。要注意數(shù)組大小和步長(zhǎng)以字節(jié)為單位表述,并不是數(shù)組元素?cái)?shù)量。

花一分鐘來(lái)考慮這張圖片,并且看看你是否能推斷出緩存信息。下面是一些需要思考的事情:

  • 程序多次遍歷并讀取數(shù)組,所以有大量的時(shí)間局部性。如果整個(gè)數(shù)組能放進(jìn)緩存,平均缺失懲罰應(yīng)幾乎為0。
  • 當(dāng)步長(zhǎng)是4的時(shí)候,我們讀取了數(shù)組的每個(gè)元素,所以程序有大量的空間局部性。例如,如果塊大小足以包含64個(gè)元素,即使數(shù)組不能完全放在緩存中,命中率應(yīng)為63/64。
  • 如果步長(zhǎng)等于塊的大小(或更大),空間局部性應(yīng)為0,因?yàn)槊看挝覀冏x取一個(gè)塊的時(shí)候,我們只訪問(wèn)一個(gè)元素。這種情況下,我們會(huì)看到最大的缺失懲罰。

總之,如果數(shù)組比緩存大小更小,或步長(zhǎng)小于塊的大小,我們認(rèn)為會(huì)有良好的緩存性能。如果數(shù)組大于緩存大小,并且步長(zhǎng)較大時(shí),性能只會(huì)下降。

在圖7.1中,只要數(shù)組小于2 ** 22字節(jié),緩存性能對(duì)于所有步長(zhǎng)都很好。我們可以推測(cè)緩存大小近似4MiB。實(shí)際上,根據(jù)規(guī)范應(yīng)該是3MiB。

當(dāng)步長(zhǎng)為8、16或32B時(shí),緩存性能良好。在64B時(shí)開(kāi)始下降,對(duì)于更大的步長(zhǎng),平均缺失懲罰約為9ns。我們可以推斷出塊大小為128B。

許多處理器都使用了“多級(jí)緩存”,它包含一個(gè)小型快速的緩存,和一個(gè)大型慢速的緩存。這個(gè)例子中,當(dāng)數(shù)組大小大于2 ** 14B時(shí),缺失懲罰似乎增長(zhǎng)了一點(diǎn)。所以這個(gè)處理器可能也擁有一個(gè)訪問(wèn)時(shí)間小于1ns的16KB緩存。

7.5 緩存友好的編程

內(nèi)存的緩存功能由硬件實(shí)現(xiàn),所以多數(shù)情況下程序員都不需要知道太多關(guān)于它的東西。但是如果你知道緩存如何工作,你就可以編寫(xiě)更有效利用它們的程序。

例如,如果你在處理一個(gè)大型數(shù)組,只遍歷數(shù)組一次,在每個(gè)元素上執(zhí)行多個(gè)操作,可能比遍歷數(shù)組多次要快。

如果你處理二維數(shù)組,它以行數(shù)組的形式儲(chǔ)存。如果你需要遍歷元素,按行遍歷并且步長(zhǎng)為元素大小會(huì)比按列遍歷并且步長(zhǎng)為行的大小更快。

鏈表數(shù)據(jù)結(jié)構(gòu)并不總具有空間局部性,因?yàn)楣?jié)點(diǎn)在內(nèi)存中并不一定是連續(xù)的。但是如果你同時(shí)分配了很多個(gè)節(jié)點(diǎn),它們?cè)诙阎型ǔ7峙涞揭黄稹;蛘撸绻阋淮畏峙淞艘粋€(gè)節(jié)點(diǎn)數(shù)組,你應(yīng)該知道它們是連續(xù)的,這樣會(huì)更好。

類(lèi)似歸并排序的遞歸策略通常具有良好的緩存行為,因?yàn)樗鼈儗⒋髷?shù)組劃分為小片段,之后處理這些小片段。有時(shí)這些算法可以調(diào)優(yōu)來(lái)利用緩存行為。

對(duì)于那些性能至關(guān)重要的應(yīng)用,可以設(shè)計(jì)適配緩存大小、塊大小以及其它硬件特征的算法。像這樣的算法叫做“緩存感知”。緩存感知算法的明顯缺點(diǎn)就是它們硬件特定的。

7.6 存儲(chǔ)器層次結(jié)構(gòu)

在這一章的幾個(gè)位置上,你可能會(huì)有一個(gè)問(wèn)題:“如果緩存比主存快得多,那為什么不使用一大塊緩存,然后把主存扔掉呢?”

在沒(méi)有深入計(jì)算機(jī)體系結(jié)構(gòu)之前,可以給出兩個(gè)原因:電子和經(jīng)濟(jì)學(xué)上的。緩存很快是由于它們很小,并且離CPU很近,這可以減少由于電容造成的延遲和信號(hào)傳播。如果你把緩存做得很大,它就變得很慢。

另外,緩存占據(jù)處理器芯片的空間,更大的處理器會(huì)更貴。主存通常使用動(dòng)態(tài)隨機(jī)訪問(wèn)內(nèi)存(DRAM),每位上只有一個(gè)晶體管和一個(gè)電容,所以它可以將更多內(nèi)存打包在同一空間上。但是這種實(shí)現(xiàn)內(nèi)存的方法要比緩存實(shí)現(xiàn)的方式更慢。

同時(shí)主存通常包裝在雙列直插式內(nèi)存模塊(DIMM)中,它至少包含16個(gè)芯片。幾個(gè)小型芯片比一個(gè)大型芯片更便宜。

速度、大小和成本之間的權(quán)衡是緩存的根本原因。如果有既快又大還便宜的內(nèi)存技術(shù),我們就不需要其它東西了。

與內(nèi)存相同的原則也適用于存儲(chǔ)器。閃存非常快,但是它們比硬盤(pán)更貴,所以它們就更小。磁帶比硬盤(pán)更慢,但是它們可以?xún)?chǔ)存更多東西,相對(duì)較便宜。

下面的表格展示了每種技術(shù)通常的訪問(wèn)時(shí)間、大小和成本。

設(shè)備 訪問(wèn)時(shí)間 通常大小 成本
寄存器 0.5 ns 256 B ?
緩存 1 ns 2 MiB ?
DRAM 10 ns 4 GiB $10 / GiB
SSD 10 μs 100 GiB $1 / GiB
HDD 5 ms 500 GiB $0.25 / GiB
磁帶 minutes 1–2 TiB $0.02 / GiB

寄存器的數(shù)量和大小取決于體系結(jié)構(gòu)的細(xì)節(jié)。當(dāng)前的計(jì)算機(jī)擁有32個(gè)通用寄存器,每個(gè)都可以?xún)?chǔ)存一個(gè)“字”。在32位計(jì)算機(jī)上,一個(gè)字為32位,4個(gè)字節(jié)。64位計(jì)算機(jī)上,一個(gè)字為64位,8個(gè)字節(jié)。所以寄存器文件的總?cè)萘渴?00~300字節(jié)。

寄存器和緩存的成本很難衡量。它們包含在芯片的成本中。但是顧客并不能直接了解到其成本。

對(duì)于表中的其它數(shù)據(jù),我觀察了計(jì)算機(jī)在線(xiàn)商店中,通常待售的計(jì)算機(jī)硬件規(guī)格。截至你讀到這里為止,這些數(shù)據(jù)應(yīng)該已經(jīng)過(guò)時(shí)了,但是它們可以帶給你在過(guò)去的某個(gè)時(shí)間上,一些關(guān)于性能和成本差距的概念。

這些技術(shù)構(gòu)成了“存儲(chǔ)器體系結(jié)構(gòu)”。結(jié)構(gòu)中每一級(jí)都比它上一級(jí)大而緩慢。某種意義上,每一級(jí)都作為其下一級(jí)的緩存。 你可以認(rèn)為主存是持久化儲(chǔ)存在SSD或HDD上的程序和數(shù)據(jù)的緩存。并且如果你需要處理磁帶上非常大的數(shù)據(jù)集,你可以用硬盤(pán)緩存一部分?jǐn)?shù)據(jù)。

7.7 緩存策略

存儲(chǔ)器層次結(jié)構(gòu)展示了一個(gè)考慮到緩存的框架。在結(jié)構(gòu)的每一級(jí)中,我們都需要強(qiáng)調(diào)四個(gè)緩存的基本問(wèn)題:

  • 誰(shuí)在層次結(jié)構(gòu)中上移或下移數(shù)據(jù)?在結(jié)構(gòu)的頂端,寄存器通常由編譯器完成分配。CPU上的硬件管理內(nèi)存的緩存。在執(zhí)行程序或打開(kāi)文件的過(guò)程中,用戶(hù)可以將存儲(chǔ)器上的文件隱式移動(dòng)到內(nèi)存中。但是操作系統(tǒng)也會(huì)將數(shù)據(jù)從內(nèi)存移動(dòng)回存儲(chǔ)器。在層次結(jié)構(gòu)的底端,管理員在磁帶和磁盤(pán)之間顯式移動(dòng)數(shù)據(jù)。
  • 移動(dòng)了什么東西?通常,在結(jié)構(gòu)頂端的塊大小比底端要小。在內(nèi)存的緩存中,通常塊大小為128B。內(nèi)存中的頁(yè)面可能為4KiB,但是當(dāng)操作系統(tǒng)從磁盤(pán)讀取文件時(shí),它可能會(huì)一次讀10或100個(gè)塊。
  • 數(shù)據(jù)什么時(shí)候會(huì)移動(dòng)?在多數(shù)的基本的緩存中,數(shù)據(jù)在首次使用時(shí)會(huì)移到緩存。但是許多緩存使用一些“預(yù)取”機(jī)制,也就是說(shuō)數(shù)據(jù)會(huì)在顯式請(qǐng)求之前加載。我們已經(jīng)見(jiàn)過(guò)預(yù)取的一些形式了:在請(qǐng)求其一部分時(shí)加載整個(gè)塊。
  • 緩存中數(shù)據(jù)在什么地方?當(dāng)緩存填滿(mǎn)之后,我們不把一些東西扔掉就不可能放進(jìn)一些東西。理想化來(lái)說(shuō),我們打算保留將要用到的數(shù)據(jù),并替換掉不會(huì)用到的數(shù)據(jù)。

這些問(wèn)題的答案構(gòu)成了“緩存策略”。在靠近頂端的位置,緩存策略?xún)A向于更簡(jiǎn)單,因?yàn)樗鼈兎浅?欤⒂捎布?shí)現(xiàn)。在靠近底端的位置,會(huì)有更多做決定的次數(shù),并且設(shè)計(jì)良好的策略會(huì)有很大不同。

多數(shù)緩存策略基于歷史重演的原則,如果我們有最近時(shí)期的信息,我們可以用它來(lái)預(yù)測(cè)不久的將來(lái)。例如,如果一塊數(shù)據(jù)在最近使用了,我們認(rèn)為它不久之后會(huì)再次使用。這個(gè)原則展示了一種叫做“最近最少使用”的策略,即LRU。它從緩存中移除最久未使用的數(shù)據(jù)塊。更多話(huà)題請(qǐng)見(jiàn)緩存算法的維基百科

7.8 頁(yè)面調(diào)度

在帶有虛擬內(nèi)存的系統(tǒng)中,操作系統(tǒng)可以將頁(yè)面在存儲(chǔ)器和內(nèi)存之間移動(dòng)。像我在6.2中提到的那樣,這種機(jī)制叫做“頁(yè)面調(diào)度”,或者簡(jiǎn)單來(lái)說(shuō)叫“換頁(yè)”。

下面是工作流程:

  1. 進(jìn)程A調(diào)用malloc來(lái)分配頁(yè)面。如果堆中沒(méi)有所請(qǐng)求大小的空閑空間,malloc會(huì)調(diào)用sbrk向操作系統(tǒng)請(qǐng)求更多內(nèi)存。
  2. 如果物理內(nèi)存中有空閑頁(yè),操作系統(tǒng)會(huì)將其加載到進(jìn)程A的頁(yè)表,創(chuàng)建新的虛擬內(nèi)存有效范圍。
  3. 如果沒(méi)有空閑頁(yè)面,調(diào)度系統(tǒng)會(huì)選擇一個(gè)屬于進(jìn)程B的“犧牲頁(yè)面”。它將頁(yè)面內(nèi)容從內(nèi)存復(fù)制到磁盤(pán),之后修改進(jìn)程B的頁(yè)表來(lái)表示這個(gè)頁(yè)面“被換出”了。
  4. 一旦進(jìn)程B的數(shù)據(jù)被寫(xiě)入,頁(yè)面會(huì)重新分配給進(jìn)程A。為了防止進(jìn)程A讀取進(jìn)程B的數(shù)據(jù),頁(yè)面應(yīng)被清空。
  5. 此時(shí)sbrk的調(diào)用可以返回了,向malloc提供堆區(qū)額外的空間。之后malloc分配所請(qǐng)求的內(nèi)存并返回。進(jìn)程A可以繼續(xù)執(zhí)行。
  6. 當(dāng)進(jìn)程A執(zhí)行完畢,或中斷后,調(diào)度器可能會(huì)讓進(jìn)程B繼續(xù)執(zhí)行。當(dāng)它訪問(wèn)到被換出的頁(yè)面時(shí),內(nèi)存管理器單元注意到這個(gè)頁(yè)面是“無(wú)效”的,并且會(huì)觸發(fā)中斷。
  7. 當(dāng)操作系統(tǒng)處理中斷時(shí),它會(huì)看到頁(yè)面被換出了,于是它將頁(yè)面從磁盤(pán)傳送到內(nèi)存。
  8. 一旦頁(yè)面被換入之后,進(jìn)程B可以繼續(xù)執(zhí)行。

當(dāng)頁(yè)面調(diào)度工作良好時(shí),它可以極大提升物理內(nèi)存的利用水平,允許更多進(jìn)程在更少的空間內(nèi)執(zhí)行。下面是它的原因:

  • 大多數(shù)進(jìn)程不會(huì)用完所分配的內(nèi)存。text段的許多部分都永遠(yuǎn)不會(huì)執(zhí)行,或者執(zhí)行一次就再也不用了。這些頁(yè)面可以被換出而不會(huì)引發(fā)任何問(wèn)題。
  • 如果程序泄露了內(nèi)存,它可能會(huì)丟掉所分配的空間,并且永遠(yuǎn)不會(huì)使用它了。通過(guò)將這些頁(yè)面換出,操作系統(tǒng)可以有效填補(bǔ)泄露。
  • 在多數(shù)系統(tǒng)中,有些進(jìn)程像守護(hù)進(jìn)程那樣,多數(shù)時(shí)間下都是閑置的,只在特定場(chǎng)合被“喚醒”來(lái)響應(yīng)事件。當(dāng)它們閑置時(shí),這些進(jìn)程可以被換出。
  • 另外,可能有許多進(jìn)程運(yùn)行同一個(gè)程序。這些進(jìn)程可以共享相同的text段,避免在物理內(nèi)存中保留多個(gè)副本。

如果你增加分配給所有進(jìn)程的總內(nèi)存,它可以超出物理內(nèi)存的大小,并且系統(tǒng)仍舊運(yùn)行良好。

在某種程度上是這樣。

當(dāng)進(jìn)程訪問(wèn)被換出的頁(yè)面時(shí),就需要從磁盤(pán)獲取數(shù)據(jù),這會(huì)花費(fèi)幾個(gè)毫秒。這一延遲通常很明顯。如果你將一個(gè)窗口閑置一段時(shí)間,之后切換回它,它可能會(huì)執(zhí)行得比較慢,并且你可能在頁(yè)面換入時(shí)會(huì)聽(tīng)到磁盤(pán)工作的聲音。

像這樣偶爾的延遲可能還可以接受,但是如果你擁有很多占據(jù)大量空間的進(jìn)程,它們就會(huì)相互影響。當(dāng)進(jìn)程A運(yùn)行時(shí),它會(huì)收回進(jìn)程B所需的頁(yè)面,之后進(jìn)程B運(yùn)行時(shí),它又會(huì)收回進(jìn)程A所需的頁(yè)面。當(dāng)這種情況發(fā)生時(shí),兩個(gè)進(jìn)程都會(huì)執(zhí)行緩慢,系統(tǒng)會(huì)變得無(wú)法響應(yīng)。這種我們不想看到的場(chǎng)景叫做“顛簸”。

理論上,操作系統(tǒng)應(yīng)該通過(guò)檢測(cè)調(diào)度和塊上的增長(zhǎng)來(lái)避免顛簸,或者殺掉進(jìn)程直到系統(tǒng)能夠再次響應(yīng)。但是在我看來(lái),多數(shù)系統(tǒng)都沒(méi)有這樣做,或者做得不好。它們通常讓用戶(hù)去限制物理內(nèi)存的使用,或者嘗試在顛簸發(fā)生時(shí)恢復(fù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,619評(píng)論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,155評(píng)論 3 425
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,635評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,539評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,255評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,646評(píng)論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,655評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,838評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,399評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,146評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,338評(píng)論 1 372
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,893評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,565評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,983評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,257評(píng)論 1 292
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,059評(píng)論 3 397
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,296評(píng)論 2 376

推薦閱讀更多精彩內(nèi)容