溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

關(guān)于JavaScript的的高速緩存未命中分析過程是怎樣的

發(fā)布時間:2021-09-30 16:08:44 來源:億速云 閱讀:155 作者:柒染 欄目:web開發(fā)

關(guān)于JavaScript的的高速緩存未命中分析過程是怎樣的,很多新手對此不是很清楚,為了幫助大家解決這個難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來學(xué)習(xí)下,希望你能有所收獲。

介紹

JavaScript是一種非常高級的語言,在使用JavaScript開發(fā)的時候不必對存儲器中的數(shù)據(jù)存儲方式作過多的考慮。在本文中,我們將探討數(shù)據(jù)如何存儲在內(nèi)存中,以及JavaScript中分發(fā)和訪問數(shù)據(jù)的方式將如何影響CPU和內(nèi)存的性能。

浪漫三角

當(dāng)計算機(jī)需要進(jìn)行一些計算任務(wù)時,計算機(jī)處理單元(CPU)需要數(shù)據(jù)進(jìn)行處理,因此,根據(jù)手中的任務(wù),它將發(fā)送請求到內(nèi)存以通過總線獲取待處理的數(shù)據(jù),就像下面這樣:

這就是我們的浪漫三角

CPU->總線->內(nèi)存

浪漫三角需要第四個元素來保持穩(wěn)定

由于CPU比內(nèi)存快得多,因此從CPU->總線->內(nèi)存->總線->CPU這樣的處理方式就浪費了很多計算時間,因為查找內(nèi)存時,CPU處于空閑狀態(tài)而無法執(zhí)行其他操作。

緩存的出現(xiàn)有效的緩解了這個問題。在本文中我們不會詳細(xì)討論緩存的技術(shù)細(xì)節(jié)和類型,我們只需要知道緩存可以作為CPU的一個內(nèi)部存儲空間。

當(dāng)CPU接收到要運(yùn)行的命令時,它將首先在高速緩存中搜索目標(biāo)數(shù)據(jù),如果沒有搜索到目標(biāo)數(shù)據(jù),它再通過總線發(fā)起請求。

然后,總線將請求的數(shù)據(jù)加上一部分內(nèi)存,將其存儲在高速緩存中以供CPU快速調(diào)用。

這樣一來,CPU就能夠有效的處理數(shù)據(jù),而不會浪費時間等待內(nèi)存返回。

高速緩存的引用也可能導(dǎo)致新的問題

基于上面的架構(gòu),我們在處理大量數(shù)據(jù)時可能會出現(xiàn)一種名為”高速緩存未命中”的錯誤。

高速緩存未命中意味著在計算期間,CPU發(fā)現(xiàn)高速緩存中沒有必要的數(shù)據(jù),因此需要通過常規(guī)通道(即已知的慢速存儲器)來請求此數(shù)據(jù)。

上圖是一個很好的實例,在處理組中數(shù)據(jù)是,由于計算的數(shù)據(jù)超出了緩存限制的數(shù)據(jù),就導(dǎo)致了緩存未命中。

可是這跟我作為JavaScript程序員有什么關(guān)系呢?

好問題,大多數(shù)情況下,我們JavaScript開發(fā)人員不必關(guān)心這個問題。但是隨著越來越多的數(shù)據(jù)涌入Node.js服務(wù)器甚至富客戶端,所以當(dāng)使用JavaScript遍歷大型數(shù)據(jù)集時就容易遇見緩存未命中的問題。

一個經(jīng)典的例子

接下來讓我們以一個例子作為說明。

這是一個叫做Boom的類:

class Boom {   constructor(id) {     this.id = id;   }   setPosition(x, y) {     this.x = x;     this.y = y;   } }

此類(Boom)僅具有3個屬性:id,x和y。

現(xiàn)在,讓我們創(chuàng)建一個填充x和y的方法。

讓我們構(gòu)建設(shè)置:

const ROWS = 1000; const COLS = 1000; const repeats = 100; const arr = new Array(ROWS * COLS).fill(0).map((a, i) => new Boom(i));

現(xiàn)在,我們將在一種方法中使用此設(shè)置:

function localAccess() {   for (let i = 0; i < ROWS; i++) {     for (let j = 0; j < COLS; j++) {       arr[i * ROWS + j].x = 0;     }   } }

本地訪問的作用是線性遍歷數(shù)組并將x設(shè)置為0。

如果我們重復(fù)執(zhí)行此功能100次(請查看設(shè)置中的重復(fù)常數(shù)),則可以測量運(yùn)行時間:

function repeat(cb, type) {   console.log(`%c Started data ${type}`, 'color: red');   const start = performance.now();   for (let i = 0; i < repeats; i++) {     cb();   }   const end = performance.now();   console.log('Finished data locality test run in ', ((end?-?start) / 1000).toFixed(4), ' seconds');   return end?-?start; } repeat(localAccess, 'Local');

日志輸出是這樣的:

丟失緩存要付出的代價

