高效能kv儲存設計 之 cacheline對齊

2021-10-10 19:41:19 字數 4280 閱讀 5795

最近在設計高讀效能的kv儲存,為了盡可能保證cache的命中,應盡可能將"同時讀取"的資料放到一起,以此來保證資料大概率同時出現在高速cache中。當前大部分計算機的cacheline size為64位元組對齊,即當讀取位址0x403140(64的整數倍 + 0)的時候,會將0x403140 ~ 0x40317f(64的整數倍 + 63)中的內容都載入進快取。以此為參考依據來設計資料結構的大小可以加強資料訪問速率,在極端場景下獲得較大收益。

測試程式

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define size 4*1024

#define step_len 2

struct block

;int

main()

block* addr =

(block*

)mmap

(null

,sizeof

(block)

* size +

sizeof

(block)

, prot_read | prot_write,

map_shared /*map_hugetlb*/

, fd,0)

;if(addr == map_failed)

// block* addr = (block*)malloc(sizeof(block) * size + sizeof(block));

for(

int32_t i =

0; i < size +

1; i++)}

uint64_t int_addr =

(uint64_t

)addr;

uint64_t aligned_addr = int_addr %

64==0?

int_addr : int_addr +

64- int_addr %64;

uint64_t unaligned_addr = int_addr %

64==0?

aligned_addr +

16: int_addr;

int64_t aligned_cost =0;

int64_t unaligned_cost =0;

int round =

10000

; block* test_addr =

null

;while

(round--

)gettimeofday

(&tv_stop,

null);

aligned_cost +

= tv_stop.tv_sec *

1000000

+ tv_stop.tv_usec -

tv_start.tv_sec *

1000000

- tv_start.tv_usec;

test_addr =

(block*

)unaligned_addr;

gettimeofday

(&tv_start,

null);

for(

int32_t i =

0; i < size; i+

=step_len)

gettimeofday

(&tv_stop,

null);

unaligned_cost +

= tv_stop.tv_sec *

1000000

+ tv_stop.tv_usec -

tv_start.tv_sec *

1000000

- tv_start.tv_usec;

}printf

("aligned addr run time: %ld\n"

, aligned_cost)

;printf

("unaligned run time: %ld\n"

, unaligned_cost)

;munmap

(addr,

sizeof

(block)

* size +

sizeof

(block));

close

(fd)

;return0;

}

測試目標

(1)通過不同的編譯引數,對比資料結構設計大小為56b(小於cacheline size)、64b(等於cacheline size)、72b(大於cacheline size)時的讀寫效能

(2)在同一次測試中,對比測試虛擬記憶體起始位址「是否64位元組對齊」的效能差異

程式說明

(1)程式中使用了大記憶體頁進行記憶體分配(須開啟大記憶體頁功能,見文件廣告系統索引應用hugepage調研),其目的是避免「由於block size變化引起的訪存範圍變化,導致記憶體頁表頻繁程度變化」而引入的干擾。測試環境的大記憶體頁大小為2mb,程式中size設定為16k,這樣一來,即使block size為72b,總記憶體(72b * 16k = 1152kb)也不會超過2mb,即只使用乙個大記憶體頁即可,這樣就可規避記憶體頁表置換引入的干擾。

(2)在程式的最開始對記憶體進行了一次遍歷,是為了將所有記憶體預熱一次。這樣做是為了避免後面進行「是否64位元組對齊」效能測試時,由於未預熱導致「先執行的實驗效能注定更差」的干擾

(3)以step_len為步長,遍歷記憶體。遍歷的時候,分為「起始位址64b對齊」和「起始位址64b未對齊」兩種情況,對比效能差距。注意,這裡特意把「起始位址64b對齊」放在前面執行,原因是前面執行的程式更有可能收到「記憶體未預熱」的影響。由於我們期望「起始位址64b對齊」的效能更好,因此要把它放到「更不利」的位置上執行。

(4)為什麼step_len選擇2而不是1?如果step_len取1,那麼當block size小於64b時,前乙個block的最後乙個元素 和 後乙個block的第乙個元素 可能會存在於同乙個cacheline中,這樣會縮小我們的實驗效果

編譯時執行如下命令:

g++ test.cc -o test56 -o2 -dcount=7

g++ test.cc -o test64 -o2 -dcount=8

g++ test.cc -o test72 -o2 -dcount=9

程式中的count巨集定義,是在編譯時通過引數傳入的,這樣便於我們快速修改編譯引數。

執行結果

$ sudo ./test56

block size: 56

aligned addr run time: 204926

unaligned run time: 205065

$ sudo ./test64

block size: 64

aligned addr run time: 172494

unaligned run time: 238219

$ sudo ./test72

block size: 72

aligned addr run time: 273002

unaligned run time: 276078

由此可以得到以下結論:

(1)當block起始位址按照64b(cacheline size)對齊時,block size按照cacheline size設計,其訪存效能明顯優於block size大於cacheline size 或 block size小於cacheline size 的設計;當block起始位址未按照64b(cacheline size)對齊時,block size越小,訪存效能越好

(2)block size相同時,block起始位址按照64b(cacheline size)對齊的訪存效能總是優於block起始位址未按照64b(cacheline size)對齊;其中block size等於64b(cacheline size)時,block起始位址按照64b(cacheline size)對齊的效能提公升尤其明顯

因此在高效能儲存中,資料結構設計應:

(1)block size大小盡可能保證block size * n = 64b(cacheline size),其中n為整數;

(2)block起始位址按照64b(cacheline size)對齊

效能優化之cache

隨著cpu的頻率不斷提公升,而記憶體的訪問速度卻沒有質的突破,為了彌補訪問記憶體的速度慢,充分發揮cpu的計算資源,提高cpu整體吞吐量,在cpu與記憶體之間引入了一級cache。隨著熱點資料體積越來越大,一級cache l1已經不滿足發展的要求,引入了二級cache l2,cache l3cpu ...

高效能CPU設計

cpu從出現到現在,已有幾十年歷史,經過多次迭代改進,由原始的五級流水擴充套件到現在的n級流水,從一次只能執行一條指令到現在的多核超標量處理器,cpu的效能得到巨大提公升。當前,提公升cpu的效能主要有兩個方向 第一,努力提公升cpu單核core的效能,採用超標量技術,支援同時多執行緒 第二,擴充套...

高效能分頁儲存過程

select count 1 from dbo.gatherkeywordinfo dbo.p partpage gatherkeywordinfo gatherkeywordid 5,1000,exec dbo p partpage v gatherkeywordinfo gatherkeywor...