在《記憶體隨機也比順序訪問慢,帶你深入理解記憶體IO程序》一文中,我們理解了記憶體IO的內部實作程序,知道了記憶體的隨機IO比順序IO要慢,并對延遲時間進行了大概的估算,那么我們今天來用代碼的方式來時間一下,看看在我們的專案工程中,記憶體訪問的在不同的訪問場景下延時究竟是個什么表現,
先測順序情況
測驗原理就是定義一個指定大小的double(8位元組)陣列,然后以指定的步長去回圈,這里面的變數有兩個,核心代碼如下:
void init_data(double *data, int n){
int i;
for (i = 0; i < n; i++) {
data[i] = i;
}
}
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;
}
在這個核心代碼的基礎上,我們有兩個可調節變數:
- 一是陣列大小,陣列越小,高速快取命中率越高,平均延時就會越低,
- 二是回圈步長,步長越小,順序性越好,同樣也會增加快取命中率,平均延時也低,
我們再測驗的程序中采取的辦法是,固定其中一個變數,然后動態調節另外一個變數來查看效果,
另外說明一下,這個代碼測驗中考慮的幾個額外的開銷的處理情況,
1.加法開銷:由于加法指令簡單,一個CPU周期就可完成,CPU周期比記憶體周期要快,所以暫且忽略它,
2.耗時統計:這涉及到高開銷的系統呼叫,本實驗通過跑1000次取一次耗時的方式來降低影響,
場景一: 固定陣列大小2K,調節步長
| 步長 | 1 | 9 | 17 | 25 | 33 | 41 | 49 | 57 |
|---|---|---|---|---|---|---|---|---|
| 延時ns | 1.28 | 1.28 | 1.33 | 1.30 | 1.30 | 1.41 | 1.45 | 1.4 |
陣列足夠小的時候,L1 cache全部都能裝的下,記憶體IO發生較少,大部分都是高效的快取IO,所以我這里看到的記憶體延時只有1ns左右,這其實只是虛擬地址轉換+L1訪問的延時,
場景二: 固定步長為8,陣列從32K到64M
| 陣列大小 | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
|---|---|---|---|---|---|---|---|---|
| 延時ns | 1.27 | 1.73 | 2.03 | 2.62 | 2.62 | 2.88 | 5.17 | 5.84 |
當陣列越來越大,Cache裝不下,導致穿透高速快取,到記憶體實際IO的次數就會變多,平均耗時就增加
場景三: 步長為32,陣列從32K到64M
| 陣列大小 | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
|---|---|---|---|---|---|---|---|---|
| 延時ns | 1.25 | 1.74 | 2.03 | 2.47 | 2.47 | 3.29 | 7.73 | 8.89 |
和場景二相比,步長變大以后,區域性變差,穿透的記憶體IO進一步增加,雖然資料量一樣大,但是平均耗時就會繼續有所上漲,不過雖然穿透增加,但由于訪問地址仍然相對比較連續,所以即使發生記憶體IO也絕大部分都是行地址不變的順序IO情況,所以耗時在9ns左右,和之前估算大致相符!
另外注意一個細節,就是隨著陣列從64M到32M變化的程序中,耗時有幾個明顯的下降點,分別是8M,256K和32K,這是因為本機的CPU的L1大小是32K,L2是256K,L3是12M,在資料集32K的時候,L1全能裝的下,所有基本都是高速快取IO,256K的時候、8M的時候,雖然L1命中率下降,但是L2、L3訪問速度仍然比真正的記憶體IO快,但是超過12M以后越多,真正的記憶體IO就越來越多了,
再測隨機IO情況
在順序的實驗場景里,陣列的下標訪問都是比較有規律地遞增,在隨機IO的測驗中,我們要徹底打亂這個規律,提前隨機好一個下標陣列,實驗時不停地訪問陣列的各個隨機位置,
void init_data(double *data, int n){
int i;
for (i = 0; i < n; i++) {
data[i] = i;
}
}
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;
}
這實際比上面的實驗多了一次記憶體IO,但由于對random_index_arr的訪問時順序的,而且該陣列也比較小,我們假設它全部能命中高速快取,所以暫且忽略它的影響,
隨機實驗場景: 陣列從32K到64M
| 陣列大小 | 32K | 64K | 256K | 512K | 2M | 8M | 16M | 64M |
|---|---|---|---|---|---|---|---|---|
| 延時ns | 2.4 | 2.4 | 2.4 | 4.8 | 4.8 | 19.2 | 24 | 38.4 |
這次的陣列訪問就沒有步長的概念了,全部打亂,隨機訪問,當資料集比較小的時候、L1、L2、L3還能抗一抗,但當增加到16M、64M以后,穿透到記憶體的IO情況會變多,穿透過去以后極大可能行地址也會變,在64M的資料集中,記憶體的延時竟然下降到了38.4ns,和我們估算的也基本一致,
結論
有了實驗資料的佐證,進一步證實了上一文《深入理解記憶體IO的實際程序!》的結論,記憶體也存在隨機訪問比順序訪問慢的多的情況,大概是4:1的關系,所以不要覺得記憶體很快,就用起來太隨性了!

開發內功修煉之記憶體篇專輯:
- 1.帶你深入理解記憶體對齊最底層原理
- 2.記憶體隨機也比順序訪問慢,帶你深入理解記憶體IO程序
- 3.從DDR到DDR4,記憶體核心頻率其實基本上就沒太大的進步
- 4.實際測驗記憶體在順序IO和隨機IO時的訪問延時差異
- 5.揭穿記憶體廠家“謊言”,實測記憶體帶寬真實表現
- 6.NUMA架構下的記憶體訪問延遲區別!
- 7.PHP7記憶體性能優化的思想精髓
- 8.一次記憶體性能提升的專案實踐
- 9.挑戰Redis單實體記憶體最大極限,“遭遇”NUMA陷阱!
我的公眾號是「開發內功修煉」,在這里我不是單純介紹技術理論,也不只介紹實踐經驗,而是把理論與實踐結合起來,用實踐加深對理論的理解、用理論提高你的技術實踐能力,歡迎你來關注我的公眾號,也請分享給你的好友~~~
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/199343.html
標籤:其他
