快在當(dāng)下互聯(lián)網(wǎng)中無疑是最重要的事情了。老板們強(qiáng)調(diào)“一定要快,快速做出新產(chǎn)品,快速迭代,快速占領(lǐng)市場”。
技術(shù)也不斷快速的迭代,宏觀的5G、大數(shù)據(jù)、人工智能、云計(jì)算、物聯(lián)網(wǎng)等技術(shù)使得用戶更快的找到內(nèi)容,微觀的Docker容器技術(shù)、Kubernetes技術(shù)、負(fù)載均衡技術(shù)、微服務(wù)架構(gòu)等技術(shù)使得程序更快速的實(shí)現(xiàn)部署。
一切都像汽車上了高速公路一樣,開始飛速的運(yùn)轉(zhuǎn)起來。那么最根本的內(nèi)容—程序也必須快起來啊。如果底層的程序運(yùn)行耗時,那么堆疊在上面的架構(gòu)設(shè)計(jì)、容器部署、彈性伸縮也是巧婦難為無米之炊,就像汽車引擎不快,即使有了技術(shù)高超的駕駛員、行走在高速公路、燃燒最好的汽油,也快不起來。所以代碼的運(yùn)行快速特別重要,今天小編給大家?guī)淼谋闶莄ache緩存~
今天分四個模塊給大家來深入淺出cache,第一模塊是代碼塊案例;第二模塊是程序如何運(yùn)行;第三模塊是cache原理剖析;第四模塊是回歸案例進(jìn)行講解。
在介紹緩存之前,我們從一串代碼分析開始,對于二維數(shù)組的賦值,有兩種寫法。
第一種寫法是形成了一個100*10的二維數(shù)組,第二種寫法是10*100的二維數(shù)組。
在教科書中的教導(dǎo)使得大部分情況下我們都會使用第一種寫法,實(shí)質(zhì)是因?yàn)樵诔绦蜻\(yùn)行效率中,第一種寫法運(yùn)行效率高,背后的原因就是cache緩存了~
在計(jì)算機(jī)程序執(zhí)行時所有的指令和數(shù)據(jù)全從存儲器中進(jìn)行獲取,因此在了解cache的工作原理之前,我們先來介紹一下計(jì)算機(jī)中數(shù)據(jù)存儲的方式。
在計(jì)算機(jī)內(nèi)部包含有如下存儲器,從外到內(nèi)依次是硬盤、主存(如RAM、ROM)、cache、寄存器,硬盤的話就是平時我們使用的C盤、D盤,可存儲文件、視頻等,而主存&cache&寄存器用以存儲各類程序及處理的數(shù)據(jù)。
隨著技術(shù)的發(fā)展,各處理器的速度和性能得到了飛躍的提升,作為拍檔的存儲器速度也必須提升起來,以保持整體系統(tǒng)的平衡。但實(shí)際上存儲器在容量尤其是訪問延時方面的性能增長越來越跟不上處理器性能發(fā)展的需要,因此在計(jì)算機(jī)內(nèi)部采用層次化的存儲器體系結(jié)構(gòu)用以減少二者的差距。
通過剛剛的講解,我們已經(jīng)知道程序是運(yùn)行在主存的RAM當(dāng)中。那么一個程序是如何運(yùn)行起來的呢?通常一個程序就是一個進(jìn)程,當(dāng)這個進(jìn)程起來的時候,首先從flash設(shè)備中把可執(zhí)行程序加載在主存中進(jìn)行運(yùn)行,當(dāng)需要對變量進(jìn)行計(jì)算時,在CPU內(nèi)部的通用寄存器將某地址ad1的變量進(jìn)行計(jì)算。
1)CPU從主存中讀取地址ad1的數(shù)據(jù)到內(nèi)部通用寄存器X0;
如圖所示,寄存器與主存的運(yùn)行速度相差10倍,也就是說CPU在寄存器獲取數(shù)據(jù)后要等待10倍的時間才能返回到主存,這樣的程序執(zhí)行速度太慢了。
因此cache出現(xiàn)了,在寄存器和主存之前增加了cache,CPU需要對存儲器進(jìn)行讀寫操作時,先訪問cache,如果cache中不存在操作數(shù)據(jù)時,再從主存中獲取,如果主存不存在,再從硬盤中獲取,在獲取了數(shù)據(jù)的同時也把該數(shù)據(jù)放一份在cache中,用以以后的執(zhí)行獲取數(shù)據(jù)。cache的運(yùn)行時間為2ns,在整體運(yùn)行效率上改善了8倍,提升了程序的運(yùn)行速度。
cache即緩存,將程序執(zhí)行常用的數(shù)據(jù)放在cache中,CPU和主存之間直接數(shù)據(jù)傳輸?shù)姆绞骄妥兂闪薈PU和cache之間直接數(shù)據(jù)傳輸,cache則負(fù)責(zé)與主存之間的數(shù)據(jù)傳輸。
如果cache中有CPU需要的數(shù)據(jù),這時候成為命中hit,但是當(dāng)cache中沒有想要的數(shù)據(jù)時(即Miss),CPU仍然需要等待從主存中獲取數(shù)據(jù),為了提升性能和命中率,在計(jì)算機(jī)系統(tǒng)中依次引入了多級cachememory、直接映射緩存、兩路組相連緩存、全相連緩存。
所謂多級緩存,即在CPU寄存器和主存中引入L1cache、L2cache、L3cache,等級越高容量越大速度也越慢.當(dāng)CPU試圖獲取某地址的數(shù)據(jù)時,首先從L1cache中查詢,如果命中則返回寄存器,如果不命中則繼續(xù)從L2cache中進(jìn)行查詢,如果命中,該數(shù)據(jù)在世界傳遞給CPU的同時也會傳遞給L1cache,如果不命中,則繼續(xù)往L3cache、主存查詢。整個的運(yùn)轉(zhuǎn)流程如圖所示。
所謂直接映射緩存,則是為了方便cache和主存之間交換信息,將cache和主存空間劃分為相等的區(qū)域,在主存中的劃分區(qū)域大小劃分為塊block,cache中存放主存塊的區(qū)域稱作行l(wèi)ine,塊block與行l(wèi)ine一一對應(yīng)。比如cache的大小是64bytes,將其劃分為64塊,那么cacheline就是1字節(jié),只能傳輸一字節(jié)的信息。
CPU獲取數(shù)據(jù)的方法仍然和多級緩存的方式一樣,先從cache中獲取,如果沒有再從主存中獲取,所不同的是將主存中的信息按塊復(fù)制到cache中,整體流程如下圖所示。
所謂全相連映射,則是將每個主存塊映射到cache的任意行中,組相連映射是將每個主存塊映射到cache固定組的任意行中,直接相連映射則是將每個主存塊映射到cache的固定行中。目前我們常見的CPU采用的是組相連映射方式,獲得了性能的提升和較低的硬件實(shí)現(xiàn)難度。
確定好cache的硬件設(shè)計(jì)方式后,下一步是cache的數(shù)據(jù)更新策略和分配策略。更新策略指的是什么情況下進(jìn)行數(shù)據(jù)的更新,包含寫直通和寫回兩種策略,寫直通策略就是CPU執(zhí)行命令并且在緩存中命中時,更新cache中的數(shù)據(jù)和主存的數(shù)據(jù),保障cache和主存的數(shù)據(jù)始終一致,寫回策略則是CPU執(zhí)行命令并且在緩存中命中時只更新cache中的數(shù)據(jù),此時cache和主存的數(shù)據(jù)可能不一致。
分配策略指的是什么情況下進(jìn)行數(shù)據(jù)的分配,包含讀分配和寫分配兩種策略,讀分配就是CPU去讀數(shù)據(jù)時發(fā)生數(shù)據(jù)缺失,分配一個cacheline緩存從主存讀取數(shù)據(jù),寫分配就是CPU執(zhí)行寫操作cache時發(fā)生數(shù)據(jù)缺失,從主存中加載數(shù)據(jù)到cacheline中,再更新cacheline的數(shù)據(jù)。
在part1中的關(guān)于二維數(shù)據(jù)的計(jì)算有兩種方式,寫法2效率高于寫法1的原因就在于緩存命中率,因?yàn)閷懛?的賦值操作緩存命中率更高一些,所以花的時間更少。對于初接觸C語言的同學(xué)來說,可能以為二維數(shù)組在內(nèi)存中的分布是這樣的:
然而由于計(jì)算機(jī)中的內(nèi)存地址是一維的,所以即使是二維數(shù)組也按一維進(jìn)行排列:
在寫法2中數(shù)據(jù)的獲取方式是連續(xù)的
而寫法1中數(shù)據(jù)獲取的方式卻是跳躍的,
寫法2獲取100個數(shù)據(jù)的時間如果需要100ns的話,那寫法1而獲取數(shù)據(jù)的時間則是10*100=1000ns(需要跳躍10次),因此寫法2的速率高于寫法1。
那么為什么二者的效率不一樣呢?原因就在于緩存命中率不一樣。在part3中我們提到CPU采用組相連的硬件設(shè)計(jì),獲取數(shù)據(jù)時先訪問緩存數(shù)據(jù),再訪問主存數(shù)據(jù)。
一般來說cache的大小是64字節(jié),在寫法1和寫法2中數(shù)據(jù)的類型都是int型,4字節(jié)數(shù)據(jù),因此對于64字節(jié)的cache中可以緩存16個連續(xù)的整數(shù),CPU直接訪問cache的這16個數(shù)當(dāng)然比從內(nèi)存里訪問更快。在寫法1中雖然加載了16個整數(shù),但數(shù)卻是跳躍的,相當(dāng)于需要重復(fù)10*1000次,相比寫法的方式需要10倍的內(nèi)存訪問次數(shù),此時CPU只能等著內(nèi)存操作完成再獲取數(shù)據(jù),因此寫法2的速率比較快,寫法1比較慢。
總的來說,緩存的出現(xiàn)加快了CPU執(zhí)行指令的速度,從而加快了程序的執(zhí)行速度,最后加快了應(yīng)用的響應(yīng)速度,給到我們更快更好的用戶體驗(yàn)。
這整個過程其實(shí)就和租房子差不多,現(xiàn)在在一線城市打拼的年輕人們一般都會租房子,租客就像CPU,找房東直租就像從主存直接獲取數(shù)據(jù)一樣,因?yàn)榉繓|有自己的工作要做、也有其它的事情要忙,房子的出租對于他來說不是最重要的事情,所以他反饋就不那么技術(shù),所以導(dǎo)致租客等待時間比較長。
中介的出現(xiàn)就像cache的出現(xiàn)一樣,拯救了CPU,因?yàn)榉课莩鲎獬蔀榱酥薪榈墓ぷ?,所以他會快速的從各處房東那里獲取信息,當(dāng)租客想在西二旗租房時,直接找中介獲取該處的房源信息直接看房即可,如果在西二旗沒有合適的房源或沒有房源,中介會尋找合適的房東進(jìn)行房源補(bǔ)充,同時反饋給租客,這就像在cache沒有命中數(shù)據(jù)時直接從主存中獲取并同步給緩存的原理差不多。
通過上述講解,相信你已經(jīng)清楚了程序是如何運(yùn)行的?回歸文章中最初提到的“快速迭代、快速出新產(chǎn)品、快速占領(lǐng)市場”的正確打開方式便是高效運(yùn)行的代碼+化整為零的微服務(wù)架構(gòu)設(shè)計(jì)+快速部署的容器技術(shù)+彈性伸縮的云計(jì)算+智能分析的大數(shù)據(jù)人工智能+5G~