在前面的文章你能估算出你的內(nèi)存的訪問延時(shí)嗎?中,我們對(duì)內(nèi)存的訪問延遲進(jìn)行了理論上的研究。那么今天我們實(shí)際編寫代碼進(jìn)行一下測(cè)試。 看一下實(shí)踐能否和理論匹配的上。
為了衡量各種情況下的內(nèi)存帶寬情況。我們用c寫了一個(gè)double類型的數(shù)組,然后用for循環(huán)對(duì)其進(jìn)行遍歷,然后統(tǒng)計(jì)其每次訪問內(nèi)存平均耗時(shí)情況。核心代碼如下:
/*
* 初始化一個(gè)指定大小的數(shù)組
* 通過對(duì)內(nèi)存大小不斷變化,逐步制造出一級(jí)、二級(jí)、三級(jí)溢出、最后穿透到內(nèi)存IO的case。
*/
void init_data(double *data, int n){
int i;
for (i = 0; i < n; i++) {
data[i] = i;
}
}
/*
* 對(duì)內(nèi)存以指定步長(zhǎng)執(zhí)行順序訪問
* 這塊測(cè)試代碼有個(gè)加法額外開銷。算術(shù)運(yùn)算是以CPU周期來計(jì)算的,比內(nèi)存周期要快。
* 所以本測(cè)試中忽略這個(gè)算術(shù)運(yùn)算帶來的額外開銷。
*/
void seque_access(int elems, int stride) {
int i;
double result = 0.0;
volatile double sink;
for (i = 0; i < elems; i += stride) {
result += data[i];
}
sink = result;
}
/*
* 對(duì)內(nèi)存執(zhí)行隨機(jī)訪問,其中random_index_arr是提前隨機(jī)好的數(shù)組下標(biāo)。
* 這實(shí)際比順序多了一次內(nèi)存IO,但由于對(duì)random_index_arr的訪問時(shí)順序的,而且數(shù)組要相對(duì)小很多。
* 所以它絕大部分情況下都能命中高速緩存,可以忽略這個(gè)小內(nèi)存IO對(duì)主測(cè)試過程的影響。
*/
void random_access(int* random_index_arr, int count) {
int i;
double result = 0.0;
volatile double sink;
for (i = 0; i < count; i++) {
result += data[*(random_index_arr+i)];
}
sink = result;
}
實(shí)驗(yàn)結(jié)果數(shù)據(jù)
注意,不同的機(jī)器的實(shí)驗(yàn)數(shù)據(jù)會(huì)略有差異。越新的機(jī)器數(shù)據(jù)表現(xiàn)越好,因?yàn)橛布?shù)不一樣,但是偏差不會(huì)太大。
case 1: 內(nèi)存2k,步長(zhǎng)從1到64
| 步長(zhǎng)(字節(jié)) | 延遲(ns) |
|---|---|
| 1 | 1.28 |
| 9 | 1.28 |
| 17 | 1.33 |
| 25 | 1.35 |
| 33 | 1.30 |
| 41 | 1.41 |
| 49 | 1.45 |
| 57 | 1.40 |
| 64 | 1.27 |
結(jié)論:內(nèi)存足夠小的時(shí)候,一級(jí)高速緩存完全能裝的下,因此絕大部分的請(qǐng)求都是一級(jí)緩存IO,內(nèi)存IO并沒有怎么進(jìn)行。所以無論步長(zhǎng)怎么變化,延遲基本都在1ns左右。
case2:內(nèi)存從8k到64M,步長(zhǎng)固定8
| 內(nèi)存大小 | 延遲(ns) |
|---|---|
| 8k | 1.32 |
| 32k | 1.27 |
| 64k | 1.73 |
| 256k | 2.03 |
| 512k | 2.62 |
| 2m | 2.62 |
| 8m | 2.88 |
| 16m | 5.17 |
| 64m | 5.84 |
結(jié)論:內(nèi)存越來越大,CPU的高速緩存逐漸hold不住了,越來越多的請(qǐng)求穿透到了內(nèi)存。延遲已經(jīng)逐步接近真實(shí)的內(nèi)存IO了
case3:內(nèi)存從8k到64M,步長(zhǎng)固定64
| 內(nèi)存大小 | 延遲(ns) |
|---|---|
| 8k | 1.25 |
| 32k | 1.25 |
| 64k | 1.74 |
| 256k | 2.03 |
| 512k | 2.47 |
| 2m | 2.47 |
| 8m | 3.29 |
| 16m | 7.73 |
| 64m | 8.89 |
結(jié)論:步長(zhǎng)從case2的8增加到了本case的64以后,空間局部性進(jìn)一步被打亂。內(nèi)存在2m以下的時(shí)候,延遲沒有太大變化。因?yàn)殡m然局部性亂,但CPU緩存都能hold住,穿透到內(nèi)存的情況不多。但當(dāng)內(nèi)存編程8m或者64m的情況下,高速緩存命中率就會(huì)降低,因此平均延遲也增加到了9ns左右。這個(gè)數(shù)字其實(shí)就是內(nèi)存在順序IO時(shí)的大概耗時(shí)。
case4 內(nèi)存8k到64m,徹底隨機(jī)訪問
| 內(nèi)存大小 | 延遲(ns) |
|---|---|
| 8k | 2.40 |
| 32k | 2.40 |
| 64k | 2.40 |
| 256k | 2.40 |
| 512k | 4.80 |
| 2m | 4.80 |
| 8m | 19.20 |
| 16m | 24.0 |
| 64m | 38.40 |
結(jié)論:當(dāng)徹底隨機(jī)訪問以后,不光是穿透到內(nèi)存的IO變多了,而且穿透到內(nèi)存的IO也增大了行地址變化的概率。也就是說內(nèi)存IO也不能以高效的順序IO進(jìn)行了,更多的請(qǐng)求進(jìn)行的是行列地址都變了的隨機(jī)IO。因此64m的情況下,內(nèi)存IO延遲增加到了將近40ns
最終結(jié)論
- 內(nèi)存順序IO延遲在9ns左右
- 內(nèi)存隨機(jī)IO延遲在40ns左右
- 如果程序局部性寫的足夠好,操作系統(tǒng)會(huì)幫你用CPU緩存訪問減少低效的內(nèi)存IO。
個(gè)人公眾號(hào)“開發(fā)內(nèi)功管理”,打通理論與實(shí)踐的任督二脈。