提高代码性能及并发性的方法浅谈
最近在做系統調優,總結了下cache相關知識,以及如何提高性能和并發性能的方法。
一CACHE相關
1. cache概述
cache,中譯名高速緩沖存儲器,其作用是為了更好的利用局部性原理,減少CPU訪問主存的次數。簡單地說,CPU正在訪問的指令和數據,其可能會被以后多次訪問到,或者是該指令和數據附近的內存區域,也可能會被多次訪問。因此,第一次訪問這一塊區域時,將其復制到cache中,以后訪問該區域的指令或者數據時,就不用再從主存中取出。
2. cache結構
假設內存容量為M,內存地址為m位:那么尋址范圍為000…00~FFF…F(m位)
倘若把內存地址分為以下三個區間:
?《深入理解計算機系統》p305 英文版 beta draft ?
?
tag, set index, block offset三個區間有什么用呢?再來看看Cache的邏輯結構吧:
?
將此圖與上圖做對比,可以得出各參數如下:
B = 2^b
S = 2^s
現在來解釋一下各個參數的意義:
一個cache被分為S個組,每個組有E個cacheline,而一個cacheline中,有B個存儲單元,現代處理器中,這個存儲單元一般是以字節(通常8個位)為單位的,也是最小的尋址單元。因此,在一個內存地址中,中間的s位決定了該單元被映射到哪一組,而最低的b位決定了該單元在cacheline中的偏移量。valid通常是一位,代表該cacheline是否是有效的(當該cacheline不存在內存映射時,當然是無效的)。tag就是內存地址的高t位,因為可能會有多個內存地址映射到同一個cacheline中,所以該位是用來校驗該cacheline是否是CPU要訪問的內存單元。
當tag和valid校驗成功是,我們稱為cache命中,這時只要將cache中的單元取出,放入CPU寄存器中即可。
當tag或valid校驗失敗的時候,就說明要訪問的內存單元(也可能是連續的一些單元,如int占4個字節,double占8個字節)并不在cache中,這時就需要去內存中取了,這就是cache不命中的情況(cache miss)。當不命中的情況發生時,系統就會從內存中取得該單元,將其裝入cache中,與此同時也放入CPU寄存器中,等待下一步處理。注意,以下這一點對理解linux cache機制非常重要:
當從內存中取單元到cache中時,會一次取一個cacheline大小的內存區域到cache中,然后存進相應的cacheline中。
例如:我們要取地址 (t, s, b) 內存單元,發生了cache miss,那么系統會取 (t, s, 00…000) 到 (t, s, FF…FFF)的內存單元,將其放入相應的cacheline中。
二 優化性能和提高并發性的主要方法我覺得有:
1.用設計上壓縮結構體大小,使整個結構體能處于一個cache line中。
2.結構體中頻繁訪問的字段盡量放在一個結構體的同一個cache line里面,這樣可以提高cache命中率。
3為了避免cache偽共享問題,cache偽共享多發生在多核cpu的情況,比如有4個cpu,出于并發的考慮,我們可能會設立一個全局數組struct cpu_info test[4];每個cpu去訪問一個test數組元素,這樣會產生cache共享問題,雖然各個cpu都修改各自的數據,但他們對數據的修改,會影響到彼此各個cpu的cache訪問,這就是種偽共享,解決的方法是把數據結構struct cpu_info定義成cache line對齊,即加上____cacheline_aligned前綴。
4.頻繁讀寫的多個結構體變量盡量同時申請,使得它們盡可能的分布在較小的線性空間范圍內,這樣可利用TLB緩沖,提高tlb的命中率。
5.當結構體比較大時,對結構體字段進行初始化或設置值時最好從第一個字段依次往后進行,這樣可保證對內存的訪問是順序進行。這樣可以充分的利用cpu的cache.
6.額外的優化可以采用非暫時移動指令(如movntdq)與預讀指令(如prefetchnta)。比如網卡取數據包時,在取到一個數據包進行處理時,可以使用prefech指令去取接下來的一個數據包,從而達到提高并發能力的效果。不過使用prefech指令時,最好有足夠的時間讓其能并發的取到,否則得不償失,反而降低了設備性能。
7.特殊情況可考慮利用多媒體指令SSE2、SSE4等。
8memset與memecpy對于是非常耗費cpu資源的,在有可能的情況下,盡量避免對大數據結構進行memset操作,可以只對某幾個字段進行賦值處理。若在必須要做memset()的情況下,可以自己開發個優化的版本,比如對于64位的機器,原始的memset()函數(來自內核2.6.18):
static inline void * __memset_generic(void * s, char c,size_t count)
{
int d0, d1;
__asm__ __volatile__(
"rep\n\t"
"stosb"
: "=&c" (d0), "=&D" (d1)
:"a" (c),"1" (s),"0" (count)
:"memory");
return s;
}
該memset()函數其實就是個循環寫,每次寫一個字節,慢的很。我們可以為64位開發個新的版本:
static inline void layout_memset_64(void *address, int len)
{
__u64 *addr = ?(__u64 *)address;
int div = len >> 6;/* ? len / 64 ? */
int md = len % 64;
int n = div - 1;
int ni = n<<3;/* ? n*8 ? */
int i;
for (i = 0; i <= ni; i += 8){
*addr++ ?= 0;
*addr++ ?= 0;
*addr++ ?= 0;
*addr++ ?= 0;
*addr++ ?= 0;
*addr++ ?= 0;
*addr++ ?= 0;
*addr++ ?= 0;
}
div = md >> 3;
md %= 8;
for (i = 0; i < div; i++)
*addr++ = 0;
for (i = 0; i < md; i++){
*((__u8*)addr+i) = 0;
}
return;
}
對于64位的平臺來說,每次寫可以寫入8個字節的數據,循環比上面的函數快了很多,會很大程度提高系統性能。
9使用大頁內存,盡量提高tlb命中率,即cpu中進行地址轉換的快表。大頁內存一個物理頁大小為4m bytes,遠大于普通的4k的物理頁面大小。
10對于提高多核系統的并發性,主要的調優手段主要有盡可能縮小互斥區,縮小鎖的粒度,最好能避免并發的情況,比如采用類似linux 內核中per cpu的方式,同時采用并發性比較好的鎖,如讀寫鎖,互斥鎖。對于一些網絡設備,可以把數據會話固定分擔到各個cpu,從而避免并發。
轉載于:https://www.cnblogs.com/dyllove98/p/3212393.html
總結
以上是生活随笔為你收集整理的提高代码性能及并发性的方法浅谈的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 应用程序之间内容分享详解
- 下一篇: Collection View Prog