現(xiàn)在,根據(jù)上面的了解,如果我們處理迭代過程中距離較遠(yuǎn)的數(shù)據(jù),則會導(dǎo)致緩存丟失。遠(yuǎn)處的數(shù)據(jù)是不在相鄰索引中的數(shù)據(jù),如下所示:

function farAccess() {   for (let i = 0; i < COLS; i++) {     for (let j = 0; j < ROWS; j++) {       arr[j * ROWS + i].x = 0;     }   } }

在這里發(fā)生的是,在每次迭代中,我們都處理上次迭代距ROWS的索引。因此,如果ROWS為1000(在我們的例子中),我們將得到以下迭代:[0,1000,2000,&hellip;,1,1001,2001,&hellip;]。

讓我們將其添加到速度測試中:

repeat(localAccess, 'Local'); setTimeout(() => {   repeat(farAccess, 'Non Local'); }, 2000);

這是最終結(jié)果:

非本地迭代速度幾乎慢了4倍。隨著數(shù)據(jù)量的增加,這種差異將越來越大。發(fā)生這種情況的原因是由于高速緩存未命中,CPU處于空閑狀態(tài)。

那么您要付出的代價是什么?這完全取決于您的數(shù)據(jù)大小。

好吧,我發(fā)誓我永遠(yuǎn)不會那樣做!

您通常可能不這么認(rèn)為,但在某些情況下,您可能會希望使用非線性(例如1,2,3,4,5)或非偶然性。比如( for (let i = 0; i <  n; i+=1000))

例如,您從服務(wù)或數(shù)據(jù)庫中獲取數(shù)據(jù),并需要通過某種復(fù)雜的邏輯對數(shù)據(jù)進(jìn)行排序或過濾。這可能導(dǎo)致訪問數(shù)據(jù)的方式與farAccess函數(shù)中顯示的方式類似。

如下所示:

查看上圖,我們看到了存儲在內(nèi)存中的數(shù)據(jù)(頂部灰色條)。在下面,我們看到了當(dāng)數(shù)據(jù)從服務(wù)器到達(dá)時創(chuàng)建的數(shù)組。最后,我們看到排序后的數(shù)組,其中包含對存儲在內(nèi)存中各個位置的對象的引用。

這樣,對排序后的數(shù)組進(jìn)行迭代可能會導(dǎo)致在上面的示例中看到的多個緩存未命中。

請注意,此示例適用于小型陣列。高速緩存未命中與更大的數(shù)據(jù)有關(guān)。

在當(dāng)今世界中,您需要在前端使用精美的動畫,并且可以在后端(無服務(wù)器或其他服務(wù)器)中為CPU的每毫秒時間計費(這很關(guān)鍵)。

不好了!都沒了!!!

并不是,有多種解決方案可以解決此問題,現(xiàn)在您已經(jīng)知道造成性能下降的原因,您可以自己考慮解決方案。比如只需要將處理在一起的數(shù)據(jù)更緊密地存儲在內(nèi)存中。

這種技術(shù)稱為數(shù)據(jù)局部性設(shè)計模式。

讓我們繼續(xù)我們的例子。假設(shè)在我們的應(yīng)用程序中,最常見的過程是使用farAccess函數(shù)中顯示的邏輯來遍歷數(shù)據(jù)。我們希望對數(shù)據(jù)進(jìn)行優(yōu)化,以使其在最常見的for循環(huán)中快速運(yùn)行。我們將像這樣排列相同的數(shù)據(jù):

const diffArr = new Array(ROWS * COLS).fill(0); for (let col = 0; col < COLS; col++) {   for (let row = 0; row < ROWS; row++) {     diffArr[row * ROWS + col] = arr[col * COLS + row];   } }

所以現(xiàn)在在diffArr中,原始數(shù)組中索引為[0,1,2,&hellip;]的對象現(xiàn)在被設(shè)置為[0,1000,2000,&hellip;,1,1001,2001,&hellip;,2,  1002,2002,&hellip;]。數(shù)字表示對象的索引。這模擬了對數(shù)組進(jìn)行排序的方法,這是實現(xiàn)數(shù)據(jù)局部性設(shè)計模式的一種方法。

為了方便測試,我們將稍微更改farAccess函數(shù)以獲得一個自定義數(shù)組:

function farAccess(array) {   let data = arr;   if (array) {     data = array;   }   for (let i = 0; i < COLS; i++) {     for (let j = 0; j < ROWS; j++) {       data[j * ROWS + i].x = 0;     }   } }

現(xiàn)在將場景添加到我們的測試中:

repeat(localAccess, 'Local'); setTimeout(() => {   repeat(farAccess, 'Non Local')   setTimeout(() => {     repeat(() => farAccess(diffArr), 'Non Local Sorted')   }, 2000); }, 2000);

我們運(yùn)行它,我們得到:

我們已經(jīng)優(yōu)化了數(shù)據(jù),以適應(yīng)需要查看的更常見的方式。

看完上述內(nèi)容是否對您有幫助呢?如果還想對相關(guān)知識有進(jìn)一步的了解或閱讀更多相關(guān)文章,請關(guān)注億速云行業(yè)資訊頻道,感謝您對億速云的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI